Merge branch 'jc/revert-merge'
authorJunio C Hamano <gitster@pobox.com>
Sun, 4 Nov 2007 08:26:02 +0000 (01:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 4 Nov 2007 08:26:02 +0000 (01:26 -0700)
* jc/revert-merge:
cherry-pick/revert -m: add tests
revert/cherry-pick: work on merge commits as well

Conflicts:

builtin-revert.c

132 files changed:
.gitignore
.mailmap
Documentation/RelNotes-1.5.3.5.txt
Documentation/RelNotes-1.5.4.txt
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/diff-format.txt
Documentation/everyday.txt
Documentation/git-add.txt
Documentation/git-bisect.txt
Documentation/git-branch.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-mv.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rm.txt
Documentation/git-stripspace.txt
Documentation/git-svnimport.txt [deleted file]
Documentation/git-symbolic-ref.txt
Documentation/git-tools.txt
Documentation/git.txt
Makefile
builtin-add.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-count-objects.c
builtin-describe.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-for-each-ref.c
builtin-fsck.c
builtin-gc.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-mv.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c
builtin-prune-packed.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-revert.c
builtin-rm.c
builtin-symbolic-ref.c
builtin-unpack-objects.c
builtin-update-ref.c
builtin.h
cache.h
color.c
color.h
commit.h
connect.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
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
dir.c
dir.h
exec_cmd.c
exec_cmd.h
git-bisect.sh
git-clone.sh
git-cvsexportcommit.perl
git-cvsimport.perl
git-ls-remote.sh
git-mergetool.sh
git-pull.sh
git-rebase--interactive.sh
git-rebase.sh
git-repack.sh
git-sh-setup.sh
git-svn.perl
git-svnimport.perl [deleted file]
git.c
gitk
gitweb/INSTALL
gitweb/gitweb.perl
hash.c [new file with mode: 0644]
hash.h [new file with mode: 0644]
help.c
http-push.c
index-pack.c
log-tree.c
log-tree.h
merge-recursive.c
pack-write.c
pack.h
parse-options.c [new file with mode: 0644]
parse-options.h [new file with mode: 0644]
peek-remote.c
progress.c
progress.h
remote.c
remote.h
run-command.c
run-command.h
send-pack.c
sha1_file.c
shell.c
sideband.c
t/t0021-conversion.sh
t/t0040-parse-options.sh [new file with mode: 0755]
t/t3501-revert-cherry-pick.sh
t/t3901-i18n-patch.sh
t/t5100/sample.mbox
t/t5516-fetch-push.sh
t/t6030-bisect-porcelain.sh
t/t6300-for-each-ref.sh [changed mode: 0644->0755]
t/t7102-reset.sh
t/t9500-gitweb-standalone-no-errors.sh
t/test-lib.sh
test-parse-options.c [new file with mode: 0644]
transport.c
unpack-trees.c
upload-pack.c
wt-status.c
wt-status.h
index 62afef2347bb747aaaf8796e9f9ff5decc647e0e..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
@@ -154,6 +153,7 @@ test-delta
 test-dump-cache-tree
 test-genrandom
 test-match-trees
+test-parse-options
 test-sha1
 common-cmds.h
 *.tar.gz
index 5529b198e8d14decbe4ad99db3f7fb632de0439d..3b2ce578a1a02a86d7a1f0b66b5ea5e51472033b 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -37,6 +37,7 @@ Sam Vilain <sam@vilain.net>
 Santi Béjar <sbejar@gmail.com>
 Sean Estabrooks <seanlkml@sympatico.ca>
 Shawn O. Pearce <spearce@spearce.org>
+Steven Grimm <koreth@midwinter.com>
 Theodore Ts'o <tytso@mit.edu>
 Tony Luck <tony.luck@intel.com>
 Uwe Kleine-König <Uwe_Zeisberger@digi.com>
index 9581e03c49eafa61f8d17b027395aa5613c9b27f..f99a2cd65003c06ef5da26a4508b4ce74d6b267f 100644 (file)
@@ -63,11 +63,32 @@ Fixes since v1.5.3.4
 
  * Git segfaulted when reading an invalid .gitattributes file.  Fixed.
 
- * post-receive-email example hook fixed was fixed for
-   non-fast-forward updates.
+ * post-receive-email example hook was fixed for non-fast-forward
+   updates.
 
  * Documentation updates for supported (but previously undocumented)
    options of "git-archive" and "git-reflog".
 
  * "make clean" no longer deletes the configure script that ships
    with the git tarball, making multiple architecture builds easier.
+
+ * "git-remote show origin" spewed a warning message from Perl
+   when no remote is defined for the current branch via
+   branch.<name>.remote configuration settings.
+
+ * Building with NO_PERL_MAKEMAKER excessively rebuilt contents
+   of perl/ subdirectory by rewriting perl.mak.
+
+ * http.sslVerify configuration settings were not used in scripted
+   Porcelains.
+
+ * "git-add" leaked a bit of memory while scanning for files to add.
+
+ * A few workarounds to squelch false warnings from recent gcc have
+   been added.
+
+ * "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 ceee85723211eacdc3b0c8a3e3fc0327a680b476..133fa64d226d3e1265144c3504c06d6e3f9e8c33 100644 (file)
@@ -4,6 +4,8 @@ GIT v1.5.4 Release Notes
 Updates since v1.5.3
 --------------------
 
+ * Comes with much improved gitk.
+
  * git-reset is now built-in.
 
  * git-send-email can optionally talk over ssmtp and use SMTP-AUTH.
@@ -19,6 +21,29 @@ Updates since v1.5.3
  * git-archive can optionally substitute keywords in files marked with
    export-subst attribute.
 
+ * git-for-each-ref learned %(xxxdate:<dateformat>) syntax to
+   show the various date fields in different formats.
+
+ * git-gc --auto is a low-impact way to automatically run a
+   variant of git-repack that does not lose unreferenced objects
+   (read: safer than the usual one) after the user accumulates
+   too many loose objects.
+
+ * git-push has been rewritten in C.
+
+ * git-push learned --dry-run option to show what would happen
+   if a push is run.
+
+ * git-remote learned "rm" subcommand.
+
+ * git-rebase --interactive mode can now work on detached HEAD.
+
+ * git-cvsserver can be run via git-shell.
+
+ * git-am and git-rebase are far less verbose.
+
+ * git-pull learned to pass --[no-]ff option to underlying git-merge.
+
  * Various Perforce importer updates.
 
 Fixes since v1.5.3
@@ -29,7 +54,6 @@ this release, unless otherwise noted.
 
 --
 exec >/var/tmp/1
-O=v1.5.3.2-99-ge4b2890
+O=v1.5.3.4-450-g952a9e5
 echo O=`git describe refs/heads/master`
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
-
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 edf50cd2113e73daa0a05c16a416424441f010f3..0df004ea261d6862db0a9b528ec7f2c11bdd5b65 100644 (file)
@@ -661,6 +661,15 @@ pack.threads::
        machines. The required amount of memory for the delta search window
        is however multiplied by the number of threads.
 
+pack.indexVersion::
+       Specify the default pack index version.  Valid values are 1 for
+       legacy pack index used by Git versions prior to 1.5.2, and 2 for
+       the new pack index with capabilities for packs larger than 4 GB
+       as well as proper protection against the repacking of corrupted
+       packs.  Version 2 is selected and this config option ignored
+       whenever the corresponding pack is larger than 2 GB.  Otherwise
+       the default is 1.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
index 6b2590d0723ad94a45c9cae174935839df3331d8..99817c53375e6f58b7a3e089cd81e3af5aaffc79 100644 (file)
@@ -553,13 +553,8 @@ can explore on your own.
 
 [NOTE]
 Most likely, you are not directly using the core
-git Plumbing commands, but using Porcelain like Cogito on top
-of it. Cogito works a bit differently and you usually do not
-have to run `git-update-index` yourself for changed files (you
-do tell underlying git about additions and removals via
-`cg-add` and `cg-rm` commands). Just before you make a commit
-with `cg-commit`, Cogito figures out which files you modified,
-and runs `git-update-index` on them for you.
+git Plumbing commands, but using Porcelain such as `git-add`, `git-rm'
+and `git-commit'.
 
 
 Tagging a version
@@ -686,8 +681,8 @@ $ git reset
 
 and in fact a lot of the common git command combinations can be scripted
 with the `git xyz` interfaces.  You can learn things by just looking
-at what the various git scripts do.  For example, `git reset` is the
-above two lines implemented in `git-reset`, but some things like
+at what the various git scripts do.  For example, `git reset` used to be
+the above two lines implemented in `git-reset`, but some things like
 `git status` and `git commit` are slightly more complex scripts around
 the basic git commands.
 
@@ -805,8 +800,8 @@ you have, you can say
 $ git branch
 ------------
 
-which is nothing more than a simple script around `ls .git/refs/heads`.
-There will be asterisk in front of the branch you are currently on.
+which used to be nothing more than a simple script around `ls .git/refs/heads`.
+There will be an asterisk in front of the branch you are currently on.
 
 Sometimes you may wish to create a new branch _without_ actually
 checking it out and switching to it. If so, just use the command
@@ -833,7 +828,7 @@ that branch, and do some work there.
 ------------------------------------------------
 $ git checkout mybranch
 $ echo "Work, work, work" >>hello
-$ git commit -m 'Some work.' -i hello
+$ git commit -m "Some work." -i hello
 ------------------------------------------------
 
 Here, we just added another line to `hello`, and we used a shorthand for
@@ -858,7 +853,7 @@ hasn't happened in the `master` branch at all. Then do
 ------------
 $ echo "Play, play, play" >>hello
 $ echo "Lots of fun" >>example
-$ git commit -m 'Some fun.' -i hello example
+$ git commit -m "Some fun." -i hello example
 ------------
 
 since the master branch is obviously in a much better mood.
@@ -883,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
@@ -952,7 +947,7 @@ the later output lines is used to show commits contained in the
 `master` branch, and the second column for the `mybranch`
 branch. Three commits are shown along with their log messages.
 All of them have non blank characters in the first column (`*`
-shows an ordinary commit on the current branch, `.` is a merge commit), which
+shows an ordinary commit on the current branch, `-` is a merge commit), which
 means they are now part of the `master` branch. Only the "Some
 work" commit has the plus `+` character in the second column,
 because `mybranch` has not been merged to incorporate these
@@ -970,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
@@ -1086,7 +1081,7 @@ to help dumb transport downloaders.
 There are (confusingly enough) `git-ssh-fetch` and `git-ssh-upload`
 programs, which are 'commit walkers'; they outlived their
 usefulness when git Native and SSH transports were introduced,
-and not used by `git pull` or `git push` scripts.
+and are not used by `git pull` or `git push` scripts.
 
 Once you fetch from the remote repository, you `merge` that
 with your current branch.
@@ -1193,7 +1188,7 @@ $ mb=$(git-merge-base HEAD mybranch)
 
 The command writes the commit object name of the common ancestor
 to the standard output, so we captured its output to a variable,
-because we will be using it in the next step.  BTW, the common
+because we will be using it in the next step.  By the way, the common
 ancestor commit is the "New day." commit in this case.  You can
 tell it by:
 
@@ -1459,8 +1454,7 @@ Although git is a truly distributed system, it is often
 convenient to organize your project with an informal hierarchy
 of developers. Linux kernel development is run this way. There
 is a nice illustration (page 17, "Merges to Mainline") in
-link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf
-[Randy Dunlap's presentation].
+link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf[Randy Dunlap's presentation].
 
 It should be stressed that this hierarchy is purely *informal*.
 There is nothing fundamental in git that enforces the "chain of
@@ -1613,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 08c61b1f1ac6ca880c6c3a4f311f65486aabd23e..ce7c170d6982cfd92ca0a23da77cbd6d6901e869 100644 (file)
@@ -109,7 +109,7 @@ $ tar zxf frotz.tar.gz
 $ cd frotz
 $ git-init
 $ git add . <1>
-$ git commit -m 'import of frotz source tree.'
+$ git commit -m "import of frotz source tree."
 $ git tag v2.43 <2>
 ------------
 +
@@ -300,7 +300,7 @@ $ git merge topic/one topic/two && git merge hold/linus <8>
 $ git checkout maint
 $ git cherry-pick master~4 <9>
 $ compile/test
-$ git tag -s -m 'GIT 0.99.9x' v0.99.9x <10>
+$ git tag -s -m "GIT 0.99.9x" v0.99.9x <10>
 $ git fetch ko && git show-branch master maint 'tags/ko-*' <11>
 $ git push ko <12>
 $ git push ko v0.99.9x <13>
index 2fe73555559d5a31a7122c9e5ebb039db9428dbc..963e1ab1e2b7437a32f0aff97dda8dae3c4f47a0 100644 (file)
@@ -50,10 +50,10 @@ OPTIONS
        and `dir/file2`) can be given to add all files in the
        directory, recursively.
 
--n::
+-n, \--dry-run::
         Don't actually add the file(s), just show if they exist.
 
--v::
+-v, \--verbose::
         Be verbose.
 
 -f::
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 b7285bcdbc80a2aa574fa13050c00f1ce9975419..5e81aa4ee15af50bd47aa9b26ffd566328f3c35e 100644 (file)
@@ -85,7 +85,7 @@ OPTIONS
 -a::
        List both remote-tracking branches and local branches.
 
--v::
+-v, --verbose::
        Show sha1 and commit subject line for each head.
 
 --abbrev=<length>::
index 253f4f03c532eb34912f1b5f570f3f6359692fb9..14e58f3866e3a4e7d1da01753cb68082e88ae2f6 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git-clone' [--template=<template_directory>]
          [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare]
          [-o <name>] [-u <upload-pack>] [--reference <repository>]
-         [--depth <depth>] <repository> [<directory>]
+         [--depth <depth>] [--] <repository> [<directory>]
 
 DESCRIPTION
 -----------
@@ -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..895d7503100632f5ab79af548fed3b3a6d79c413 100644 (file)
@@ -152,7 +152,7 @@ definition impossible to preserve signatures at any rate.)
        does this in the '.git-rewrite/' directory but you can override
        that choice by this parameter.
 
--f\|--force::
+-f|--force::
        `git filter-branch` refuses to start with an existing temporary
        directory or when there are already refs starting with
        'refs/original/', unless forced.
@@ -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 2c9cf743c7a097ab955938d023e347e866fbc13e..3b8ca76dff5efb3b5a9f07b459889333518f04ed 100644 (file)
@@ -34,7 +34,7 @@ OPTIONS
        condition. An error happens when a source is neither existing nor
         controlled by GIT, or when it would overwrite an existing
         file unless '-f' is given.
--n::
+-n, \--dry-run::
        Do nothing; only show what would happen
 
 
index 15e3aca9a17cbcc651079968c76b571a9d144696..87afa6f8da0c6421e909663fe79a6f8b0fc0a7d8 100644 (file)
@@ -157,7 +157,7 @@ need to get to the other branch for a quick bugfix.
 ------------
 $ git checkout feature ;# you were working in "feature" branch and
 $ work work work       ;# got interrupted
-$ git commit -a -m 'snapshot WIP'                 <1>
+$ git commit -a -m "snapshot WIP"                 <1>
 $ git checkout master
 $ fix fix fix
 $ git commit ;# commit with real log
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
index be61a821642ada0a0e8e3303aa81463c716fd970..48c1d97f93220ed4d71d3e95e7d75025d77f153d 100644 (file)
@@ -30,7 +30,7 @@ OPTIONS
 -f::
        Override the up-to-date check.
 
--n::
+-n, \--dry-run::
         Don't actually remove the file(s), just show if they exist in
         the index.
 
@@ -51,7 +51,7 @@ OPTIONS
 \--ignore-unmatch::
        Exit with a zero status even if no files matched.
 
-\--quiet::
+-q, \--quiet::
        git-rm normally outputs one line (in the form of an "rm" command)
        for each file removed. This option suppresses that output.
 
index 5212358306a684c006e8ca30c3f7e21fb3c97afb..f80526ba7e013ef58ec43e8861b03696729aaa5b 100644 (file)
@@ -16,7 +16,7 @@ Remove multiple empty lines, and empty lines at beginning and end.
 
 OPTIONS
 -------
--s\|--strip-comments::
+-s|--strip-comments::
        In addition to empty lines, also strip lines starting with '#'.
 
 <stream>::
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 a88f7228605ee35002573123f3dbe63c3e405dc6..694cabab2453ab19cfa4e4fd5eaa186f9b2fa9ba 100644 (file)
@@ -26,7 +26,7 @@ a regular file whose contents is `ref: refs/heads/master`.
 OPTIONS
 -------
 
--q::
+-q, --quiet::
        Do not issue an error message if the <name> is not a
        symbolic ref but a detached HEAD; instead exit with
        non-zero status silently.
index 10653ff898c2278f127f097ddf4a79db5615c802..a96403cb8cb720dbf094b06a0dc0b430147298fc 100644 (file)
@@ -22,6 +22,9 @@ Alternative/Augmentative Porcelains
    providing generally smoother user experience than the "raw" Core GIT
    itself and indeed many other version control systems.
 
+   Cogito is no longer maintained as most of its functionality
+   is now in core GIT.
+
 
    - *pg* (http://www.spearce.org/category/projects/scm/pg/)
 
@@ -33,7 +36,7 @@ Alternative/Augmentative Porcelains
    - *StGit* (http://www.procode.org/stgit/)
 
    Stacked GIT provides a quilt-like patch management functionality in the
-    GIT environment. You can easily manage your patches in the scope of GIT
+   GIT environment. You can easily manage your patches in the scope of GIT
    until they get merged upstream.
 
 
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 72f5ef43ce4479a744f7af57fe32021a00b8a10e..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 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 \
@@ -313,7 +312,7 @@ LIB_OBJS = \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
        convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
-       transport.o bundle.o walker.o
+       transport.o bundle.o walker.o parse-options.o
 
 BUILTIN_OBJS = \
        builtin-add.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)
@@ -976,7 +974,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X test-parse-options$X
 
 all:: $(TEST_PROGRAMS)
 
index f9a65803d8dcbb9ae7eb3a3c61d8ac345b84d1cd..45b14e8a61764e18d8b89ebfef550a820ffbf831 100644 (file)
 #include "diffcore.h"
 #include "commit.h"
 #include "revision.h"
+#include "run-command.h"
+#include "parse-options.h"
 
-static const char builtin_add_usage[] =
-"git-add [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <filepattern>...";
+static const char * const builtin_add_usage[] = {
+       "git-add [options] [--] <filepattern>...",
+       NULL
+};
 
 static int take_worktree_changes;
 static const char *excludes_file;
@@ -44,6 +48,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
                        die("pathspec '%s' did not match any files",
                                        pathspec[i]);
        }
+        free(seen);
 }
 
 static void fill_directory(struct dir_struct *dir, const char **pathspec,
@@ -106,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);
@@ -115,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);
 }
 
@@ -135,6 +138,7 @@ static void refresh(int verbose, const char **pathspec)
                if (!seen[i])
                        die("pathspec '%s' did not match any files", pathspec[i]);
        }
+        free(seen);
 }
 
 static int git_add_config(const char *var, const char *value)
@@ -149,80 +153,63 @@ 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[] =
 "The following paths are ignored by one of your .gitignore files:\n";
 
+static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+static int add_interactive = 0;
+
+static struct option builtin_add_options[] = {
+       OPT__DRY_RUN(&show_only),
+       OPT__VERBOSE(&verbose),
+       OPT_GROUP(""),
+       OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"),
+       OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
+       OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
+       OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+       OPT_END(),
+};
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
-       int i, newfd;
-       int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+       int i, newfd, orig_argc = argc;
        const char **pathspec;
        struct dir_struct dir;
-       int add_interactive = 0;
 
-       for (i = 1; i < argc; i++) {
-               if (!strcmp("--interactive", argv[i]) ||
-                   !strcmp("-i", argv[i]))
-                       add_interactive++;
-       }
+       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 || argc != 2)
+               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);
 
        newfd = hold_locked_index(&lock_file, 1);
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-n")) {
-                       show_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
-                       ignored_too = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-u")) {
-                       take_worktree_changes = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--refresh")) {
-                       refresh_only = 1;
-                       continue;
-               }
-               usage(builtin_add_usage);
-       }
-
        if (take_worktree_changes) {
-               update(verbose, prefix, argv + i);
+               if (read_cache() < 0)
+                       die("index file corrupt");
+               add_files_to_cache(verbose, prefix, argv);
                goto finish;
        }
 
-       if (argc <= i) {
+       if (argc == 0) {
                fprintf(stderr, "Nothing specified, nothing added.\n");
                fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
                return 0;
        }
-       pathspec = get_pathspec(prefix, argv + i);
+       pathspec = get_pathspec(prefix, argv);
 
        if (refresh_only) {
                refresh(verbose, pathspec);
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 8432b823e6ef6020a897838e7a561c3919c31f0b..aedc294eac276af8d1a3e8c39bf93fa8fda93f94 100644 (file)
@@ -2215,9 +2215,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        argv[unk++] = arg;
        }
 
-       if (!incremental)
-               setup_pager();
-
        if (!blame_move_score)
                blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
        if (!blame_copy_score)
@@ -2411,6 +2408,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        read_mailmap(&mailmap, ".mailmap", NULL);
 
+       if (!incremental)
+               setup_pager();
+
        assign_blame(&sb, &revs, opt);
 
        if (incremental)
index 3da8b55b8f49c52794d337a026a4f5a5ae727b24..fbd90e42d111c698d33305570c8f1988998a3c70 100644 (file)
 #include "commit.h"
 #include "builtin.h"
 #include "remote.h"
-
-static const char builtin_branch_usage[] =
-  "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
+#include "parse-options.h"
+
+static const char * const builtin_branch_usage[] = {
+       "git-branch [options] [-r | -a]",
+       "git-branch [options] [-l] [-f] <branchname> [<start-point>]",
+       "git-branch [options] [-r] (-d | -D) <branchname>",
+       "git-branch [options] (-m | -M) [<oldbranch>] <newbranch>",
+       NULL
+};
 
 #define REF_UNKNOWN_TYPE    0x00
 #define REF_LOCAL_BRANCH    0x01
@@ -142,7 +148,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
 
                if (!force &&
                    !in_merge_bases(rev, &head_rev, 1)) {
-                       error("The branch '%s' is not a strict subset of "
+                       error("The branch '%s' is not an ancestor of "
                                "your current HEAD.\n"
                                "If you are sure you want to delete it, "
                                "run 'git branch -D %s'.", argv[i], argv[i]);
@@ -505,93 +511,45 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        int rename = 0, force_rename = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
        int reflog = 0, track;
-       int kinds = REF_LOCAL_BRANCH;
-       int i;
+       int kinds = REF_LOCAL_BRANCH, kind_remote = 0, kind_any = 0;
+
+       struct option options[] = {
+               OPT_GROUP("Generic options"),
+               OPT__VERBOSE(&verbose),
+               OPT_BOOLEAN( 0 , "track",  &track, "set up tracking mode (see git-pull(1))"),
+               OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
+               OPT_BOOLEAN('r', NULL,     &kind_remote, "act on remote-tracking branches"),
+               OPT__ABBREV(&abbrev),
+
+               OPT_GROUP("Specific git-branch actions:"),
+               OPT_BOOLEAN('a', NULL,     &kind_any, "list both remote-tracking and local branches"),
+               OPT_BOOLEAN('d', NULL,     &delete, "delete fully merged branch"),
+               OPT_BOOLEAN('D', NULL,     &force_delete, "delete branch (even if not merged)"),
+               OPT_BOOLEAN('l', NULL,     &reflog, "create the branch's reflog"),
+               OPT_BOOLEAN('f', NULL,     &force_create, "force creation (when already exists)"),
+               OPT_BOOLEAN('m', NULL,     &rename, "move/rename a branch and its reflog"),
+               OPT_BOOLEAN('M', NULL,     &force_rename, "move/rename a branch, even if target exists"),
+               OPT_END(),
+       };
 
        git_config(git_branch_config);
        track = branch_track;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "--track")) {
-                       track = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-track")) {
-                       track = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "-d")) {
-                       delete = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-D")) {
-                       delete = 1;
-                       force_delete = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
-                       force_create = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-m")) {
-                       rename = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-M")) {
-                       rename = 1;
-                       force_rename = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       kinds = REF_REMOTE_BRANCH;
-                       continue;
-               }
-               if (!strcmp(arg, "-a")) {
-                       kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
-                       continue;
-               }
-               if (!strcmp(arg, "-l")) {
-                       reflog = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--no-abbrev")) {
-                       abbrev = 0;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--abbrev=")) {
-                       abbrev = strtoul(arg + 9, NULL, 10);
-                       if (abbrev < MINIMUM_ABBREV)
-                               abbrev = MINIMUM_ABBREV;
-                       else if (abbrev > 40)
-                               abbrev = 40;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--color")) {
-                       branch_use_color = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-color")) {
-                       branch_use_color = 0;
-                       continue;
-               }
-               usage(builtin_branch_usage);
-       }
+       argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
+
+       delete |= force_delete;
+       rename |= force_rename;
+       if (kind_remote)
+               kinds = REF_REMOTE_BRANCH;
+       if (kind_any)
+               kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
+       if (abbrev && abbrev < MINIMUM_ABBREV)
+               abbrev = MINIMUM_ABBREV;
+       else if (abbrev > 40)
+               abbrev = 40;
 
        if ((delete && rename) || (delete && force_create) ||
            (rename && force_create))
-               usage(builtin_branch_usage);
+               usage_with_options(builtin_branch_usage, options);
 
        head = resolve_ref("HEAD", head_sha1, 0, NULL);
        if (!head)
@@ -599,26 +557,25 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        head = xstrdup(head);
        if (!strcmp(head, "HEAD")) {
                detached = 1;
-       }
-       else {
+       } else {
                if (prefixcmp(head, "refs/heads/"))
                        die("HEAD not found below refs/heads!");
                head += 11;
        }
 
        if (delete)
-               return delete_branches(argc - i, argv + i, force_delete, kinds);
-       else if (i == argc)
+               return delete_branches(argc, argv, force_delete, kinds);
+       else if (argc == 0)
                print_ref_list(kinds, detached, verbose, abbrev);
-       else if (rename && (i == argc - 1))
-               rename_branch(head, argv[i], force_rename);
-       else if (rename && (i == argc - 2))
-               rename_branch(argv[i], argv[i + 1], force_rename);
-       else if (i == argc - 1 || i == argc - 2)
-               create_branch(argv[i], (i == argc - 2) ? argv[i+1] : head,
+       else if (rename && (argc == 1))
+               rename_branch(head, argv[0], force_rename);
+       else if (rename && (argc == 2))
+               rename_branch(argv[0], argv[1], force_rename);
+       else if (argc <= 2)
+               create_branch(argv[0], (argc == 2) ? argv[1] : head,
                              force_create, reflog, track);
        else
-               usage(builtin_branch_usage);
+               usage_with_options(builtin_branch_usage, options);
 
        return 0;
 }
index 4274ec19500953bd7b6775e6d66271e9e116fa86..f00306fb677acb6003444b931dc9b2bf719bc562 100644 (file)
@@ -6,8 +6,7 @@
 
 #include "cache.h"
 #include "builtin.h"
-
-static const char count_objects_usage[] = "git-count-objects [-v]";
+#include "parse-options.h"
 
 static void count_objects(DIR *d, char *path, int len, int verbose,
                          unsigned long *loose,
@@ -67,29 +66,28 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
        }
 }
 
-int cmd_count_objects(int ac, const char **av, const char *prefix)
+static char const * const count_objects_usage[] = {
+       "git-count-objects [-v]",
+       NULL
+};
+
+int cmd_count_objects(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       int verbose = 0;
+       int i, verbose = 0;
        const char *objdir = get_object_directory();
        int len = strlen(objdir);
        char *path = xmalloc(len + 50);
        unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
        unsigned long loose_size = 0;
+       struct option opts[] = {
+               OPT__VERBOSE(&verbose),
+               OPT_END(),
+       };
 
-       for (i = 1; i < ac; i++) {
-               const char *arg = av[i];
-               if (*arg != '-')
-                       break;
-               else if (!strcmp(arg, "-v"))
-                       verbose = 1;
-               else
-                       usage(count_objects_usage);
-       }
-
+       argc = parse_options(argc, argv, opts, count_objects_usage, 0);
        /* we do not take arguments other than flags for now */
-       if (i < ac)
-               usage(count_objects_usage);
+       if (argc)
+               usage_with_options(count_objects_usage, opts);
        memcpy(path, objdir, len);
        if (len && objdir[len-1] != '/')
                path[len++] = '/';
index 669110cb0645629ca5b152d8328aa91d63be1550..6eeb9b50456da53e396136c11904bbdc3d36a921 100644 (file)
@@ -4,12 +4,15 @@
 #include "refs.h"
 #include "builtin.h"
 #include "exec_cmd.h"
+#include "parse-options.h"
 
 #define SEEN           (1u<<0)
 #define MAX_TAGS       (FLAG_BITS - 1)
 
-static const char describe_usage[] =
-"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
+static const char * const describe_usage[] = {
+       "git-describe [options] <committish>*",
+       NULL
+};
 
 static int debug;      /* Display lots of verbose info */
 static int all;        /* Default to annotated tags only */
@@ -242,57 +245,42 @@ static void describe(const char *arg, int last_one)
 
 int cmd_describe(int argc, const char **argv, const char *prefix)
 {
-       int i;
        int contains = 0;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "contains",   &contains, "find the tag that comes after the commit"),
+               OPT_BOOLEAN(0, "debug",      &debug, "debug search strategy on stderr"),
+               OPT_BOOLEAN(0, "all",        &all, "use any ref in .git/refs"),
+               OPT_BOOLEAN(0, "tags",       &tags, "use any tag in .git/refs/tags"),
+               OPT__ABBREV(&abbrev),
+               OPT_INTEGER(0, "candidates", &max_candidates,
+                                       "consider <n> most recent tags (default: 10)"),
+               OPT_END(),
+       };
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
-               else if (!strcmp(arg, "--contains"))
-                       contains = 1;
-               else if (!strcmp(arg, "--debug"))
-                       debug = 1;
-               else if (!strcmp(arg, "--all"))
-                       all = 1;
-               else if (!strcmp(arg, "--tags"))
-                       tags = 1;
-               else if (!prefixcmp(arg, "--abbrev=")) {
-                       abbrev = strtoul(arg + 9, NULL, 10);
-                       if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev))
-                               abbrev = DEFAULT_ABBREV;
-               }
-               else if (!prefixcmp(arg, "--candidates=")) {
-                       max_candidates = strtoul(arg + 13, NULL, 10);
-                       if (max_candidates < 1)
-                               max_candidates = 1;
-                       else if (max_candidates > MAX_TAGS)
-                               max_candidates = MAX_TAGS;
-               }
-               else
-                       usage(describe_usage);
-       }
+       argc = parse_options(argc, argv, options, describe_usage, 0);
+       if (max_candidates < 1)
+               max_candidates = 1;
+       else if (max_candidates > MAX_TAGS)
+               max_candidates = MAX_TAGS;
 
        save_commit_buffer = 0;
 
        if (contains) {
-               const char **args = xmalloc((4 + argc - i) * sizeof(char*));
+               const char **args = xmalloc((4 + argc) * sizeof(char*));
                args[0] = "name-rev";
                args[1] = "--name-only";
                args[2] = "--tags";
-               memcpy(args + 3, argv + i, (argc - i) * sizeof(char*));
-               args[3 + argc - i] = NULL;
-               return cmd_name_rev(3 + argc - i, args, prefix);
+               memcpy(args + 3, argv, argc * sizeof(char*));
+               args[3 + argc] = NULL;
+               return cmd_name_rev(3 + argc, args, prefix);
        }
 
-       if (argc <= i)
+       if (argc == 0) {
                describe("HEAD", 1);
-       else
-               while (i < argc) {
-                       describe(argv[i], (i == argc - 1));
-                       i++;
+       } else {
+               while (argc-- > 0) {
+                       describe(*argv++, argc == 0);
                }
-
+       }
        return 0;
 }
index 8f25d509a0b1bace78d0cb3f2a219b23e8b66d18..862652be92f65c50aaa900fd9983f8c54840d672 100644 (file)
@@ -7,15 +7,17 @@
 #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;
 static int unpack_limit = 100;
-static struct fetch_pack_args args;
+static struct fetch_pack_args args = {
+       /* .uploadpack = */ "git-upload-pack",
+};
 
 static const char fetch_pack_usage[] =
 "git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [--no-progress] [-v] [<host>:]<directory> [<refs>...]";
-static const char *uploadpack = "git-upload-pack";
 
 #define COMPLETE       (1U << 0)
 #define COMMON         (1U << 1)
@@ -456,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) {
@@ -519,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)
@@ -544,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],
@@ -762,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;
 
@@ -773,16 +747,14 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                        st.st_mtime = 0;
        }
 
-       pid = git_connect(fd, (char *)dest, 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 b9d2b0c27e3c09dd2746db94c25c00eaa492ba7c..5f5b59bfdb5056dfedf692215db2f9e4207f8580 100644 (file)
@@ -48,15 +48,21 @@ static void add_merge_config(struct ref **head,
                if (rm)
                        continue;
 
-               /* Not fetched to a tracking branch?  We need to fetch
+               /*
+                * Not fetched to a tracking branch?  We need to fetch
                 * it anyway to allow this branch's "branch.$name.merge"
-                * to be honored by git-pull.
+                * to be honored by git-pull, but we do not have to
+                * fail if branch.$name.merge is misconfigured to point
+                * at a nonexisting branch.  If we were indeed called by
+                * git-pull, it will notice the misconfiguration because
+                * there is no entry in the resulting FETCH_HEAD marked
+                * for merging.
                 */
                refspec.src = branch->merge[i]->src;
                refspec.dst = NULL;
                refspec.pattern = 0;
                refspec.force = 0;
-               get_fetch_map(remote_refs, &refspec, tail);
+               get_fetch_map(remote_refs, &refspec, tail, 1);
                for (rm = *old_tail; rm; rm = rm->next)
                        rm->merge = 1;
        }
@@ -75,7 +81,7 @@ static struct ref *get_ref_map(struct transport *transport,
 
        if (ref_count || tags) {
                for (i = 0; i < ref_count; i++) {
-                       get_fetch_map(remote_refs, &refs[i], &tail);
+                       get_fetch_map(remote_refs, &refs[i], &tail, 0);
                        if (refs[i].dst && refs[i].dst[0])
                                *autotags = 1;
                }
@@ -88,7 +94,7 @@ static struct ref *get_ref_map(struct transport *transport,
                        refspec.dst = "refs/tags/";
                        refspec.pattern = 1;
                        refspec.force = 0;
-                       get_fetch_map(remote_refs, &refspec, &tail);
+                       get_fetch_map(remote_refs, &refspec, &tail, 0);
                }
        } else {
                /* Use the defaults */
@@ -97,7 +103,7 @@ static struct ref *get_ref_map(struct transport *transport,
                int has_merge = branch_has_merge_config(branch);
                if (remote && (remote->fetch_refspec_nr || has_merge)) {
                        for (i = 0; i < remote->fetch_refspec_nr; i++) {
-                               get_fetch_map(remote_refs, &remote->fetch[i], &tail);
+                               get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0);
                                if (remote->fetch[i].dst &&
                                    remote->fetch[i].dst[0])
                                        *autotags = 1;
@@ -110,11 +116,13 @@ static struct ref *get_ref_map(struct transport *transport,
                         * as given in branch.<name>.remote, we add the
                         * ref given in branch.<name>.merge, too.
                         */
-                       if (has_merge && !strcmp(branch->remote_name,
-                                               remote->name))
+                       if (has_merge &&
+                           !strcmp(branch->remote_name, remote->name))
                                add_merge_config(&ref_map, remote_refs, branch, &tail);
                } else {
                        ref_map = get_remote_ref(remote_refs, "HEAD");
+                       if (!ref_map)
+                               die("Couldn't find remote ref HEAD");
                        ref_map->merge = 1;
                }
        }
@@ -123,12 +131,6 @@ static struct ref *get_ref_map(struct transport *transport,
        return ref_map;
 }
 
-static void show_new(enum object_type type, unsigned char *sha1_new)
-{
-       fprintf(stderr, "  %s: %s\n", typename(type),
-               find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
-}
-
 static int s_update_ref(const char *action,
                        struct ref *ref,
                        int check_old)
@@ -149,34 +151,38 @@ static int s_update_ref(const char *action,
        return 0;
 }
 
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
 static int update_local_ref(struct ref *ref,
-                           const char *note,
-                           int verbose)
+                           const char *remote,
+                           int verbose,
+                           char *display)
 {
-       char oldh[41], newh[41];
        struct commit *current = NULL, *updated;
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
+       const char *pretty_ref = ref->name + (
+               !prefixcmp(ref->name, "refs/heads/") ? 11 :
+               !prefixcmp(ref->name, "refs/tags/") ? 10 :
+               !prefixcmp(ref->name, "refs/remotes/") ? 13 :
+               0);
 
+       *display = 0;
        type = sha1_object_info(ref->new_sha1, NULL);
        if (type < 0)
                die("object %s not found", sha1_to_hex(ref->new_sha1));
 
        if (!*ref->name) {
                /* Not storing */
-               if (verbose) {
-                       fprintf(stderr, "* fetched %s\n", note);
-                       show_new(type, ref->new_sha1);
-               }
+               if (verbose)
+                       sprintf(display, "* branch %s -> FETCH_HEAD", remote);
                return 0;
        }
 
        if (!hashcmp(ref->old_sha1, ref->new_sha1)) {
-               if (verbose) {
-                       fprintf(stderr, "* %s: same as %s\n",
-                               ref->name, note);
-                       show_new(type, ref->new_sha1);
-               }
+               if (verbose)
+                       sprintf(display, "= %-*s %s -> %s", SUMMARY_WIDTH,
+                               "[up to date]", remote, pretty_ref);
                return 0;
        }
 
@@ -188,63 +194,65 @@ static int update_local_ref(struct ref *ref,
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
-               fprintf(stderr,
-                       " * %s: Cannot fetch into the current branch.\n",
-                       ref->name);
+               sprintf(display, "! %-*s %s -> %s  (can't  fetch in current branch)",
+                       SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
                return 1;
        }
 
        if (!is_null_sha1(ref->old_sha1) &&
            !prefixcmp(ref->name, "refs/tags/")) {
-               fprintf(stderr, "* %s: updating with %s\n",
-                       ref->name, note);
-               show_new(type, ref->new_sha1);
+               sprintf(display, "- %-*s %s -> %s",
+                       SUMMARY_WIDTH, "[tag update]", remote, pretty_ref);
                return s_update_ref("updating tag", ref, 0);
        }
 
        current = lookup_commit_reference_gently(ref->old_sha1, 1);
        updated = lookup_commit_reference_gently(ref->new_sha1, 1);
        if (!current || !updated) {
-               char *msg;
-               if (!strncmp(ref->name, "refs/tags/", 10))
+               const char *msg;
+               const char *what;
+               if (!strncmp(ref->name, "refs/tags/", 10)) {
                        msg = "storing tag";
-               else
+                       what = "[new tag]";
+               }
+               else {
                        msg = "storing head";
-               fprintf(stderr, "* %s: storing %s\n",
-                       ref->name, note);
-               show_new(type, ref->new_sha1);
+                       what = "[new branch]";
+               }
+
+               sprintf(display, "* %-*s %s -> %s",
+                       SUMMARY_WIDTH, what, remote, pretty_ref);
                return s_update_ref(msg, ref, 0);
        }
 
-       strcpy(oldh, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
-       strcpy(newh, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-
        if (in_merge_bases(current, &updated, 1)) {
-               fprintf(stderr, "* %s: fast forward to %s\n",
-                       ref->name, note);
-               fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
+               char quickref[83];
+               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+               strcat(quickref, "..");
+               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               sprintf(display, "  %-*s %s -> %s  (fast forward)",
+                       SUMMARY_WIDTH, quickref, remote, pretty_ref);
                return s_update_ref("fast forward", ref, 1);
-       }
-       if (!force && !ref->force) {
-               fprintf(stderr,
-                       "* %s: not updating to non-fast forward %s\n",
-                       ref->name, note);
-               fprintf(stderr,
-                       "  old...new: %s...%s\n", oldh, newh);
+       } else if (force || ref->force) {
+               char quickref[84];
+               strcpy(quickref, find_unique_abbrev(current->object.sha1, DEFAULT_ABBREV));
+               strcat(quickref, "...");
+               strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
+               sprintf(display, "+ %-*s %s -> %s  (forced update)",
+                       SUMMARY_WIDTH, quickref, remote, pretty_ref);
+               return s_update_ref("forced-update", ref, 1);
+       } else {
+               sprintf(display, "! %-*s %s -> %s  (non fast forward)",
+                       SUMMARY_WIDTH, "[rejected]", remote, pretty_ref);
                return 1;
        }
-       fprintf(stderr,
-               "* %s: forcing update to non-fast forward %s\n",
-               ref->name, note);
-       fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
-       return s_update_ref("forced-update", ref, 1);
 }
 
 static void store_updated_refs(const char *url, struct ref *ref_map)
 {
        FILE *fp;
        struct commit *commit;
-       int url_len, i, note_len;
+       int url_len, i, note_len, shown_url = 0;
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
@@ -307,8 +315,17 @@ static void store_updated_refs(const char *url, struct ref *ref_map)
                        rm->merge ? "" : "not-for-merge",
                        note);
 
-               if (ref)
-                       update_local_ref(ref, note, verbose);
+               if (ref) {
+                       update_local_ref(ref, what, verbose, note);
+                       if (*note) {
+                               if (!shown_url) {
+                                       fprintf(stderr, "From %.*s\n",
+                                                       url_len, url);
+                                       shown_url = 1;
+                               }
+                               fprintf(stderr, " %s\n", note);
+                       }
+               }
        }
        fclose(fp);
 }
@@ -368,9 +385,6 @@ static struct ref *find_non_local_tags(struct transport *transport,
                if (!path_list_has_path(&existing_refs, ref_name) &&
                    !path_list_has_path(&new_refs, ref_name) &&
                    lookup_object(ref->old_sha1)) {
-                       fprintf(stderr, "Auto-following %s\n",
-                               ref_name);
-
                        path_list_insert(ref_name, &new_refs);
 
                        rm = alloc_ref(strlen(ref_name) + 1);
@@ -509,7 +523,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                        depth = argv[i];
                        continue;
                }
-               if (!strcmp(arg, "--quiet")) {
+               if (!strcmp(arg, "--quiet") || !strcmp(arg, "-q")) {
                        quiet = 1;
                        continue;
                }
index c74ef2800c839a5537707c5c64aa55acb2a9efad..da8c7948e63609aeeae20cde87b519933a5a4207 100644 (file)
@@ -7,6 +7,7 @@
 #include "tree.h"
 #include "blob.h"
 #include "quote.h"
+#include "parse-options.h"
 
 /* Quoting styles */
 #define QUOTE_NONE 0
@@ -158,17 +159,18 @@ static const char *find_next(const char *cp)
  * Make sure the format string is well formed, and parse out
  * the used atoms.
  */
-static void verify_format(const char *format)
+static int verify_format(const char *format)
 {
        const char *cp, *sp;
        for (cp = format; *cp && (sp = find_next(cp)); ) {
                const char *ep = strchr(sp, ')');
                if (!ep)
-                       die("malformatted format string %s", sp);
+                       return error("malformatted format string %s", sp);
                /* sp points at "%(" and ep points at the closing ")" */
                parse_atom(sp + 2, ep);
                cp = ep + 1;
        }
+       return 0;
 }
 
 /*
@@ -800,94 +802,76 @@ static struct ref_sort *default_sort(void)
        return sort;
 }
 
-int cmd_for_each_ref(int ac, const char **av, const char *prefix)
+int opt_parse_sort(const struct option *opt, const char *arg, int unset)
+{
+       struct ref_sort **sort_tail = opt->value;
+       struct ref_sort *s;
+       int len;
+
+       if (!arg) /* should --no-sort void the list ? */
+               return -1;
+
+       *sort_tail = s = xcalloc(1, sizeof(*s));
+       sort_tail = &s->next;
+
+       if (*arg == '-') {
+               s->reverse = 1;
+               arg++;
+       }
+       len = strlen(arg);
+       s->atom = parse_atom(arg, arg+len);
+       return 0;
+}
+
+static char const * const for_each_ref_usage[] = {
+       "git-for-each-ref [options] [<pattern>]",
+       NULL
+};
+
+int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
 {
        int i, num_refs;
-       const char *format = NULL;
+       const char *format = "%(objectname) %(objecttype)\t%(refname)";
        struct ref_sort *sort = NULL, **sort_tail = &sort;
-       int maxcount = 0;
-       int quote_style = -1; /* unspecified yet */
+       int maxcount = 0, quote_style;
+       int quote_shell = 0, quote_perl = 0, quote_python = 0, quote_tcl = 0;
        struct refinfo **refs;
        struct grab_ref_cbdata cbdata;
 
-       for (i = 1; i < ac; i++) {
-               const char *arg = av[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!prefixcmp(arg, "--format=")) {
-                       if (format)
-                               die("more than one --format?");
-                       format = arg + 9;
-                       continue;
-               }
-               if (!strcmp(arg, "-s") || !strcmp(arg, "--shell") ) {
-                       if (0 <= quote_style)
-                               die("more than one quoting style?");
-                       quote_style = QUOTE_SHELL;
-                       continue;
-               }
-               if (!strcmp(arg, "-p") || !strcmp(arg, "--perl") ) {
-                       if (0 <= quote_style)
-                               die("more than one quoting style?");
-                       quote_style = QUOTE_PERL;
-                       continue;
-               }
-               if (!strcmp(arg, "--python") ) {
-                       if (0 <= quote_style)
-                               die("more than one quoting style?");
-                       quote_style = QUOTE_PYTHON;
-                       continue;
-               }
-               if (!strcmp(arg, "--tcl") ) {
-                       if (0 <= quote_style)
-                               die("more than one quoting style?");
-                       quote_style = QUOTE_TCL;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--count=")) {
-                       if (maxcount)
-                               die("more than one --count?");
-                       maxcount = atoi(arg + 8);
-                       if (maxcount <= 0)
-                               die("The number %s did not parse", arg);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--sort=")) {
-                       struct ref_sort *s = xcalloc(1, sizeof(*s));
-                       int len;
-
-                       s->next = NULL;
-                       *sort_tail = s;
-                       sort_tail = &s->next;
-
-                       arg += 7;
-                       if (*arg == '-') {
-                               s->reverse = 1;
-                               arg++;
-                       }
-                       len = strlen(arg);
-                       sort->atom = parse_atom(arg, arg+len);
-                       continue;
-               }
-               break;
+       struct option opts[] = {
+               OPT_BOOLEAN('s', "shell", &quote_shell, "quote placeholders suitably for shells"),
+               OPT_BOOLEAN('p', "perl",  &quote_perl, "quote placeholders suitably for perl"),
+               OPT_BOOLEAN( 0 , "python", &quote_python, "quote placeholders suitably for python"),
+               OPT_BOOLEAN( 0 , "tcl",  &quote_tcl, "quote placeholders suitably for tcl"),
+
+               OPT_GROUP(""),
+               OPT_INTEGER( 0 , "count", &maxcount, "show only <n> matched refs"),
+               OPT_STRING(  0 , "format", &format, "format", "format to use for the output"),
+               OPT_CALLBACK(0 , "sort", &sort_tail, "key",
+                           "field name to sort on", &opt_parse_sort),
+               OPT_END(),
+       };
+
+       parse_options(argc, argv, opts, for_each_ref_usage, 0);
+       if (maxcount < 0) {
+               error("invalid --count argument: `%d'", maxcount);
+               usage_with_options(for_each_ref_usage, opts);
        }
-       if (quote_style < 0)
-               quote_style = QUOTE_NONE;
+       if (quote_shell + quote_perl + quote_python + quote_tcl > 1) {
+               error("more than one quoting style ?");
+               usage_with_options(for_each_ref_usage, opts);
+       }
+       if (verify_format(format))
+               usage_with_options(for_each_ref_usage, opts);
 
+       quote_style = QUOTE_SHELL * quote_shell + QUOTE_PERL * quote_perl +
+               QUOTE_PYTHON * quote_python + QUOTE_TCL * quote_tcl;
        if (!sort)
                sort = default_sort();
        sort_atom_limit = used_atom_cnt;
-       if (!format)
-               format = "%(objectname) %(objecttype)\t%(refname)";
-
-       verify_format(format);
 
        memset(&cbdata, 0, sizeof(cbdata));
-       cbdata.grab_pattern = av + i;
+       cbdata.grab_pattern = argv;
        for_each_ref(grab_single_ref, &cbdata);
        refs = cbdata.grab_array;
        num_refs = cbdata.grab_cnt;
index 8d12287f037c499acad26ea81acab73490c38d5c..e4874f64a3742b0b54ba2c25f398bece41a5015e 100644 (file)
@@ -8,6 +8,7 @@
 #include "pack.h"
 #include "cache-tree.h"
 #include "tree-walk.h"
+#include "parse-options.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -666,9 +667,24 @@ static int fsck_cache_tree(struct cache_tree *it)
        return err;
 }
 
-static const char fsck_usage[] =
-"git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] "
-"[--strict] [--verbose] <head-sha1>*]";
+static char const * const fsck_usage[] = {
+       "git-fsck [options] [<object>...]",
+       NULL
+};
+
+static struct option fsck_opts[] = {
+       OPT__VERBOSE(&verbose),
+       OPT_BOOLEAN(0, "unreachable", &show_unreachable, "show unreachable objects"),
+       OPT_BOOLEAN(0, "tags", &show_tags, "report tags"),
+       OPT_BOOLEAN(0, "root", &show_root, "report root nodes"),
+       OPT_BOOLEAN(0, "cache", &keep_cache_objects, "make index objects head nodes"),
+       OPT_BOOLEAN(0, "reflogs", &include_reflogs, "make reflogs head nodes (default)"),
+       OPT_BOOLEAN(0, "full", &check_full, "also consider alternate objects"),
+       OPT_BOOLEAN(0, "strict", &check_strict, "enable more strict checking"),
+       OPT_BOOLEAN(0, "lost-found", &write_lost_and_found,
+                               "write dangling objects in .git/lost-found"),
+       OPT_END(),
+};
 
 int cmd_fsck(int argc, const char **argv, const char *prefix)
 {
@@ -677,49 +693,10 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        track_object_refs = 1;
        errors_found = 0;
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (!strcmp(arg, "--unreachable")) {
-                       show_unreachable = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--tags")) {
-                       show_tags = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--root")) {
-                       show_root = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--cache")) {
-                       keep_cache_objects = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-reflogs")) {
-                       include_reflogs = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--full")) {
-                       check_full = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--strict")) {
-                       check_strict = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--verbose")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--lost-found")) {
-                       check_full = 1;
-                       include_reflogs = 0;
-                       write_lost_and_found = 1;
-                       continue;
-               }
-               if (*arg == '-')
-                       usage(fsck_usage);
+       argc = parse_options(argc, argv, fsck_opts, fsck_usage, 0);
+       if (write_lost_and_found) {
+               check_full = 1;
+               include_reflogs = 0;
        }
 
        fsck_head_link();
@@ -741,22 +718,18 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
                        verify_pack(p, 0);
 
                for (p = packed_git; p; p = p->next) {
-                       uint32_t i, num;
+                       uint32_t j, num;
                        if (open_pack_index(p))
                                continue;
                        num = p->num_objects;
-                       for (i = 0; i < num; i++)
-                               fsck_sha1(nth_packed_object_sha1(p, i));
+                       for (j = 0; j < num; j++)
+                               fsck_sha1(nth_packed_object_sha1(p, j));
                }
        }
 
        heads = 0;
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-
-               if (*arg == '-')
-                       continue;
-
                if (!get_sha1(arg, head_sha1)) {
                        struct object *obj = lookup_object(head_sha1);
 
@@ -783,7 +756,6 @@ int cmd_fsck(int argc, const char **argv, const char *prefix)
        }
 
        if (keep_cache_objects) {
-               int i;
                read_cache();
                for (i = 0; i < active_nr; i++) {
                        unsigned int mode;
index 3a2ca4f901b985c45820c8a5f68061cf1c647f30..c5bce893a606b58dad73ff5d791d4a479a1859e4 100644 (file)
 
 #include "builtin.h"
 #include "cache.h"
+#include "parse-options.h"
 #include "run-command.h"
 
 #define FAILED_RUN "failed to run %s"
 
-static const char builtin_gc_usage[] = "git-gc [--prune] [--aggressive]";
+static const char * const builtin_gc_usage[] = {
+       "git-gc [options]",
+       NULL
+};
 
 static int pack_refs = 1;
 static int aggressive_window = -1;
@@ -165,38 +169,34 @@ static int need_to_gc(void)
 
 int cmd_gc(int argc, const char **argv, const char *prefix)
 {
-       int i;
        int prune = 0;
+       int aggressive = 0;
        int auto_gc = 0;
        char buf[80];
 
+       struct option builtin_gc_options[] = {
+               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced loose objects"),
+               OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
+               OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
+               OPT_END()
+       };
+
        git_config(gc_config);
 
        if (pack_refs < 0)
                pack_refs = !is_bare_repository();
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--prune")) {
-                       prune = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--aggressive")) {
-                       append_option(argv_repack, "-f", MAX_ADD);
-                       if (aggressive_window > 0) {
-                               sprintf(buf, "--window=%d", aggressive_window);
-                               append_option(argv_repack, buf, MAX_ADD);
-                       }
-                       continue;
-               }
-               if (!strcmp(arg, "--auto")) {
-                       auto_gc = 1;
-                       continue;
+       argc = parse_options(argc, argv, builtin_gc_options, builtin_gc_usage, 0);
+       if (argc > 0)
+               usage_with_options(builtin_gc_usage, builtin_gc_options);
+
+       if (aggressive) {
+               append_option(argv_repack, "-f", MAX_ADD);
+               if (aggressive_window > 0) {
+                       sprintf(buf, "--window=%d", aggressive_window);
+                       append_option(argv_repack, buf, MAX_ADD);
                }
-               break;
        }
-       if (i != argc)
-               usage(builtin_gc_usage);
 
        if (auto_gc) {
                /*
index fb12248f825807b085f4e5ed761002c30925ead5..2600847974f8a44bdd2148da6ff82ecf761fb193 100644 (file)
@@ -915,6 +915,7 @@ static void handle_info(void)
 static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
                    const char *msg, const char *patch)
 {
+       int peek;
        keep_subject = ks;
        metainfo_charset = encoding;
        fin = in;
@@ -935,6 +936,11 @@ static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
        p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
        s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(char *));
 
+       do {
+               peek = fgetc(in);
+       } while (isspace(peek));
+       ungetc(peek, in);
+
        /* process the email header */
        while (read_one_header_line(line, sizeof(line), fin))
                check_header(line, sizeof(line), p_hdr_data, 1);
index 43fc373a15cbe935054b47f9bd67c04ecf216e4e..74b04706f36128711719fff3e1423b3fb454af4c 100644 (file)
@@ -164,6 +164,7 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
 {
        char name[PATH_MAX];
        int ret = -1;
+       int peek;
 
        FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
        int file_done = 0;
@@ -173,6 +174,11 @@ static int split_mbox(const char *file, const char *dir, int allow_bare,
                goto out;
        }
 
+       do {
+               peek = fgetc(f);
+       } while (isspace(peek));
+       ungetc(peek, f);
+
        if (fgets(buf, sizeof(buf), f) == NULL) {
                /* empty stdin is OK */
                if (f != stdin) {
index b9446516915c998e41e645a7cc3e28c25ee21dc1..a3f9ad174492827e42f2854287fab19915d8ef76 100644 (file)
@@ -8,9 +8,12 @@
 #include "dir.h"
 #include "cache-tree.h"
 #include "path-list.h"
+#include "parse-options.h"
 
-static const char builtin_mv_usage[] =
-"git-mv [-n] [-f] (<source> <destination> | [-k] <source>... <destination>)";
+static const char * const builtin_mv_usage[] = {
+       "git-mv [options] <source>... <destination>",
+       NULL
+};
 
 static const char **copy_pathspec(const char *prefix, const char **pathspec,
                                  int count, int base_name)
@@ -61,8 +64,14 @@ static struct lock_file lock_file;
 
 int cmd_mv(int argc, const char **argv, const char *prefix)
 {
-       int i, newfd, count;
+       int i, newfd;
        int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
+       struct option builtin_mv_options[] = {
+               OPT__DRY_RUN(&show_only),
+               OPT_BOOLEAN('f', NULL, &force, "force move/rename even if target exists"),
+               OPT_BOOLEAN('k', NULL, &ignore_errors, "skip move/rename errors"),
+               OPT_END(),
+       };
        const char **source, **destination, **dest_path;
        enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
        struct stat st;
@@ -78,52 +87,29 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
        if (read_cache() < 0)
                die("index file corrupt");
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
+       argc = parse_options(argc, argv, builtin_mv_options, builtin_mv_usage, 0);
+       if (--argc < 1)
+               usage_with_options(builtin_mv_usage, builtin_mv_options);
 
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-n")) {
-                       show_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
-                       force = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-k")) {
-                       ignore_errors = 1;
-                       continue;
-               }
-               usage(builtin_mv_usage);
-       }
-       count = argc - i - 1;
-       if (count < 1)
-               usage(builtin_mv_usage);
-
-       source = copy_pathspec(prefix, argv + i, count, 0);
-       modes = xcalloc(count, sizeof(enum update_mode));
-       dest_path = copy_pathspec(prefix, argv + argc - 1, 1, 0);
+       source = copy_pathspec(prefix, argv, argc, 0);
+       modes = xcalloc(argc, sizeof(enum update_mode));
+       dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
 
        if (dest_path[0][0] == '\0')
                /* special case: "." was normalized to "" */
-               destination = copy_pathspec(dest_path[0], argv + i, count, 1);
+               destination = copy_pathspec(dest_path[0], argv, argc, 1);
        else if (!lstat(dest_path[0], &st) &&
                        S_ISDIR(st.st_mode)) {
                dest_path[0] = add_slash(dest_path[0]);
-               destination = copy_pathspec(dest_path[0], argv + i, count, 1);
+               destination = copy_pathspec(dest_path[0], argv, argc, 1);
        } else {
-               if (count != 1)
-                       usage(builtin_mv_usage);
+               if (argc != 1)
+                       usage_with_options(builtin_mv_usage, builtin_mv_options);
                destination = dest_path;
        }
 
        /* Checking */
-       for (i = 0; i < count; i++) {
+       for (i = 0; i < argc; i++) {
                const char *src = source[i], *dst = destination[i];
                int length, src_is_dir;
                const char *bad = NULL;
@@ -167,13 +153,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
                                if (last - first > 0) {
                                        source = xrealloc(source,
-                                                       (count + last - first)
+                                                       (argc + last - first)
                                                        * sizeof(char *));
                                        destination = xrealloc(destination,
-                                                       (count + last - first)
+                                                       (argc + last - first)
                                                        * sizeof(char *));
                                        modes = xrealloc(modes,
-                                                       (count + last - first)
+                                                       (argc + last - first)
                                                        * sizeof(enum update_mode));
                                }
 
@@ -183,13 +169,13 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                for (j = 0; j < last - first; j++) {
                                        const char *path =
                                                active_cache[first + j]->name;
-                                       source[count + j] = path;
-                                       destination[count + j] =
+                                       source[argc + j] = path;
+                                       destination[argc + j] =
                                                prefix_path(dst, dst_len,
                                                        path + length);
-                                       modes[count + j] = INDEX;
+                                       modes[argc + j] = INDEX;
                                }
-                               count += last - first;
+                               argc += last - first;
                        }
                } else if (lstat(dst, &st) == 0) {
                        bad = "destination exists";
@@ -216,12 +202,12 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
                if (bad) {
                        if (ignore_errors) {
-                               if (--count > 0) {
+                               if (--argc > 0) {
                                        memmove(source + i, source + i + 1,
-                                               (count - i) * sizeof(char *));
+                                               (argc - i) * sizeof(char *));
                                        memmove(destination + i,
                                                destination + i + 1,
-                                               (count - i) * sizeof(char *));
+                                               (argc - i) * sizeof(char *));
                                }
                        } else
                                die ("%s, source=%s, destination=%s",
@@ -229,7 +215,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                }
        }
 
-       for (i = 0; i < count; i++) {
+       for (i = 0; i < argc; i++) {
                const char *src = source[i], *dst = destination[i];
                enum update_mode mode = modes[i];
                if (show_only || verbose)
@@ -253,7 +239,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                        path_list_insert(dst, &added);
        }
 
-        if (show_only) {
+       if (show_only) {
                show_list("Changed  : ", &changed);
                show_list("Adding   : ", &added);
                show_list("Deleting : ", &deleted);
index 03083e94776234b280316978cd3f638262f41398..a0c89a827b666d8e01ba64597b732205ce1a643e 100644 (file)
@@ -3,12 +3,10 @@
 #include "commit.h"
 #include "tag.h"
 #include "refs.h"
+#include "parse-options.h"
 
 #define CUTOFF_DATE_SLOP 86400 /* one day */
 
-static const char name_rev_usage[] =
-       "git-name-rev [--tags | --refs=<pattern>] ( --all | --stdin | committish [committish...] )\n";
-
 typedef struct rev_name {
        const char *tip_name;
        int generation;
@@ -153,51 +151,41 @@ static const char* get_rev_name(struct object *o)
        }
 }
 
+static char const * const name_rev_usage[] = {
+       "git-name-rev [options] ( --all | --stdin | <commit>... )",
+       NULL
+};
+
 int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = { 0, 0, NULL };
-       int as_is = 0, all = 0, transform_stdin = 0;
+       int all = 0, transform_stdin = 0;
        struct name_ref_data data = { 0, 0, NULL };
+       struct option opts[] = {
+               OPT_BOOLEAN(0, "name-only", &data.name_only, "print only names (no SHA-1)"),
+               OPT_BOOLEAN(0, "tags", &data.tags_only, "only use tags to name the commits"),
+               OPT_STRING(0, "refs", &data.ref_filter, "pattern",
+                                  "only use refs matching <pattern>"),
+               OPT_GROUP(""),
+               OPT_BOOLEAN(0, "all", &all, "list all commits reachable from all refs"),
+               OPT_BOOLEAN(0, "stdin", &transform_stdin, "read from stdin"),
+               OPT_END(),
+       };
 
        git_config(git_default_config);
+       argc = parse_options(argc, argv, opts, name_rev_usage, 0);
+       if (!!all + !!transform_stdin + !!argc > 1) {
+               error("Specify either a list, or --all, not both!");
+               usage_with_options(name_rev_usage, opts);
+       }
+       if (all || transform_stdin)
+               cutoff = 0;
 
-       if (argc < 2)
-               usage(name_rev_usage);
-
-       for (--argc, ++argv; argc; --argc, ++argv) {
+       for (; argc; argc--, argv++) {
                unsigned char sha1[20];
                struct object *o;
                struct commit *commit;
 
-               if (!as_is && (*argv)[0] == '-') {
-                       if (!strcmp(*argv, "--")) {
-                               as_is = 1;
-                               continue;
-                       } else if (!strcmp(*argv, "--name-only")) {
-                               data.name_only = 1;
-                               continue;
-                       } else if (!strcmp(*argv, "--tags")) {
-                               data.tags_only = 1;
-                               continue;
-                       } else  if (!prefixcmp(*argv, "--refs=")) {
-                               data.ref_filter = *argv + 7;
-                               continue;
-                       } else if (!strcmp(*argv, "--all")) {
-                               if (argc > 1)
-                                       die("Specify either a list, or --all, not both!");
-                               all = 1;
-                               cutoff = 0;
-                               continue;
-                       } else if (!strcmp(*argv, "--stdin")) {
-                               if (argc > 1)
-                                       die("Specify either a list, or --stdin, not both!");
-                               transform_stdin = 1;
-                               cutoff = 0;
-                               continue;
-                       }
-                       usage(name_rev_usage);
-               }
-
                if (get_sha1(*argv, sha1)) {
                        fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
                                        *argv);
@@ -212,10 +200,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                }
 
                commit = (struct commit *)o;
-
                if (cutoff > commit->date)
                        cutoff = commit->date;
-
                add_object_array((struct object *)commit, *argv, &revs);
        }
 
index 0be539ed7fd9bf95bb40515b560c7615ed318f37..545ece5da75672f435d018ccb534bc8c342bbf3d 100644 (file)
@@ -57,7 +57,7 @@ struct object_entry {
  * nice "minimum seek" order.
  */
 static struct object_entry *objects;
-static struct object_entry **written_list;
+static struct pack_idx_entry **written_list;
 static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
@@ -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;
 
@@ -579,7 +577,7 @@ static off_t write_one(struct sha1file *f,
                e->idx.offset = 0;
                return 0;
        }
-       written_list[nr_written++] = e;
+       written_list[nr_written++] = &e->idx;
 
        /* make sure off_t is sufficiently large not to wrap */
        if (offset > offset + size)
@@ -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);
-       written_list = xmalloc(nr_objects * sizeof(struct object_entry *));
+               progress_state = start_progress("Writing objects", nr_result);
+       written_list = xmalloc(nr_objects * sizeof(*written_list));
 
        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,20 @@ 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);
+                       idx_tmp_name = write_idx_file(NULL, 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,19 +669,20 @@ 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));
                }
 
                /* mark written objects as written to previous pack */
                for (j = 0; j < nr_written; j++) {
-                       written_list[j]->idx.offset = (off_t)-1;
+                       written_list[j]->offset = (off_t)-1;
                }
                nr_remaining -= nr_written;
        } 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 +850,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 +1513,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 +1710,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");
        }
@@ -1773,6 +1767,12 @@ static int git_pack_config(const char *k, const char *v)
 #endif
                return 0;
        }
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%d", pack_idx_default_version);
+               return 0;
+       }
        return git_default_config(k, v);
 }
 
@@ -2135,23 +2135,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 09df4e11a8bb89cded9be4af06bb162b7a92bd4c..a62f06bb89aa6ef035c26c4aad05c0fdbf0512de 100644 (file)
@@ -3,9 +3,7 @@
 #include "refs.h"
 #include "object.h"
 #include "tag.h"
-
-static const char builtin_pack_refs_usage[] =
-"git-pack-refs [--all] [--prune | --no-prune]";
+#include "parse-options.h"
 
 struct ref_to_prune {
        struct ref_to_prune *next;
@@ -117,31 +115,26 @@ static int pack_refs(unsigned int flags)
        return 0;
 }
 
+static char const * const pack_refs_usage[] = {
+       "git-pack-refs [options]",
+       NULL
+};
+
 int cmd_pack_refs(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       unsigned int flags;
-
-       flags = PACK_REFS_PRUNE;
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!strcmp(arg, "--prune")) {
-                       flags |= PACK_REFS_PRUNE; /* now the default */
-                       continue;
-               }
-               if (!strcmp(arg, "--no-prune")) {
-                       flags &= ~PACK_REFS_PRUNE;
-                       continue;
-               }
-               if (!strcmp(arg, "--all")) {
-                       flags |= PACK_REFS_ALL;
-                       continue;
-               }
-               /* perhaps other parameters later... */
-               break;
-       }
-       if (i != argc)
-               usage(builtin_pack_refs_usage);
-
+       int all = 0, prune = 1;
+       unsigned int flags = 0;
+       struct option opts[] = {
+               OPT_BOOLEAN(0, "all", &all, "pack everything"),
+               OPT_BOOLEAN(0, "prune", &prune, "prune loose refs (default)"),
+               OPT_END(),
+       };
+
+       if (parse_options(argc, argv, opts, pack_refs_usage, 0))
+               usage_with_options(pack_refs_usage, opts);
+       if (prune)
+               flags |= PACK_REFS_PRUNE;
+       if (all)
+               flags |= PACK_REFS_ALL;
        return pack_refs(flags);
 }
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 e1dc31e0eb80ddc2d1b82f12eda31401b9f40ebc..5467e36c73766494ba23d03fd23a9cbf03d05b95 100644 (file)
@@ -113,10 +113,17 @@ static int update_index_refresh(void)
        return run_command_v_opt(argv_update_index, RUN_GIT_CMD);
 }
 
+struct update_cb_data {
+       int index_fd;
+       struct lock_file *lock;
+       int exit_code;
+};
+
 static void update_index_from_diff(struct diff_queue_struct *q,
                struct diff_options *opt, void *data)
 {
        int i;
+       struct update_cb_data *cb = data;
 
        /* do_diff_cache() mangled the index */
        discard_cache();
@@ -133,29 +140,34 @@ static void update_index_from_diff(struct diff_queue_struct *q,
                } else
                        remove_file_from_cache(one->path);
        }
+
+       cb->exit_code = write_cache(cb->index_fd, active_cache, active_nr) ||
+               close(cb->index_fd) ||
+               commit_locked_index(cb->lock);
 }
 
 static int read_from_tree(const char *prefix, const char **argv,
                unsigned char *tree_sha1)
 {
-        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int index_fd;
        struct diff_options opt;
+       struct update_cb_data cb;
 
        memset(&opt, 0, sizeof(opt));
        diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
        opt.output_format = DIFF_FORMAT_CALLBACK;
        opt.format_callback = update_index_from_diff;
+       opt.format_callback_data = &cb;
 
-       index_fd = hold_locked_index(lock, 1);
+       cb.lock = xcalloc(1, sizeof(struct lock_file));
+       cb.index_fd = hold_locked_index(cb.lock, 1);
+       cb.exit_code = 0;
        read_cache();
        if (do_diff_cache(tree_sha1, &opt))
                return 1;
        diffcore_std(&opt);
        diff_flush(&opt);
-       return write_cache(index_fd, active_cache, active_nr) ||
-               close(index_fd) ||
-               commit_locked_index(lock);
+
+       return cb.exit_code;
 }
 
 static void prepend_reflog_action(const char *action, char *buf, size_t size)
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 bfed69d7e2992c659c79bd0c35852cc8da30e968..62ab1fa1f484a113f45da96de8cd209a0c5c522e 100644 (file)
@@ -7,6 +7,7 @@
 #include "run-command.h"
 #include "exec_cmd.h"
 #include "utf8.h"
+#include "parse-options.h"
 
 /*
  * This implements the builtins revert and cherry-pick.
  * Copyright (c) 2005 Junio C Hamano
  */
 
-static const char *revert_usage = "git-revert [--edit | --no-edit] [-n] [-m parent-number] <commit-ish>";
+static const char * const revert_usage[] = {
+       "git-revert [options] <commit-ish>",
+       NULL
+};
 
-static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-m parent-number] [-r] [-x] <commit-ish>";
+static const char * const cherry_pick_usage[] = {
+       "git-cherry-pick [options] <commit-ish>",
+       NULL
+};
 
-static int edit;
-static int replay;
+static int edit, no_replay, no_commit, needed_deref, mainline;
 static enum { REVERT, CHERRY_PICK } action;
-static int no_commit;
 static struct commit *commit;
-static int needed_deref;
-static int mainline;
 
 static const char *me;
 
 #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
 
-static void parse_options(int argc, const char **argv)
+static void parse_args(int argc, const char **argv)
 {
-       const char *usage_str = action == REVERT ?
-               revert_usage : cherry_pick_usage;
+       const char * const * usage_str =
+               action == REVERT ?  revert_usage : cherry_pick_usage;
        unsigned char sha1[20];
        const char *arg;
-       int i;
+       int noop;
+       struct option options[] = {
+               OPT_BOOLEAN('n', "no-commit", &no_commit, "don't automatically commit"),
+               OPT_BOOLEAN('e', "edit", &edit, "edit the commit message"),
+               OPT_BOOLEAN('x', NULL, &no_replay, "append commit name when cherry-picking"),
+               OPT_BOOLEAN('r', NULL, &noop, "no-op (backward compatibility)"),
+               OPT_INTEGER('m', "mainline", &mainline, "parent number"),
+               OPT_END(),
+       };
+
+       if (parse_options(argc, argv, options, usage_str, 0) != 1)
+               usage_with_options(usage_str, options);
+       arg = argv[0];
 
-       if (argc < 2)
-               usage(usage_str);
-
-       for (i = 1; i < argc; i++) {
-               arg = argv[i];
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit"))
-                       no_commit = 1;
-               else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit"))
-                       edit = 1;
-               else if (!strcmp(arg, "--no-edit"))
-                       edit = 0;
-               else if (!strcmp(arg, "-x") || !strcmp(arg, "--i-really-want-"
-                               "to-expose-my-private-commit-object-name"))
-                       replay = 0;
-               else if (!strcmp(arg, "-m") || !strcmp(arg, "--mainline")) {
-                       if (++i >= argc ||
-                           strtol_i(argv[i], 10, &mainline) ||
-                           mainline <= 0)
-                               usage(usage_str);
-               }
-               else if (strcmp(arg, "-r"))
-                       usage(usage_str);
-       }
-       if (i != argc - 1)
-               usage(usage_str);
-       arg = argv[argc - 1];
        if (get_sha1(arg, sha1))
                die ("Cannot find '%s'", arg);
        commit = (struct commit *)parse_object(sha1);
@@ -250,10 +237,10 @@ static int revert_or_cherry_pick(int argc, const char **argv)
        git_config(git_default_config);
        me = action == REVERT ? "revert" : "cherry-pick";
        setenv(GIT_REFLOG_ACTION, me, 0);
-       parse_options(argc, argv);
+       parse_args(argc, argv);
 
        /* this is copied from the shell script, but it's never triggered... */
-       if (action == REVERT && replay)
+       if (action == REVERT && !no_replay)
                die("revert is incompatible with replay");
 
        if (no_commit) {
@@ -338,7 +325,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                next = commit;
                set_author_ident_env(message);
                add_message_to_msg(message);
-               if (!replay) {
+               if (no_replay) {
                        add_to_msg("(cherry picked from commit ");
                        add_to_msg(sha1_to_hex(commit->object.sha1));
                        add_to_msg(")\n");
@@ -377,7 +364,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 "
@@ -416,13 +403,14 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
 {
        if (isatty(0))
                edit = 1;
+       no_replay = 1;
        action = REVERT;
        return revert_or_cherry_pick(argc, argv);
 }
 
 int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
 {
-       replay = 1;
+       no_replay = 0;
        action = CHERRY_PICK;
        return revert_or_cherry_pick(argc, argv);
 }
index 3b0677e44b290f0a44fd81bd2e31ddc96bc1946d..bca2bd97036fdaf2e2bf99e308ba62e1acf4d9e8 100644 (file)
@@ -8,9 +8,12 @@
 #include "dir.h"
 #include "cache-tree.h"
 #include "tree-walk.h"
+#include "parse-options.h"
 
-static const char builtin_rm_usage[] =
-"git-rm [-f] [-n] [-r] [--cached] [--ignore-unmatch] [--quiet] [--] <file>...";
+static const char * const builtin_rm_usage[] = {
+       "git-rm [options] [--] <file>...",
+       NULL
+};
 
 static struct {
        int nr, alloc;
@@ -121,11 +124,23 @@ static int check_local_mod(unsigned char *head, int index_only)
 
 static struct lock_file lock_file;
 
+static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
+static int ignore_unmatch = 0;
+
+static struct option builtin_rm_options[] = {
+       OPT__DRY_RUN(&show_only),
+       OPT__QUIET(&quiet),
+       OPT_BOOLEAN( 0 , "cached",         &index_only, "only remove from the index"),
+       OPT_BOOLEAN('f', NULL,             &force,      "override the up-to-date check"),
+       OPT_BOOLEAN('r', NULL,             &recursive,  "allow recursive removal"),
+       OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
+                               "exit with a zero status even if nothing matched"),
+       OPT_END(),
+};
+
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0;
-       int ignore_unmatch = 0;
        const char **pathspec;
        char *seen;
 
@@ -136,34 +151,11 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        if (read_cache() < 0)
                die("index file corrupt");
 
-       for (i = 1 ; i < argc ; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
-               else if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               else if (!strcmp(arg, "-n"))
-                       show_only = 1;
-               else if (!strcmp(arg, "--cached"))
-                       index_only = 1;
-               else if (!strcmp(arg, "-f"))
-                       force = 1;
-               else if (!strcmp(arg, "-r"))
-                       recursive = 1;
-               else if (!strcmp(arg, "--quiet"))
-                       quiet = 1;
-               else if (!strcmp(arg, "--ignore-unmatch"))
-                       ignore_unmatch = 1;
-               else
-                       usage(builtin_rm_usage);
-       }
-       if (argc <= i)
-               usage(builtin_rm_usage);
+       argc = parse_options(argc, argv, builtin_rm_options, builtin_rm_usage, 0);
+       if (!argc)
+               usage_with_options(builtin_rm_usage, builtin_rm_options);
 
-       pathspec = get_pathspec(prefix, argv + i);
+       pathspec = get_pathspec(prefix, argv);
        seen = NULL;
        for (i = 0; pathspec[i] ; i++)
                /* nothing */;
index 9eb95e50da5656bc66e9db4d8cd23ef327a5ca8e..d33982b967e7665ae79fb186435d9ed9aabb907b 100644 (file)
@@ -1,9 +1,12 @@
 #include "builtin.h"
 #include "cache.h"
 #include "refs.h"
+#include "parse-options.h"
 
-static const char git_symbolic_ref_usage[] =
-"git-symbolic-ref [-q] [-m <reason>] name [ref]";
+static const char * const git_symbolic_ref_usage[] = {
+       "git-symbolic-ref [options] name [ref]",
+       NULL
+};
 
 static void check_symref(const char *HEAD, int quiet)
 {
@@ -26,44 +29,25 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 {
        int quiet = 0;
        const char *msg = NULL;
+       struct option options[] = {
+               OPT__QUIET(&quiet),
+               OPT_STRING('m', NULL, &msg, "reason", "reason of the update"),
+               OPT_END(),
+       };
 
        git_config(git_default_config);
-
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (arg[0] != '-')
-                       break;
-               else if (!strcmp("-q", arg))
-                       quiet = 1;
-               else if (!strcmp("-m", arg)) {
-                       argc--;
-                       argv++;
-                       if (argc <= 1)
-                               break;
-                       msg = argv[1];
-                       if (!*msg)
-                               die("Refusing to perform update with empty message");
-               }
-               else if (!strcmp("--", arg)) {
-                       argc--;
-                       argv++;
-                       break;
-               }
-               else
-                       die("unknown option %s", arg);
-               argc--;
-               argv++;
-       }
-
+       argc = parse_options(argc, argv, options, git_symbolic_ref_usage, 0);
+       if (msg &&!*msg)
+               die("Refusing to perform update with empty message");
        switch (argc) {
-       case 2:
-               check_symref(argv[1], quiet);
+       case 1:
+               check_symref(argv[0], quiet);
                break;
-       case 3:
-               create_symref(argv[1], argv[2], msg);
+       case 2:
+               create_symref(argv[0], argv[1], msg);
                break;
        default:
-               usage(git_symbolic_ref_usage);
+               usage_with_options(git_symbolic_ref_usage, options);
        }
        return 0;
 }
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 fe1f74c9f3fa4a44cfd56508171e65a40e27342c..e90737c350402fec8937a9e343485e1c71411f55 100644 (file)
@@ -1,59 +1,44 @@
 #include "cache.h"
 #include "refs.h"
 #include "builtin.h"
+#include "parse-options.h"
 
-static const char git_update_ref_usage[] =
-"git-update-ref [-m <reason>] (-d <refname> <value> | [--no-deref] <refname> <value> [<oldval>])";
+static const char * const git_update_ref_usage[] = {
+       "git-update-ref [options] -d <refname> <oldval>",
+       "git-update-ref [options]    <refname> <newval> [<oldval>]",
+       NULL
+};
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
-       const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
+       const char *refname, *value, *oldval, *msg=NULL;
        unsigned char sha1[20], oldsha1[20];
-       int i, delete, ref_flags;
+       int delete = 0, no_deref = 0;
+       struct option options[] = {
+               OPT_STRING( 'm', NULL, &msg, "reason", "reason of the update"),
+               OPT_BOOLEAN('d', NULL, &delete, "deletes the reference"),
+               OPT_BOOLEAN( 0 , "no-deref", &no_deref,
+                                       "update <refname> not the one it points to"),
+               OPT_END(),
+       };
 
-       delete = 0;
-       ref_flags = 0;
        git_config(git_default_config);
+       argc = parse_options(argc, argv, options, git_update_ref_usage, 0);
+       if (msg && !*msg)
+               die("Refusing to perform update with empty message.");
 
-       for (i = 1; i < argc; i++) {
-               if (!strcmp("-m", argv[i])) {
-                       if (i+1 >= argc)
-                               usage(git_update_ref_usage);
-                       msg = argv[++i];
-                       if (!*msg)
-                               die("Refusing to perform update with empty message.");
-                       continue;
-               }
-               if (!strcmp("-d", argv[i])) {
-                       delete = 1;
-                       continue;
-               }
-               if (!strcmp("--no-deref", argv[i])) {
-                       ref_flags |= REF_NODEREF;
-                       continue;
-               }
-               if (!refname) {
-                       refname = argv[i];
-                       continue;
-               }
-               if (!value) {
-                       value = argv[i];
-                       continue;
-               }
-               if (!oldval) {
-                       oldval = argv[i];
-                       continue;
-               }
-       }
-       if (!refname || !value)
-               usage(git_update_ref_usage);
+       if (argc < 2 || argc > 3)
+               usage_with_options(git_update_ref_usage, options);
+       refname = argv[0];
+       value   = argv[1];
+       oldval  = argv[2];
 
        if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
 
        if (delete) {
                if (oldval)
-                       usage(git_update_ref_usage);
+                       usage_with_options(git_update_ref_usage, options);
                return delete_ref(refname, sha1);
        }
 
@@ -62,5 +47,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                die("%s: not a valid old SHA1", oldval);
 
        return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                         ref_flags, DIE_ON_ERR);
+                         no_deref ? REF_NODEREF : 0, DIE_ON_ERR);
 }
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;;
index 4286d160a02bdbeff272cc306ab01b85b87b8cbe..e147da0596a880e864fb707192b5bc856cb3daba 100644 (file)
@@ -796,6 +796,7 @@ Return the list of files that haven't been handled."
                             (with-current-buffer buffer (erase-buffer))
                             (dolist (info files) (git-set-fileinfo-state info 'uptodate))
                             (git-call-process-env nil nil "rerere")
+                            (git-call-process-env nil nil "gc" "--auto")
                             (git-refresh-files)
                             (git-refresh-ewoc-hf git-status)
                             (message "Committed %s." commit)
@@ -842,7 +843,8 @@ Return the list of files that haven't been handled."
   "Mark all files."
   (interactive)
   (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) t) t) git-status)
+  (ewoc-map (lambda (info) (unless (git-fileinfo->marked info)
+                             (setf (git-fileinfo->marked info) t))) git-status)
   ; move back to goal column after invalidate
   (when goal-column (move-to-column goal-column)))
 
@@ -850,7 +852,9 @@ Return the list of files that haven't been handled."
   "Unmark all files."
   (interactive)
   (unless git-status (error "Not in git-status buffer."))
-  (ewoc-map (lambda (info) (setf (git-fileinfo->marked info) nil) t) git-status)
+  (ewoc-map (lambda (info) (when (git-fileinfo->marked info)
+                             (setf (git-fileinfo->marked info) nil)
+                             t)) git-status)
   ; move back to goal column after invalidate
   (when goal-column (move-to-column goal-column)))
 
@@ -955,7 +959,7 @@ Return the list of files that haven't been handled."
       (when modified
         (apply #'git-call-process-env nil nil "checkout" "HEAD" modified))
       (git-update-status-files (append added modified) 'uptodate)
-      (git-success-message "Reverted" files))))
+      (git-success-message "Reverted" (git-get-filenames files)))))
 
 (defun git-resolve-file ()
   "Resolve conflicts in marked file(s)."
@@ -1353,7 +1357,7 @@ Commands:
   "Update the corresponding git-status buffer when a file is saved.
 Meant to be used in `after-save-hook'."
   (let* ((file (expand-file-name buffer-file-name))
-         (dir (condition-case nil (git-get-top-dir (file-name-directory file))))
+         (dir (condition-case nil (git-get-top-dir (file-name-directory file)) (error nil)))
          (buffer (and dir (git-find-status-buffer dir))))
     (when buffer
       (with-current-buffer buffer
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;
diff --git a/dir.c b/dir.c
index 4c17d3643eebf0d9071003065b5c120db130f9ab..5bcc764c97dca5e584d0a4227bfb299efb5699ce 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -118,14 +118,32 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
        return retval;
 }
 
+static int no_wildcard(const char *string)
+{
+       return string[strcspn(string, "*?[{")] == '\0';
+}
+
 void add_exclude(const char *string, const char *base,
                 int baselen, struct exclude_list *which)
 {
        struct exclude *x = xmalloc(sizeof (*x));
 
+       x->to_exclude = 1;
+       if (*string == '!') {
+               x->to_exclude = 0;
+               string++;
+       }
        x->pattern = string;
+       x->patternlen = strlen(string);
        x->base = base;
        x->baselen = baselen;
+       x->flags = 0;
+       if (!strchr(string, '/'))
+               x->flags |= EXC_FLAG_NODIR;
+       if (no_wildcard(string))
+               x->flags |= EXC_FLAG_NOWILDCARD;
+       if (*string == '*' && no_wildcard(string+1))
+               x->flags |= EXC_FLAG_ENDSWITH;
        if (which->nr == which->alloc) {
                which->alloc = alloc_nr(which->alloc);
                which->excludes = xrealloc(which->excludes,
@@ -209,7 +227,7 @@ void pop_exclude_per_directory(struct dir_struct *dir, int stk)
  * Return 1 for exclude, 0 for include and -1 for undecided.
  */
 static int excluded_1(const char *pathname,
-                     int pathlen,
+                     int pathlen, const char *basename,
                      struct exclude_list *el)
 {
        int i;
@@ -218,19 +236,21 @@ static int excluded_1(const char *pathname,
                for (i = el->nr - 1; 0 <= i; i--) {
                        struct exclude *x = el->excludes[i];
                        const char *exclude = x->pattern;
-                       int to_exclude = 1;
+                       int to_exclude = x->to_exclude;
 
-                       if (*exclude == '!') {
-                               to_exclude = 0;
-                               exclude++;
-                       }
-
-                       if (!strchr(exclude, '/')) {
+                       if (x->flags & EXC_FLAG_NODIR) {
                                /* match basename */
-                               const char *basename = strrchr(pathname, '/');
-                               basename = (basename) ? basename+1 : pathname;
-                               if (fnmatch(exclude, basename, 0) == 0)
-                                       return to_exclude;
+                               if (x->flags & EXC_FLAG_NOWILDCARD) {
+                                       if (!strcmp(exclude, basename))
+                                               return to_exclude;
+                               } else if (x->flags & EXC_FLAG_ENDSWITH) {
+                                       if (x->patternlen - 1 <= pathlen &&
+                                           !strcmp(exclude + 1, pathname + pathlen - x->patternlen + 1))
+                                               return to_exclude;
+                               } else {
+                                       if (fnmatch(exclude, basename, 0) == 0)
+                                               return to_exclude;
+                               }
                        }
                        else {
                                /* match with FNM_PATHNAME:
@@ -246,9 +266,14 @@ static int excluded_1(const char *pathname,
                                    strncmp(pathname, x->base, baselen))
                                    continue;
 
-                               if (fnmatch(exclude, pathname+baselen,
-                                           FNM_PATHNAME) == 0)
-                                       return to_exclude;
+                               if (x->flags & EXC_FLAG_NOWILDCARD) {
+                                       if (!strcmp(exclude, pathname + baselen))
+                                               return to_exclude;
+                               } else {
+                                       if (fnmatch(exclude, pathname+baselen,
+                                                   FNM_PATHNAME) == 0)
+                                           return to_exclude;
+                               }
                        }
                }
        }
@@ -259,9 +284,11 @@ int excluded(struct dir_struct *dir, const char *pathname)
 {
        int pathlen = strlen(pathname);
        int st;
+       const char *basename = strrchr(pathname, '/');
+       basename = (basename) ? basename+1 : pathname;
 
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
-               switch (excluded_1(pathname, pathlen, &dir->exclude_list[st])) {
+               switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
                case 0:
                        return 0;
                case 1:
diff --git a/dir.h b/dir.h
index a248a23ac4eab2aff1e91060f0e9281a8842bf6b..aaa247b2569fc5ae923f12dcd3cdede5c2dccd8d 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -17,13 +17,20 @@ struct dir_entry {
        char name[FLEX_ARRAY]; /* more */
 };
 
+#define EXC_FLAG_NODIR 1
+#define EXC_FLAG_NOWILDCARD 2
+#define EXC_FLAG_ENDSWITH 4
+
 struct exclude_list {
        int nr;
        int alloc;
        struct exclude {
                const char *pattern;
+               int patternlen;
                const char *base;
                int baselen;
+               int to_exclude;
+               int flags;
        } **excludes;
 };
 
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 5e582fe247892fa7dffc44556c939863c36edc35..3f006936085cedcdca436455643b05d2198e3606 100755 (executable)
@@ -14,7 +14,7 @@ die() {
 }
 
 usage() {
-       die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]"
+       die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] [--] <repo> [<dir>]"
 }
 
 get_repo_base() {
@@ -28,7 +28,8 @@ get_repo_base() {
        ) 2>/dev/null
 }
 
-if [ -n "$GIT_SSL_NO_VERIFY" ]; then
+if [ -n "$GIT_SSL_NO_VERIFY" -o \
+       "`git config --bool http.sslVerify`" = false ]; then
     curl_extra_args="-k"
 fi
 
@@ -159,6 +160,9 @@ while
        *,--depth)
                shift
                depth="--depth=$1";;
+       *,--)
+               shift
+               break ;;
        *,-*) usage ;;
        *) break ;;
        esac
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 2954fb846e3372a648124d92b7c1a1ccbd44306f..e4bc2b54f68930df217751975e32b4224f8d765c 100755 (executable)
@@ -818,6 +818,7 @@ sub commit {
                $state = 4;
        } elsif ($state == 4 and s/^Branch:\s+//) {
                s/\s+$//;
+               tr/_/\./ if ( $opt_u );
                s/[\/]/$opt_s/g;
                $branch = $_;
                $state = 5;
index d56cf92ebfa685fd724a47e1ca753f0735dbd5bd..fec70bbf88c614a2dadfc40950fdd7abdf7f2c63 100755 (executable)
@@ -54,9 +54,10 @@ tmpdir=$tmp-d
 
 case "$peek_repo" in
 http://* | https://* | ftp://* )
-        if [ -n "$GIT_SSL_NO_VERIFY" ]; then
-            curl_extra_args="-k"
-        fi
+       if [ -n "$GIT_SSL_NO_VERIFY" -o \
+               "`git config --bool http.sslVerify`" = false ]; then
+               curl_extra_args="-k"
+       fi
        if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
                "`git config --bool http.noEPSV`" = true ]; then
                curl_extra_args="${curl_extra_args} --disable-epsv"
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 74bfc16744d69d8394f89b6f77d81697a8336032..75ec011969f7cd84979911b917b886696a077c5e 100755 (executable)
@@ -4,7 +4,7 @@
 #
 # Fetch one or more remote refs and merge it/them into the current HEAD.
 
-USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...'
+USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'
 LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
@@ -15,7 +15,7 @@ cd_to_toplevel
 test -z "$(git ls-files -u)" ||
        die "You are in the middle of a conflicted merge."
 
-strategy_args= no_summary= no_commit= squash=
+strategy_args= no_summary= no_commit= squash= no_ff=
 while :
 do
        case "$1" in
@@ -27,8 +27,16 @@ do
                ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
                no_commit=--no-commit ;;
+       --c|--co|--com|--comm|--commi|--commit)
+               no_commit=--commit ;;
        --sq|--squ|--squa|--squas|--squash)
                squash=--squash ;;
+       --no-sq|--no-squ|--no-squa|--no-squas|--no-squash)
+               squash=--no-squash ;;
+       --ff)
+               no_ff=--ff ;;
+       --no-ff)
+               no_ff=--no-ff ;;
        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\
                --strateg=*|--strategy=*|\
        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)
@@ -133,5 +141,5 @@ then
 fi
 
 merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-exec git-merge $no_summary $no_commit $squash $strategy_args \
+exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
        "$merge_name" HEAD $merge_head
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
index 22bb47b34d030ec4d1b3dc7cda05465559e0a09d..4900f57f18e113229a9248df7daa7e13f39becaf 100755 (executable)
@@ -252,7 +252,7 @@ sub usage {
                next if $cmd && $cmd ne $_;
                next if /^multi-/; # don't show deprecated commands
                print $fd '  ',pack('A17',$_),$cmd{$_}->[1],"\n";
-               foreach (keys %{$cmd{$_}->[2]}) {
+               foreach (sort keys %{$cmd{$_}->[2]}) {
                        # mixed-case options are for .git/config only
                        next if /[A-Z]/ && /^[a-z]+$/i;
                        # prints out arguments as they should be passed:
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 */
diff --git a/gitk b/gitk
index 41a1c69e19c52cc07afaf4c14fc6ef8a6178e541..1da0b0af1d1da6c8596f366d7a36519b4e249c3b 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -82,11 +82,14 @@ proc dorunq {} {
 proc start_rev_list {view} {
     global startmsecs
     global commfd leftover tclencoding datemode
-    global viewargs viewfiles commitidx
-    global lookingforhead showlocalchanges
+    global viewargs viewfiles commitidx viewcomplete vnextroot
+    global showlocalchanges commitinterest mainheadid
+    global progressdirn progresscoords proglastnc curview
 
     set startmsecs [clock clicks -milliseconds]
     set commitidx($view) 0
+    set viewcomplete($view) 0
+    set vnextroot($view) 0
     set order "--topo-order"
     if {$datemode} {
        set order "--date-order"
@@ -100,13 +103,20 @@ proc start_rev_list {view} {
     }
     set commfd($view) $fd
     set leftover($view) {}
-    set lookingforhead $showlocalchanges
+    if {$showlocalchanges} {
+       lappend commitinterest($mainheadid) {dodiffindex}
+    }
     fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
     filerun $fd [list getcommitlines $fd $view]
-    nowbusy $view
+    nowbusy $view "Reading"
+    if {$view == $curview} {
+       set progressdirn 1
+       set progresscoords {0 0}
+       set proglastnc 0
+    }
 }
 
 proc stop_rev_list {} {
@@ -123,7 +133,7 @@ proc stop_rev_list {} {
 }
 
 proc getcommits {} {
-    global phase canv mainfont curview
+    global phase canv curview
 
     set phase getcommits
     initlayout
@@ -131,12 +141,26 @@ proc getcommits {} {
     show_status "Reading commits..."
 }
 
+# This makes a string representation of a positive integer which
+# sorts as a string in numerical order
+proc strrep {n} {
+    if {$n < 16} {
+       return [format "%x" $n]
+    } elseif {$n < 256} {
+       return [format "x%.2x" $n]
+    } elseif {$n < 65536} {
+       return [format "y%.4x" $n]
+    }
+    return [format "z%.8x" $n]
+}
+
 proc getcommitlines {fd view}  {
-    global commitlisted
+    global commitlisted commitinterest
     global leftover commfd
-    global displayorder commitidx commitrow commitdata
+    global displayorder commitidx viewcomplete commitrow commitdata
     global parentlist children curview hlview
     global vparentlist vdisporder vcmitlisted
+    global ordertok vnextroot idpending
 
     set stuff [read $fd 500000]
     # git log doesn't terminate the last commit with a null...
@@ -147,9 +171,29 @@ proc getcommitlines {fd view}  {
        if {![eof $fd]} {
            return 1
        }
-       global viewname
+       # Check if we have seen any ids listed as parents that haven't
+       # appeared in the list
+       foreach vid [array names idpending "$view,*"] {
+           # should only get here if git log is buggy
+           set id [lindex [split $vid ","] 1]
+           set commitrow($vid) $commitidx($view)
+           incr commitidx($view)
+           if {$view == $curview} {
+               lappend parentlist {}
+               lappend displayorder $id
+               lappend commitlisted 0
+           } else {
+               lappend vparentlist($view) {}
+               lappend vdisporder($view) $id
+               lappend vcmitlisted($view) 0
+           }
+       }
+       set viewcomplete($view) 1
+       global viewname progresscoords
        unset commfd($view)
        notbusy $view
+       set progresscoords {0 0}
+       adjustprogress
        # set it blocking so we wait for the process to terminate
        fconfigure $fd -blocking 1
        if {[catch {close $fd} err]} {
@@ -221,14 +265,35 @@ proc getcommitlines {fd view}  {
            exit 1
        }
        set id [lindex $ids 0]
+       if {![info exists ordertok($view,$id)]} {
+           set otok "o[strrep $vnextroot($view)]"
+           incr vnextroot($view)
+           set ordertok($view,$id) $otok
+       } else {
+           set otok $ordertok($view,$id)
+           unset idpending($view,$id)
+       }
        if {$listed} {
            set olds [lrange $ids 1 end]
-           set i 0
-           foreach p $olds {
-               if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
-                   lappend children($view,$p) $id
+           if {[llength $olds] == 1} {
+               set p [lindex $olds 0]
+               lappend children($view,$p) $id
+               if {![info exists ordertok($view,$p)]} {
+                   set ordertok($view,$p) $ordertok($view,$id)
+                   set idpending($view,$p) 1
+               }
+           } else {
+               set i 0
+               foreach p $olds {
+                   if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
+                       lappend children($view,$p) $id
+                   }
+                   if {![info exists ordertok($view,$p)]} {
+                       set ordertok($view,$p) "$otok[strrep $i]]"
+                       set idpending($view,$p) 1
+                   }
+                   incr i
                }
-               incr i
            }
        } else {
            set olds {}
@@ -248,24 +313,54 @@ proc getcommitlines {fd view}  {
            lappend vdisporder($view) $id
            lappend vcmitlisted($view) $listed
        }
+       if {[info exists commitinterest($id)]} {
+           foreach script $commitinterest($id) {
+               eval [string map [list "%I" $id] $script]
+           }
+           unset commitinterest($id)
+       }
        set gotsome 1
     }
     if {$gotsome} {
        run chewcommits $view
+       if {$view == $curview} {
+           # update progress bar
+           global progressdirn progresscoords proglastnc
+           set inc [expr {($commitidx($view) - $proglastnc) * 0.0002}]
+           set proglastnc $commitidx($view)
+           set l [lindex $progresscoords 0]
+           set r [lindex $progresscoords 1]
+           if {$progressdirn} {
+               set r [expr {$r + $inc}]
+               if {$r >= 1.0} {
+                   set r 1.0
+                   set progressdirn 0
+               }
+               if {$r > 0.2} {
+                   set l [expr {$r - 0.2}]
+               }
+           } else {
+               set l [expr {$l - $inc}]
+               if {$l <= 0.0} {
+                   set l 0.0
+                   set progressdirn 1
+               }
+               set r [expr {$l + 0.2}]
+           }
+           set progresscoords [list $l $r]
+           adjustprogress
+       }
     }
     return 2
 }
 
 proc chewcommits {view} {
-    global curview hlview commfd
+    global curview hlview viewcomplete
     global selectedline pending_select
 
-    set more 0
     if {$view == $curview} {
-       set allread [expr {![info exists commfd($view)]}]
-       set tlimit [expr {[clock clicks -milliseconds] + 50}]
-       set more [layoutmore $tlimit $allread]
-       if {$allread && !$more} {
+       layoutmore
+       if {$viewcomplete($view)} {
            global displayorder commitidx phase
            global numcommits startmsecs
 
@@ -286,7 +381,7 @@ proc chewcommits {view} {
     if {[info exists hlview] && $view == $hlview} {
        vhighlightmore
     }
-    return $more
+    return 0
 }
 
 proc readcommit {id} {
@@ -295,7 +390,7 @@ proc readcommit {id} {
 }
 
 proc updatecommits {} {
-    global viewdata curview phase displayorder
+    global viewdata curview phase displayorder ordertok idpending
     global children commitrow selectedline thickerline showneartags
 
     if {$phase ne {}} {
@@ -306,6 +401,10 @@ proc updatecommits {} {
     foreach id $displayorder {
        catch {unset children($n,$id)}
        catch {unset commitrow($n,$id)}
+       catch {unset ordertok($n,$id)}
+    }
+    foreach vid [array names idpending "$n,*"] {
+       unset idpending($vid)
     }
     set curview -1
     catch {unset selectedline}
@@ -516,7 +615,7 @@ proc confirm_popup msg {
 
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist
-    global textfont mainfont uifont tabstop
+    global tabstop
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global diffcontextstring diffcontext
@@ -525,23 +624,26 @@ proc makewindow {} {
     global highlight_files gdttype
     global searchstring sstring
     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
-    global headctxmenu
+    global headctxmenu progresscanv progressitem progresscoords statusw
+    global fprogitem fprogcoord lastprogupdate progupdatepending
+    global rprogitem rprogcoord
+    global have_tk85
 
     menu .bar
     .bar add cascade -label "File" -menu .bar.file
-    .bar configure -font $uifont
+    .bar configure -font uifont
     menu .bar.file
     .bar.file add command -label "Update" -command updatecommits
     .bar.file add command -label "Reread references" -command rereadrefs
     .bar.file add command -label "List references" -command showrefs
     .bar.file add command -label "Quit" -command doquit
-    .bar.file configure -font $uifont
+    .bar.file configure -font uifont
     menu .bar.edit
     .bar add cascade -label "Edit" -menu .bar.edit
     .bar.edit add command -label "Preferences" -command doprefs
-    .bar.edit configure -font $uifont
+    .bar.edit configure -font uifont
 
-    menu .bar.view -font $uifont
+    menu .bar.view -font uifont
     .bar add cascade -label "View" -menu .bar.view
     .bar.view add command -label "New view..." -command {newview 0}
     .bar.view add command -label "Edit view..." -command editview \
@@ -555,7 +657,7 @@ proc makewindow {} {
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
     .bar.help add command -label "Key bindings" -command keys
-    .bar.help configure -font $uifont
+    .bar.help configure -font uifont
     . configure -menu .bar
 
     # the gui has upper and lower half, parts of a paned window.
@@ -612,10 +714,10 @@ proc makewindow {} {
     set entries $sha1entry
     set sha1but .tf.bar.sha1label
     button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
-       -command gotocommit -width 8 -font $uifont
+       -command gotocommit -width 8 -font uifont
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
     pack .tf.bar.sha1label -side left
-    entry $sha1entry -width 40 -font $textfont -textvariable sha1string
+    entry $sha1entry -width 40 -font textfont -textvariable sha1string
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
 
@@ -642,62 +744,61 @@ proc makewindow {} {
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
-    button .tf.bar.findbut -text "Find" -command dofind -font $uifont
-    pack .tf.bar.findbut -side left
+    # Status label and progress bar
+    set statusw .tf.bar.status
+    label $statusw -width 15 -relief sunken -font uifont
+    pack $statusw -side left -padx 5
+    set h [expr {[font metrics uifont -linespace] + 2}]
+    set progresscanv .tf.bar.progress
+    canvas $progresscanv -relief sunken -height $h -borderwidth 2
+    set progressitem [$progresscanv create rect -1 0 0 $h -fill green]
+    set fprogitem [$progresscanv create rect -1 0 0 $h -fill yellow]
+    set rprogitem [$progresscanv create rect -1 0 0 $h -fill red]
+    pack $progresscanv -side right -expand 1 -fill x
+    set progresscoords {0 0}
+    set fprogcoord 0
+    set rprogcoord 0
+    bind $progresscanv <Configure> adjustprogress
+    set lastprogupdate [clock clicks -milliseconds]
+    set progupdatepending 0
+
+    # build up the bottom bar of upper window
+    label .tf.lbar.flabel -text "Find " -font uifont
+    button .tf.lbar.fnext -text "next" -command {dofind 1 1} -font uifont
+    button .tf.lbar.fprev -text "prev" -command {dofind -1 1} -font uifont
+    label .tf.lbar.flab2 -text " commit " -font uifont
+    pack .tf.lbar.flabel .tf.lbar.fnext .tf.lbar.fprev .tf.lbar.flab2 \
+       -side left -fill y
+    set gdttype "containing:"
+    set gm [tk_optionMenu .tf.lbar.gdttype gdttype \
+               "containing:" \
+               "touching paths:" \
+               "adding/removing string:"]
+    trace add variable gdttype write gdttype_change
+    $gm conf -font uifont
+    .tf.lbar.gdttype conf -font uifont
+    pack .tf.lbar.gdttype -side left -fill y
+
     set findstring {}
-    set fstring .tf.bar.findstring
+    set fstring .tf.lbar.findstring
     lappend entries $fstring
-    entry $fstring -width 30 -font $textfont -textvariable findstring
+    entry $fstring -width 30 -font textfont -textvariable findstring
     trace add variable findstring write find_change
-    pack $fstring -side left -expand 1 -fill x -in .tf.bar
     set findtype Exact
-    set findtypemenu [tk_optionMenu .tf.bar.findtype \
+    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
                      findtype Exact IgnCase Regexp]
-    trace add variable findtype write find_change
-    .tf.bar.findtype configure -font $uifont
-    .tf.bar.findtype.menu configure -font $uifont
+    trace add variable findtype write findcom_change
+    .tf.lbar.findtype configure -font uifont
+    .tf.lbar.findtype.menu configure -font uifont
     set findloc "All fields"
-    tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
+    tk_optionMenu .tf.lbar.findloc findloc "All fields" Headline \
        Comments Author Committer
     trace add variable findloc write find_change
-    .tf.bar.findloc configure -font $uifont
-    .tf.bar.findloc.menu configure -font $uifont
-    pack .tf.bar.findloc -side right
-    pack .tf.bar.findtype -side right
-
-    # build up the bottom bar of upper window
-    label .tf.lbar.flabel -text "Highlight:  Commits " \
-    -font $uifont
-    pack .tf.lbar.flabel -side left -fill y
-    set gdttype "touching paths:"
-    set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
-       "adding/removing string:"]
-    trace add variable gdttype write hfiles_change
-    $gm conf -font $uifont
-    .tf.lbar.gdttype conf -font $uifont
-    pack .tf.lbar.gdttype -side left -fill y
-    entry .tf.lbar.fent -width 25 -font $textfont \
-       -textvariable highlight_files
-    trace add variable highlight_files write hfiles_change
-    lappend entries .tf.lbar.fent
-    pack .tf.lbar.fent -side left -fill x -expand 1
-    label .tf.lbar.vlabel -text " OR in view" -font $uifont
-    pack .tf.lbar.vlabel -side left -fill y
-    global viewhlmenu selectedhlview
-    set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
-    $viewhlmenu entryconf None -command delvhighlight
-    $viewhlmenu conf -font $uifont
-    .tf.lbar.vhl conf -font $uifont
-    pack .tf.lbar.vhl -side left -fill y
-    label .tf.lbar.rlabel -text " OR " -font $uifont
-    pack .tf.lbar.rlabel -side left -fill y
-    global highlight_related
-    set m [tk_optionMenu .tf.lbar.relm highlight_related None \
-       "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
-    $m conf -font $uifont
-    .tf.lbar.relm conf -font $uifont
-    trace add variable highlight_related write vrel_change
-    pack .tf.lbar.relm -side left -fill y
+    .tf.lbar.findloc configure -font uifont
+    .tf.lbar.findloc.menu configure -font uifont
+    pack .tf.lbar.findloc -side right
+    pack .tf.lbar.findtype -side right
+    pack $fstring -side left -expand 1 -fill x
 
     # Finish putting the upper half of the viewer together
     pack .tf.lbar -in .tf -side bottom -fill x
@@ -722,23 +823,23 @@ proc makewindow {} {
     frame .bleft.mid
 
     button .bleft.top.search -text "Search" -command dosearch \
-       -font $uifont
+       -font uifont
     pack .bleft.top.search -side left -padx 5
     set sstring .bleft.top.sstring
-    entry $sstring -width 20 -font $textfont -textvariable searchstring
+    entry $sstring -width 20 -font textfont -textvariable searchstring
     lappend entries $sstring
     trace add variable searchstring write incrsearch
     pack $sstring -side left -expand 1 -fill x
-    radiobutton .bleft.mid.diff -text "Diff" \
+    radiobutton .bleft.mid.diff -text "Diff" -font uifont \
        -command changediffdisp -variable diffelide -value {0 0}
-    radiobutton .bleft.mid.old -text "Old version" \
+    radiobutton .bleft.mid.old -text "Old version" -font uifont \
        -command changediffdisp -variable diffelide -value {0 1}
-    radiobutton .bleft.mid.new -text "New version" \
+    radiobutton .bleft.mid.new -text "New version" -font uifont \
        -command changediffdisp -variable diffelide -value {1 0}
     label .bleft.mid.labeldiffcontext -text "      Lines of context: " \
-       -font $uifont
+       -font uifont
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
-    spinbox .bleft.mid.diffcontext -width 5 -font $textfont \
+    spinbox .bleft.mid.diffcontext -width 5 -font textfont \
        -from 1 -increment 1 -to 10000000 \
        -validate all -validatecommand "diffcontextvalidate %P" \
        -textvariable diffcontextstring
@@ -748,9 +849,11 @@ proc makewindow {} {
     pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
     set ctext .bleft.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
-       -tabs "[expr {$tabstop * $charspc}]" \
-       -state disabled -font $textfont \
+       -state disabled -font textfont \
        -yscrollcommand scrolltext -wrap none
+    if {$have_tk85} {
+       $ctext conf -tabstyle wordprocessor
+    }
     scrollbar .bleft.sb -command "$ctext yview"
     pack .bleft.top -side top -fill x
     pack .bleft.mid -side top -fill x
@@ -760,7 +863,7 @@ proc makewindow {} {
     lappend fglist $ctext
 
     $ctext tag conf comment -wrap $wrapcomment
-    $ctext tag conf filesep -font [concat $textfont bold] -back "#aaaaaa"
+    $ctext tag conf filesep -font textfontbold -back "#aaaaaa"
     $ctext tag conf hunksep -fore [lindex $diffcolors 2]
     $ctext tag conf d0 -fore [lindex $diffcolors 0]
     $ctext tag conf d1 -fore [lindex $diffcolors 1]
@@ -782,8 +885,8 @@ proc makewindow {} {
     $ctext tag conf m15 -fore "#ff70b0"
     $ctext tag conf mmax -fore darkgrey
     set mergemax 16
-    $ctext tag conf mresult -font [concat $textfont bold]
-    $ctext tag conf msep -font [concat $textfont bold]
+    $ctext tag conf mresult -font textfontbold
+    $ctext tag conf msep -font textfontbold
     $ctext tag conf found -back yellow
 
     .pwbottom add .bleft
@@ -794,18 +897,18 @@ proc makewindow {} {
     frame .bright.mode
     radiobutton .bright.mode.patch -text "Patch" \
        -command reselectline -variable cmitmode -value "patch"
-    .bright.mode.patch configure -font $uifont
+    .bright.mode.patch configure -font uifont
     radiobutton .bright.mode.tree -text "Tree" \
        -command reselectline -variable cmitmode -value "tree"
-    .bright.mode.tree configure -font $uifont
+    .bright.mode.tree configure -font uifont
     grid .bright.mode.patch .bright.mode.tree -sticky ew
     pack .bright.mode -side top -fill x
     set cflist .bright.cfiles
-    set indent [font measure $mainfont "nn"]
+    set indent [font measure mainfont "nn"]
     text $cflist \
        -selectbackground $selectbgcolor \
        -background $bgcolor -foreground $fgcolor \
-       -font $mainfont \
+       -font mainfont \
        -tabs [list $indent [expr {2 * $indent}]] \
        -yscrollcommand ".bright.sb set" \
        -cursor [. cget -cursor] \
@@ -817,7 +920,7 @@ proc makewindow {} {
     pack $cflist -side left -fill both -expand 1
     $cflist tag configure highlight \
        -background [$cflist cget -selectbackground]
-    $cflist tag configure bold -font [concat $mainfont bold]
+    $cflist tag configure bold -font mainfontbold
 
     .pwbottom add .bright
     .ctop add .pwbottom
@@ -856,8 +959,8 @@ proc makewindow {} {
     bindkey <End> sellastline
     bind . <Key-Up> "selnextline -1"
     bind . <Key-Down> "selnextline 1"
-    bind . <Shift-Key-Up> "next_highlight -1"
-    bind . <Shift-Key-Down> "next_highlight 1"
+    bind . <Shift-Key-Up> "dofind -1 0"
+    bind . <Shift-Key-Down> "dofind 1 0"
     bindkey <Key-Right> "goforw"
     bindkey <Key-Left> "goback"
     bind . <Key-Prior> "selnextpage -1"
@@ -882,14 +985,14 @@ proc makewindow {} {
     bindkey b "$ctext yview scroll -1 pages"
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
-    bindkey / {findnext 1}
-    bindkey <Key-Return> {findnext 0}
-    bindkey ? findprev
+    bindkey / {dofind 1 1}
+    bindkey <Key-Return> {dofind 1 1}
+    bindkey ? {dofind -1 1}
     bindkey f nextfile
     bindkey <F5> updatecommits
     bind . <$M1B-q> doquit
-    bind . <$M1B-f> dofind
-    bind . <$M1B-g> {findnext 0}
+    bind . <$M1B-f> {dofind 1 1}
+    bind . <$M1B-g> {dofind 1 0}
     bind . <$M1B-r> dosearchback
     bind . <$M1B-s> dosearch
     bind . <$M1B-equal> {incrfont 1}
@@ -898,7 +1001,7 @@ proc makewindow {} {
     bind . <$M1B-KP_Subtract> {incrfont -1}
     wm protocol . WM_DELETE_WINDOW doquit
     bind . <Button-1> "click %W"
-    bind $fstring <Key-Return> dofind
+    bind $fstring <Key-Return> {dofind 1 1}
     bind $sha1entry <Key-Return> gotocommit
     bind $sha1entry <<PasteSelection>> clearsha1
     bind $cflist <1> {sel_flist %W %x %y; break}
@@ -1014,12 +1117,45 @@ proc click {w} {
     focus .
 }
 
+# Adjust the progress bar for a change in requested extent or canvas size
+proc adjustprogress {} {
+    global progresscanv progressitem progresscoords
+    global fprogitem fprogcoord lastprogupdate progupdatepending
+    global rprogitem rprogcoord
+
+    set w [expr {[winfo width $progresscanv] - 4}]
+    set x0 [expr {$w * [lindex $progresscoords 0]}]
+    set x1 [expr {$w * [lindex $progresscoords 1]}]
+    set h [winfo height $progresscanv]
+    $progresscanv coords $progressitem $x0 0 $x1 $h
+    $progresscanv coords $fprogitem 0 0 [expr {$w * $fprogcoord}] $h
+    $progresscanv coords $rprogitem 0 0 [expr {$w * $rprogcoord}] $h
+    set now [clock clicks -milliseconds]
+    if {$now >= $lastprogupdate + 100} {
+       set progupdatepending 0
+       update
+    } elseif {!$progupdatepending} {
+       set progupdatepending 1
+       after [expr {$lastprogupdate + 100 - $now}] doprogupdate
+    }
+}
+
+proc doprogupdate {} {
+    global lastprogupdate progupdatepending
+
+    if {$progupdatepending} {
+       set progupdatepending 0
+       set lastprogupdate [clock clicks -milliseconds]
+       update
+    }
+}
+
 proc savestuff {w} {
-    global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
+    global canv canv2 canv3 mainfont textfont uifont tabstop
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth showneartags showlocalchanges
     global viewname viewfiles viewargs viewperm nextviewnum
-    global cmitmode wrapcomment datetimeformat
+    global cmitmode wrapcomment datetimeformat limitdiffs
     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
 
     if {$stuffsaved} return
@@ -1038,6 +1174,7 @@ proc savestuff {w} {
        puts $f [list set showneartags $showneartags]
        puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set datetimeformat $datetimeformat]
+       puts $f [list set limitdiffs $limitdiffs]
        puts $f [list set bgcolor $bgcolor]
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
@@ -1149,10 +1286,10 @@ Copyright 
 Use and redistribute under the terms of the GNU General Public License} \
            -justify center -aspect 400 -border 2 -bg white -relief groove
     pack $w.m -side top -fill x -padx 2 -pady 2
-    $w.m configure -font $uifont
+    $w.m configure -font uifont
     button $w.ok -text Close -command "destroy $w" -default active
     pack $w.ok -side bottom
-    $w.ok configure -font $uifont
+    $w.ok configure -font uifont
     bind $w <Visibility> "focus $w.ok"
     bind $w <Key-Escape> "destroy $w"
     bind $w <Key-Return> "destroy $w"
@@ -1190,8 +1327,8 @@ Gitk key bindings:
 <$M1T-Down>    Scroll commit list down one line
 <$M1T-PageUp>  Scroll commit list up one page
 <$M1T-PageDown>        Scroll commit list down one page
-<Shift-Up>     Move to previous highlighted line
-<Shift-Down>   Move to next highlighted line
+<Shift-Up>     Find backwards (upwards, later commits)
+<Shift-Down>   Find forwards (downwards, earlier commits)
 <Delete>, b    Scroll diff view up one page
 <Backspace>    Scroll diff view up one page
 <Space>                Scroll diff view down one page
@@ -1213,10 +1350,10 @@ f               Scroll diff view to next file
 " \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
-    $w.m configure -font $uifont
+    $w.m configure -font uifont
     button $w.ok -text Close -command "destroy $w" -default active
     pack $w.ok -side bottom
-    $w.ok configure -font $uifont
+    $w.ok configure -font uifont
     bind $w <Visibility> "focus $w.ok"
     bind $w <Key-Escape> "destroy $w"
     bind $w <Key-Return> "destroy $w"
@@ -1589,6 +1726,7 @@ proc pop_flist_menu {w X Y x y} {
     global ctext cflist cmitmode flist_menu flist_menu_file
     global treediffs diffids
 
+    stopfinding
     set l [lindex [split [$w index "@$x,$y"] "."] 0]
     if {$l <= 1} return
     if {$cmitmode eq "tree"} {
@@ -1602,14 +1740,15 @@ proc pop_flist_menu {w X Y x y} {
 }
 
 proc flist_hl {only} {
-    global flist_menu_file highlight_files
+    global flist_menu_file findstring gdttype
 
     set x [shellquote $flist_menu_file]
-    if {$only || $highlight_files eq {}} {
-       set highlight_files $x
+    if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
+       set findstring $x
     } else {
-       append highlight_files " " $x
+       append findstring " " $x
     }
+    set gdttype "touching paths:"
 }
 
 # Functions for adding and removing shell-type quoting
@@ -1746,22 +1885,22 @@ proc vieweditor {top n title} {
 
     toplevel $top
     wm title $top $title
-    label $top.nl -text "Name" -font $uifont
-    entry $top.name -width 20 -textvariable newviewname($n) -font $uifont
+    label $top.nl -text "Name" -font uifont
+    entry $top.name -width 20 -textvariable newviewname($n) -font uifont
     grid $top.nl $top.name -sticky w -pady 5
     checkbutton $top.perm -text "Remember this view" -variable newviewperm($n) \
-       -font $uifont
+       -font uifont
     grid $top.perm - -pady 5 -sticky w
-    message $top.al -aspect 1000 -font $uifont \
+    message $top.al -aspect 1000 -font uifont \
        -text "Commits to include (arguments to git rev-list):"
     grid $top.al - -sticky w -pady 5
     entry $top.args -width 50 -textvariable newviewargs($n) \
-       -background white -font $uifont
+       -background white -font uifont
     grid $top.args - -sticky ew -padx 5
-    message $top.l -aspect 1000 -font $uifont \
+    message $top.l -aspect 1000 -font uifont \
        -text "Enter files and directories to include, one per line:"
     grid $top.l - -sticky w
-    text $top.t -width 40 -height 10 -background white -font $uifont
+    text $top.t -width 40 -height 10 -background white -font uifont
     if {[info exists viewfiles($n)]} {
        foreach f $viewfiles($n) {
            $top.t insert end $f
@@ -1773,9 +1912,9 @@ proc vieweditor {top n title} {
     grid $top.t - -sticky ew -padx 5
     frame $top.buts
     button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
-       -font $uifont
+       -font uifont
     button $top.buts.can -text "Cancel" -command [list destroy $top] \
-       -font $uifont
+       -font uifont
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -1794,10 +1933,10 @@ proc doviewmenu {m first cmd op argv} {
 }
 
 proc allviewmenus {n op args} {
-    global viewhlmenu
+    global viewhlmenu
 
     doviewmenu .bar.view 5 [list showview $n] $op $args
-    doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
+    doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
 }
 
 proc newviewok {top n} {
@@ -1840,8 +1979,8 @@ proc newviewok {top n} {
            set viewname($n) $newviewname($n)
            doviewmenu .bar.view 5 [list showview $n] \
                entryconf [list -label $viewname($n)]
-           doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
-               entryconf [list -label $viewname($n) -value $viewname($n)]
+           doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
+               entryconf [list -label $viewname($n) -value $viewname($n)]
        }
        if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
            set viewfiles($n) $files
@@ -1873,8 +2012,8 @@ proc addviewmenu {n} {
 
     .bar.view add radiobutton -label $viewname($n) \
        -command [list showview $n] -variable selectedview -value $n
-    $viewhlmenu add radiobutton -label $viewname($n) \
-       -command [list addvhighlight $n] -variable selectedhlview
+    #$viewhlmenu add radiobutton -label $viewname($n) \
+    #  -command [list addvhighlight $n] -variable selectedhlview
 }
 
 proc flatten {var} {
@@ -1898,17 +2037,17 @@ proc unflatten {var l} {
 
 proc showview {n} {
     global curview viewdata viewfiles
-    global displayorder parentlist rowidlist rowoffsets
+    global displayorder parentlist rowidlist rowisopt rowfinal
     global colormap rowtextx commitrow nextcolor canvxmax
-    global numcommits rowrangelist commitlisted idrowranges rowchk
+    global numcommits commitlisted
     global selectedline currentid canv canvy0
     global treediffs
     global pending_select phase
-    global commitidx rowlaidout rowoptim
+    global commitidx
     global commfd
     global selectedview selectfirst
     global vparentlist vdisporder vcmitlisted
-    global hlview selectedhlview
+    global hlview selectedhlview commitinterest
 
     if {$n == $curview} return
     set selid {}
@@ -1934,15 +2073,11 @@ proc showview {n} {
        set vparentlist($curview) $parentlist
        set vdisporder($curview) $displayorder
        set vcmitlisted($curview) $commitlisted
-       if {$phase ne {}} {
+       if {$phase ne {} ||
+           ![info exists viewdata($curview)] ||
+           [lindex $viewdata($curview) 0] ne {}} {
            set viewdata($curview) \
-               [list $phase $rowidlist $rowoffsets $rowrangelist \
-                    [flatten idrowranges] [flatten idinlist] \
-                    $rowlaidout $rowoptim $numcommits]
-       } elseif {![info exists viewdata($curview)]
-                 || [lindex $viewdata($curview) 0] ne {}} {
-           set viewdata($curview) \
-               [list {} $rowidlist $rowoffsets $rowrangelist]
+               [list $phase $rowidlist $rowisopt $rowfinal]
        }
     }
     catch {unset treediffs}
@@ -1951,12 +2086,14 @@ proc showview {n} {
        unset hlview
        set selectedhlview None
     }
+    catch {unset commitinterest}
 
     set curview $n
     set selectedview $n
     .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
     .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
 
+    run refill_reflist
     if {![info exists viewdata($n)]} {
        if {$selid ne {}} {
            set pending_select $selid
@@ -1971,19 +2108,9 @@ proc showview {n} {
     set parentlist $vparentlist($n)
     set commitlisted $vcmitlisted($n)
     set rowidlist [lindex $v 1]
-    set rowoffsets [lindex $v 2]
-    set rowrangelist [lindex $v 3]
-    if {$phase eq {}} {
-       set numcommits [llength $displayorder]
-       catch {unset idrowranges}
-    } else {
-       unflatten idrowranges [lindex $v 4]
-       unflatten idinlist [lindex $v 5]
-       set rowlaidout [lindex $v 6]
-       set rowoptim [lindex $v 7]
-       set numcommits [lindex $v 8]
-       catch {unset rowchk}
-    }
+    set rowisopt [lindex $v 2]
+    set rowfinal [lindex $v 3]
+    set numcommits $commitidx($n)
 
     catch {unset colormap}
     catch {unset rowtextx}
@@ -2027,7 +2154,6 @@ proc showview {n} {
     } elseif {$numcommits == 0} {
        show_status "No commits selected"
     }
-    run refill_reflist
 }
 
 # Stuff relating to the highlighting facility
@@ -2079,12 +2205,12 @@ proc bolden_name {row font} {
 }
 
 proc unbolden {} {
-    global mainfont boldrows
+    global boldrows
 
     set stillbold {}
     foreach row $boldrows {
        if {![ishighlighted $row]} {
-           bolden $row $mainfont
+           bolden $row mainfont
        } else {
            lappend stillbold $row
        }
@@ -2100,7 +2226,7 @@ proc addvhighlight {n} {
     }
     set hlview $n
     if {$n != $curview && ![info exists viewdata($n)]} {
-       set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
+       set viewdata($n) [list getcommits {{}} 0 0 0]
        set vparentlist($n) {}
        set vdisporder($n) {}
        set vcmitlisted($n) {}
@@ -2123,9 +2249,8 @@ proc delvhighlight {} {
 
 proc vhighlightmore {} {
     global hlview vhl_done commitidx vhighlights
-    global displayorder vdisporder curview mainfont
+    global displayorder vdisporder curview
 
-    set font [concat $mainfont bold]
     set max $commitidx($hlview)
     if {$hlview == $curview} {
        set disp $displayorder
@@ -2141,7 +2266,7 @@ proc vhighlightmore {} {
            set row $commitrow($curview,$id)
            if {$r0 <= $row && $row <= $r1} {
                if {![highlighted $row]} {
-                   bolden $row $font
+                   bolden $row mainfontbold
                }
                set vhighlights($row) 1
            }
@@ -2151,11 +2276,11 @@ proc vhighlightmore {} {
 }
 
 proc askvhighlight {row id} {
-    global hlview vhighlights commitrow iddrawn mainfont
+    global hlview vhighlights commitrow iddrawn
 
     if {[info exists commitrow($hlview,$id)]} {
        if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
-           bolden $row [concat $mainfont bold]
+           bolden $row mainfontbold
        }
        set vhighlights($row) 1
     } else {
@@ -2163,9 +2288,9 @@ proc askvhighlight {row id} {
     }
 }
 
-proc hfiles_change {name ix op} {
+proc hfiles_change {} {
     global highlight_files filehighlight fhighlights fh_serial
-    global mainfont highlight_paths
+    global highlight_paths gdttype
 
     if {[info exists filehighlight]} {
        # delete previous highlights
@@ -2183,6 +2308,69 @@ proc hfiles_change {name ix op} {
     }
 }
 
+proc gdttype_change {name ix op} {
+    global gdttype highlight_files findstring findpattern
+
+    stopfinding
+    if {$findstring ne {}} {
+       if {$gdttype eq "containing:"} {
+           if {$highlight_files ne {}} {
+               set highlight_files {}
+               hfiles_change
+           }
+           findcom_change
+       } else {
+           if {$findpattern ne {}} {
+               set findpattern {}
+               findcom_change
+           }
+           set highlight_files $findstring
+           hfiles_change
+       }
+       drawvisible
+    }
+    # enable/disable findtype/findloc menus too
+}
+
+proc find_change {name ix op} {
+    global gdttype findstring highlight_files
+
+    stopfinding
+    if {$gdttype eq "containing:"} {
+       findcom_change
+    } else {
+       if {$highlight_files ne $findstring} {
+           set highlight_files $findstring
+           hfiles_change
+       }
+    }
+    drawvisible
+}
+
+proc findcom_change args {
+    global nhighlights boldnamerows
+    global findpattern findtype findstring gdttype
+
+    stopfinding
+    # delete previous highlights, if any
+    foreach row $boldnamerows {
+       bolden_name $row mainfont
+    }
+    set boldnamerows {}
+    catch {unset nhighlights}
+    unbolden
+    unmarkmatches
+    if {$gdttype ne "containing:" || $findstring eq {}} {
+       set findpattern {}
+    } elseif {$findtype eq "Regexp"} {
+       set findpattern $findstring
+    } else {
+       set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
+                  $findstring]
+       set findpattern "*$e*"
+    }
+}
+
 proc makepatterns {l} {
     set ret {}
     foreach e $l {
@@ -2205,8 +2393,11 @@ proc do_file_hl {serial} {
        set highlight_paths [makepatterns $paths]
        highlight_filelist
        set gdtargs [concat -- $paths]
-    } else {
+    } elseif {$gdttype eq "adding/removing string:"} {
        set gdtargs [list "-S$highlight_files"]
+    } else {
+       # must be "containing:", i.e. we're searching commit info
+       return
     }
     set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
     set filehighlight [open $cmd r+]
@@ -2236,8 +2427,8 @@ proc askfilehighlight {row id} {
 }
 
 proc readfhighlight {} {
-    global filehighlight fhighlights commitrow curview mainfont iddrawn
-    global fhl_list
+    global filehighlight fhighlights commitrow curview iddrawn
+    global fhl_list find_dirn
 
     if {![info exists filehighlight]} {
        return 0
@@ -2258,7 +2449,7 @@ proc readfhighlight {} {
        if {![info exists commitrow($curview,$line)]} continue
        set row $commitrow($curview,$line)
        if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
-           bolden $row [concat $mainfont bold]
+           bolden $row mainfontbold
        }
        set fhighlights($row) 1
     }
@@ -2269,35 +2460,17 @@ proc readfhighlight {} {
        unset filehighlight
        return 0
     }
-    next_hlcont
-    return 1
-}
-
-proc find_change {name ix op} {
-    global nhighlights mainfont boldnamerows
-    global findstring findpattern findtype
-
-    # delete previous highlights, if any
-    foreach row $boldnamerows {
-       bolden_name $row $mainfont
-    }
-    set boldnamerows {}
-    catch {unset nhighlights}
-    unbolden
-    unmarkmatches
-    if {$findtype ne "Regexp"} {
-       set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
-                  $findstring]
-       set findpattern "*$e*"
+    if {[info exists find_dirn]} {
+       run findmore
     }
-    drawvisible
+    return 1
 }
 
 proc doesmatch {f} {
-    global findtype findstring findpattern
+    global findtype findpattern
 
     if {$findtype eq "Regexp"} {
-       return [regexp $findstring $f]
+       return [regexp $findpattern $f]
     } elseif {$findtype eq "IgnCase"} {
        return [string match -nocase $findpattern $f]
     } else {
@@ -2306,7 +2479,7 @@ proc doesmatch {f} {
 }
 
 proc askfindhighlight {row id} {
-    global nhighlights commitinfo iddrawn mainfont
+    global nhighlights commitinfo iddrawn
     global findloc
     global markingmatches
 
@@ -2327,11 +2500,10 @@ proc askfindhighlight {row id} {
        }
     }
     if {$isbold && [info exists iddrawn($id)]} {
-       set f [concat $mainfont bold]
        if {![ishighlighted $row]} {
-           bolden $row $f
+           bolden $row mainfontbold
            if {$isbold > 1} {
-               bolden_name $row $f
+               bolden_name $row mainfontbold
            }
        }
        if {$markingmatches} {
@@ -2460,7 +2632,7 @@ proc is_ancestor {a} {
 }
 
 proc askrelhighlight {row id} {
-    global descendent highlight_related iddrawn mainfont rhighlights
+    global descendent highlight_related iddrawn rhighlights
     global selectedline ancestor
 
     if {![info exists selectedline]} return
@@ -2484,87 +2656,12 @@ proc askrelhighlight {row id} {
     }
     if {[info exists iddrawn($id)]} {
        if {$isbold && ![ishighlighted $row]} {
-           bolden $row [concat $mainfont bold]
+           bolden $row mainfontbold
        }
     }
     set rhighlights($row) $isbold
 }
 
-proc next_hlcont {} {
-    global fhl_row fhl_dirn displayorder numcommits
-    global vhighlights fhighlights nhighlights rhighlights
-    global hlview filehighlight findstring highlight_related
-
-    if {![info exists fhl_dirn] || $fhl_dirn == 0} return
-    set row $fhl_row
-    while {1} {
-       if {$row < 0 || $row >= $numcommits} {
-           bell
-           set fhl_dirn 0
-           return
-       }
-       set id [lindex $displayorder $row]
-       if {[info exists hlview]} {
-           if {![info exists vhighlights($row)]} {
-               askvhighlight $row $id
-           }
-           if {$vhighlights($row) > 0} break
-       }
-       if {$findstring ne {}} {
-           if {![info exists nhighlights($row)]} {
-               askfindhighlight $row $id
-           }
-           if {$nhighlights($row) > 0} break
-       }
-       if {$highlight_related ne "None"} {
-           if {![info exists rhighlights($row)]} {
-               askrelhighlight $row $id
-           }
-           if {$rhighlights($row) > 0} break
-       }
-       if {[info exists filehighlight]} {
-           if {![info exists fhighlights($row)]} {
-               # ask for a few more while we're at it...
-               set r $row
-               for {set n 0} {$n < 100} {incr n} {
-                   if {![info exists fhighlights($r)]} {
-                       askfilehighlight $r [lindex $displayorder $r]
-                   }
-                   incr r $fhl_dirn
-                   if {$r < 0 || $r >= $numcommits} break
-               }
-               flushhighlights
-           }
-           if {$fhighlights($row) < 0} {
-               set fhl_row $row
-               return
-           }
-           if {$fhighlights($row) > 0} break
-       }
-       incr row $fhl_dirn
-    }
-    set fhl_dirn 0
-    selectline $row 1
-}
-
-proc next_highlight {dirn} {
-    global selectedline fhl_row fhl_dirn
-    global hlview filehighlight findstring highlight_related
-
-    if {![info exists selectedline]} return
-    if {!([info exists hlview] || $findstring ne {} ||
-         $highlight_related ne "None" || [info exists filehighlight])} return
-    set fhl_row [expr {$selectedline + $dirn}]
-    set fhl_dirn $dirn
-    next_hlcont
-}
-
-proc cancel_next_highlight {} {
-    global fhl_dirn
-
-    set fhl_dirn 0
-}
-
 # Graph layout functions
 
 proc shortids {ids} {
@@ -2581,108 +2678,43 @@ proc shortids {ids} {
     return $res
 }
 
-proc incrange {l x o} {
-    set n [llength $l]
-    while {$x < $n} {
-       set e [lindex $l $x]
-       if {$e ne {}} {
-           lset l $x [expr {$e + $o}]
-       }
-       incr x
-    }
-    return $l
-}
-
 proc ntimes {n o} {
     set ret {}
-    for {} {$n > 0} {incr n -1} {
-       lappend ret $o
-    }
-    return $ret
-}
-
-proc usedinrange {id l1 l2} {
-    global children commitrow curview
-
-    if {[info exists commitrow($curview,$id)]} {
-       set r $commitrow($curview,$id)
-       if {$l1 <= $r && $r <= $l2} {
-           return [expr {$r - $l1 + 1}]
-       }
-    }
-    set kids $children($curview,$id)
-    foreach c $kids {
-       set r $commitrow($curview,$c)
-       if {$l1 <= $r && $r <= $l2} {
-           return [expr {$r - $l1 + 1}]
+    set o [list $o]
+    for {set mask 1} {$mask <= $n} {incr mask $mask} {
+       if {($n & $mask) != 0} {
+           set ret [concat $ret $o]
        }
+       set o [concat $o $o]
     }
-    return 0
+    return $ret
 }
 
-proc sanity {row {full 0}} {
-    global rowidlist rowoffsets
+# Work out where id should go in idlist so that order-token
+# values increase from left to right
+proc idcol {idlist id {i 0}} {
+    global ordertok curview
 
-    set col -1
-    set ids [lindex $rowidlist $row]
-    foreach id $ids {
-       incr col
-       if {$id eq {}} continue
-       if {$col < [llength $ids] - 1 &&
-           [lsearch -exact -start [expr {$col+1}] $ids $id] >= 0} {
-           puts "oops: [shortids $id] repeated in row $row col $col: {[shortids [lindex $rowidlist $row]]}"
-       }
-       set o [lindex $rowoffsets $row $col]
-       set y $row
-       set x $col
-       while {$o ne {}} {
-           incr y -1
-           incr x $o
-           if {[lindex $rowidlist $y $x] != $id} {
-               puts "oops: rowoffsets wrong at row [expr {$y+1}] col [expr {$x-$o}]"
-               puts "  id=[shortids $id] check started at row $row"
-               for {set i $row} {$i >= $y} {incr i -1} {
-                   puts "  row $i ids={[shortids [lindex $rowidlist $i]]} offs={[lindex $rowoffsets $i]}"
-               }
-               break
-           }
-           if {!$full} break
-           set o [lindex $rowoffsets $y $x]
+    set t $ordertok($curview,$id)
+    if {$i >= [llength $idlist] ||
+       $t < $ordertok($curview,[lindex $idlist $i])} {
+       if {$i > [llength $idlist]} {
+           set i [llength $idlist]
        }
-    }
-}
-
-proc makeuparrow {oid x y z} {
-    global rowidlist rowoffsets uparrowlen idrowranges displayorder
-
-    for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
-       incr y -1
-       incr x $z
-       set off0 [lindex $rowoffsets $y]
-       for {set x0 $x} {1} {incr x0} {
-           if {$x0 >= [llength $off0]} {
-               set x0 [llength [lindex $rowoffsets [expr {$y-1}]]]
-               break
-           }
-           set z [lindex $off0 $x0]
-           if {$z ne {}} {
-               incr x0 $z
-               break
-           }
+       while {[incr i -1] >= 0 &&
+              $t < $ordertok($curview,[lindex $idlist $i])} {}
+       incr i
+    } else {
+       if {$t > $ordertok($curview,[lindex $idlist $i])} {
+           while {[incr i] < [llength $idlist] &&
+                  $t >= $ordertok($curview,[lindex $idlist $i])} {}
        }
-       set z [expr {$x0 - $x}]
-       lset rowidlist $y [linsert [lindex $rowidlist $y] $x $oid]
-       lset rowoffsets $y [linsert [lindex $rowoffsets $y] $x $z]
     }
-    set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
-    lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
-    lappend idrowranges($oid) [lindex $displayorder $y]
+    return $i
 }
 
 proc initlayout {} {
-    global rowidlist rowoffsets displayorder commitlisted
-    global rowlaidout rowoptim
-    global idinlist rowchk rowrangelist idrowranges
+    global rowidlist rowisopt rowfinal displayorder commitlisted
     global numcommits canvxmax canv
     global nextcolor
     global parentlist
@@ -2693,18 +2725,13 @@ proc initlayout {} {
     set displayorder {}
     set commitlisted {}
     set parentlist {}
-    set rowrangelist {}
     set nextcolor 0
-    set rowidlist {{}}
-    set rowoffsets {{}}
-    catch {unset idinlist}
-    catch {unset rowchk}
-    set rowlaidout 0
-    set rowoptim 0
+    set rowidlist {}
+    set rowisopt {}
+    set rowfinal {}
     set canvxmax [$canv cget -width]
     catch {unset colormap}
     catch {unset rowtextx}
-    catch {unset idrowranges}
     set selectfirst 1
 }
 
@@ -2736,61 +2763,19 @@ proc visiblerows {} {
     return [list $r0 $r1]
 }
 
-proc layoutmore {tmax allread} {
-    global rowlaidout rowoptim commitidx numcommits optim_delay
-    global uparrowlen curview rowidlist idinlist
+proc layoutmore {} {
+    global commitidx viewcomplete numcommits
+    global uparrowlen downarrowlen mingaplen curview
 
-    set showlast 0
-    set showdelay $optim_delay
-    set optdelay [expr {$uparrowlen + 1}]
-    while {1} {
-       if {$rowoptim - $showdelay > $numcommits} {
-           showstuff [expr {$rowoptim - $showdelay}] $showlast
-       } elseif {$rowlaidout - $optdelay > $rowoptim} {
-           set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
-           if {$nr > 100} {
-               set nr 100
-           }
-           optimize_rows $rowoptim 0 [expr {$rowoptim + $nr}]
-           incr rowoptim $nr
-       } elseif {$commitidx($curview) > $rowlaidout} {
-           set nr [expr {$commitidx($curview) - $rowlaidout}]
-           # may need to increase this threshold if uparrowlen or
-           # mingaplen are increased...
-           if {$nr > 150} {
-               set nr 150
-           }
-           set row $rowlaidout
-           set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
-           if {$rowlaidout == $row} {
-               return 0
-           }
-       } elseif {$allread} {
-           set optdelay 0
-           set nrows $commitidx($curview)
-           if {[lindex $rowidlist $nrows] ne {} ||
-               [array names idinlist] ne {}} {
-               layouttail
-               set rowlaidout $commitidx($curview)
-           } elseif {$rowoptim == $nrows} {
-               set showdelay 0
-               set showlast 1
-               if {$numcommits == $nrows} {
-                   return 0
-               }
-           }
-       } else {
-           return 0
-       }
-       if {$tmax ne {} && [clock clicks -milliseconds] >= $tmax} {
-           return 1
-       }
+    set show $commitidx($curview)
+    if {$show > $numcommits || $viewcomplete($curview)} {
+       showstuff $show $viewcomplete($curview)
     }
 }
 
 proc showstuff {canshow last} {
     global numcommits commitrow pending_select selectedline curview
-    global lookingforhead mainheadid displayorder selectfirst
+    global mainheadid displayorder selectfirst
     global lastscrollset commitinterest
 
     if {$numcommits == 0} {
@@ -2798,15 +2783,6 @@ proc showstuff {canshow last} {
        set phase "incrdraw"
        allcanvs delete all
     }
-    for {set l $numcommits} {$l < $canshow} {incr l} {
-       set id [lindex $displayorder $l]
-       if {[info exists commitinterest($id)]} {
-           foreach script $commitinterest($id) {
-               eval [string map [list "%I" $id] $script]
-           }
-           unset commitinterest($id)
-       }
-    }
     set r0 $numcommits
     set prev $numcommits
     set numcommits $canshow
@@ -2837,28 +2813,22 @@ proc showstuff {canshow last} {
            set selectfirst 0
        }
     }
-    if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
-       && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
-       set lookingforhead 0
-       dodiffindex
-    }
 }
 
 proc doshowlocalchanges {} {
-    global lookingforhead curview mainheadid phase commitrow
+    global curview mainheadid phase commitrow
 
     if {[info exists commitrow($curview,$mainheadid)] &&
        ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
        dodiffindex
     } elseif {$phase ne {}} {
-       set lookingforhead 1
+       lappend commitinterest($mainheadid) {}
     }
 }
 
 proc dohidelocalchanges {} {
-    global lookingforhead localfrow localirow lserial
+    global localfrow localirow lserial
 
-    set lookingforhead 0
     if {$localfrow >= 0} {
        removerow $localfrow
        set localfrow -1
@@ -2875,8 +2845,9 @@ proc dohidelocalchanges {} {
 
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
-    global localirow localfrow lserial
+    global localirow localfrow lserial showlocalchanges
 
+    if {!$showlocalchanges} return
     incr lserial
     set localfrow -1
     set localirow -1
@@ -2947,207 +2918,325 @@ proc readdifffiles {fd serial} {
     return 0
 }
 
-proc layoutrows {row endrow last} {
-    global rowidlist rowoffsets displayorder
-    global uparrowlen downarrowlen maxwidth mingaplen
-    global children parentlist
-    global idrowranges
-    global commitidx curview
-    global idinlist rowchk rowrangelist
+proc nextuse {id row} {
+    global commitrow curview children
 
-    set idlist [lindex $rowidlist $row]
-    set offs [lindex $rowoffsets $row]
-    while {$row < $endrow} {
-       set id [lindex $displayorder $row]
-       set nev [expr {[llength $idlist] - $maxwidth + 1}]
-       foreach p [lindex $parentlist $row] {
-           if {![info exists idinlist($p)] || !$idinlist($p)} {
-               incr nev
-           }
-       }
-       if {$nev > 0} {
-           if {!$last &&
-               $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
-           for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
-               set i [lindex $idlist $x]
-               if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
-                   set r [usedinrange $i [expr {$row - $downarrowlen}] \
-                              [expr {$row + $uparrowlen + $mingaplen}]]
-                   if {$r == 0} {
-                       set idlist [lreplace $idlist $x $x]
-                       set offs [lreplace $offs $x $x]
-                       set offs [incrange $offs $x 1]
-                       set idinlist($i) 0
-                       set rm1 [expr {$row - 1}]
-                       lappend idrowranges($i) [lindex $displayorder $rm1]
-                       if {[incr nev -1] <= 0} break
-                       continue
-                   }
-                   set rowchk($i) [expr {$row + $r}]
-               }
+    if {[info exists children($curview,$id)]} {
+       foreach kid $children($curview,$id) {
+           if {![info exists commitrow($curview,$kid)]} {
+               return -1
+           }
+           if {$commitrow($curview,$kid) > $row} {
+               return $commitrow($curview,$kid)
            }
-           lset rowidlist $row $idlist
-           lset rowoffsets $row $offs
        }
-       set oldolds {}
-       set newolds {}
-       foreach p [lindex $parentlist $row] {
-           if {![info exists idinlist($p)]} {
-               lappend newolds $p
-           } elseif {!$idinlist($p)} {
-               lappend oldolds $p
+    }
+    if {[info exists commitrow($curview,$id)]} {
+       return $commitrow($curview,$id)
+    }
+    return -1
+}
+
+proc prevuse {id row} {
+    global commitrow curview children
+
+    set ret -1
+    if {[info exists children($curview,$id)]} {
+       foreach kid $children($curview,$id) {
+           if {![info exists commitrow($curview,$kid)]} break
+           if {$commitrow($curview,$kid) < $row} {
+               set ret $commitrow($curview,$kid)
            }
-           set idinlist($p) 1
        }
-       set col [lsearch -exact $idlist $id]
-       if {$col < 0} {
-           set col [llength $idlist]
-           lappend idlist $id
-           lset rowidlist $row $idlist
-           set z {}
-           if {$children($curview,$id) ne {}} {
-               set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
-               unset idinlist($id)
-           }
-           lappend offs $z
-           lset rowoffsets $row $offs
-           if {$z ne {}} {
-               makeuparrow $id $col $row $z
+    }
+    return $ret
+}
+
+proc make_idlist {row} {
+    global displayorder parentlist uparrowlen downarrowlen mingaplen
+    global commitidx curview ordertok children commitrow
+
+    set r [expr {$row - $mingaplen - $downarrowlen - 1}]
+    if {$r < 0} {
+       set r 0
+    }
+    set ra [expr {$row - $downarrowlen}]
+    if {$ra < 0} {
+       set ra 0
+    }
+    set rb [expr {$row + $uparrowlen}]
+    if {$rb > $commitidx($curview)} {
+       set rb $commitidx($curview)
+    }
+    set ids {}
+    for {} {$r < $ra} {incr r} {
+       set nextid [lindex $displayorder [expr {$r + 1}]]
+       foreach p [lindex $parentlist $r] {
+           if {$p eq $nextid} continue
+           set rn [nextuse $p $r]
+           if {$rn >= $row &&
+               $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
+               lappend ids [list $ordertok($curview,$p) $p]
            }
-       } else {
-           unset idinlist($id)
-       }
-       set ranges {}
-       if {[info exists idrowranges($id)]} {
-           set ranges $idrowranges($id)
-           lappend ranges $id
-           unset idrowranges($id)
-       }
-       lappend rowrangelist $ranges
-       incr row
-       set offs [ntimes [llength $idlist] 0]
-       set l [llength $newolds]
-       set idlist [eval lreplace \$idlist $col $col $newolds]
-       set o 0
-       if {$l != 1} {
-           set offs [lrange $offs 0 [expr {$col - 1}]]
-           foreach x $newolds {
-               lappend offs {}
-               incr o -1
-           }
-           incr o
-           set tmp [expr {[llength $idlist] - [llength $offs]}]
-           if {$tmp > 0} {
-               set offs [concat $offs [ntimes $tmp $o]]
+       }
+    }
+    for {} {$r < $row} {incr r} {
+       set nextid [lindex $displayorder [expr {$r + 1}]]
+       foreach p [lindex $parentlist $r] {
+           if {$p eq $nextid} continue
+           set rn [nextuse $p $r]
+           if {$rn < 0 || $rn >= $row} {
+               lappend ids [list $ordertok($curview,$p) $p]
            }
-       } else {
-           lset offs $col {}
        }
-       foreach i $newolds {
-           set idrowranges($i) $id
+    }
+    set id [lindex $displayorder $row]
+    lappend ids [list $ordertok($curview,$id) $id]
+    while {$r < $rb} {
+       foreach p [lindex $parentlist $r] {
+           set firstkid [lindex $children($curview,$p) 0]
+           if {$commitrow($curview,$firstkid) < $row} {
+               lappend ids [list $ordertok($curview,$p) $p]
+           }
        }
-       incr col $l
-       foreach oid $oldolds {
-           set idlist [linsert $idlist $col $oid]
-           set offs [linsert $offs $col $o]
-           makeuparrow $oid $col $row $o
-           incr col
+       incr r
+       set id [lindex $displayorder $r]
+       if {$id ne {}} {
+           set firstkid [lindex $children($curview,$id) 0]
+           if {$firstkid ne {} && $commitrow($curview,$firstkid) < $row} {
+               lappend ids [list $ordertok($curview,$id) $id]
+           }
        }
-       lappend rowidlist $idlist
-       lappend rowoffsets $offs
     }
-    return $row
+    set idlist {}
+    foreach idx [lsort -unique $ids] {
+       lappend idlist [lindex $idx 1]
+    }
+    return $idlist
 }
 
-proc addextraid {id row} {
-    global displayorder commitrow commitinfo
-    global commitidx commitlisted
-    global parentlist children curview
+proc rowsequal {a b} {
+    while {[set i [lsearch -exact $a {}]] >= 0} {
+       set a [lreplace $a $i $i]
+    }
+    while {[set i [lsearch -exact $b {}]] >= 0} {
+       set b [lreplace $b $i $i]
+    }
+    return [expr {$a eq $b}]
+}
 
-    incr commitidx($curview)
-    lappend displayorder $id
-    lappend commitlisted 0
-    lappend parentlist {}
-    set commitrow($curview,$id) $row
-    readcommit $id
-    if {![info exists commitinfo($id)]} {
-       set commitinfo($id) {"No commit information available"}
+proc makeupline {id row rend col} {
+    global rowidlist uparrowlen downarrowlen mingaplen
+
+    for {set r $rend} {1} {set r $rstart} {
+       set rstart [prevuse $id $r]
+       if {$rstart < 0} return
+       if {$rstart < $row} break
+    }
+    if {$rstart + $uparrowlen + $mingaplen + $downarrowlen < $rend} {
+       set rstart [expr {$rend - $uparrowlen - 1}]
     }
-    if {![info exists children($curview,$id)]} {
-       set children($curview,$id) {}
+    for {set r $rstart} {[incr r] <= $row} {} {
+       set idlist [lindex $rowidlist $r]
+       if {$idlist ne {} && [lsearch -exact $idlist $id] < 0} {
+           set col [idcol $idlist $id $col]
+           lset rowidlist $r [linsert $idlist $col $id]
+           changedrow $r
+       }
     }
 }
 
-proc layouttail {} {
-    global rowidlist rowoffsets idinlist commitidx curview
-    global idrowranges rowrangelist
+proc layoutrows {row endrow} {
+    global rowidlist rowisopt rowfinal displayorder
+    global uparrowlen downarrowlen maxwidth mingaplen
+    global children parentlist
+    global commitidx viewcomplete curview commitrow
 
-    set row $commitidx($curview)
-    set idlist [lindex $rowidlist $row]
-    while {$idlist ne {}} {
-       set col [expr {[llength $idlist] - 1}]
-       set id [lindex $idlist $col]
-       addextraid $id $row
-       catch {unset idinlist($id)}
-       lappend idrowranges($id) $id
-       lappend rowrangelist $idrowranges($id)
-       unset idrowranges($id)
-       incr row
-       set offs [ntimes $col 0]
-       set idlist [lreplace $idlist $col $col]
-       lappend rowidlist $idlist
-       lappend rowoffsets $offs
-    }
-
-    foreach id [array names idinlist] {
-       unset idinlist($id)
-       addextraid $id $row
-       lset rowidlist $row [list $id]
-       lset rowoffsets $row 0
-       makeuparrow $id 0 $row 0
-       lappend idrowranges($id) $id
-       lappend rowrangelist $idrowranges($id)
-       unset idrowranges($id)
-       incr row
-       lappend rowidlist {}
-       lappend rowoffsets {}
+    set idlist {}
+    if {$row > 0} {
+       set rm1 [expr {$row - 1}]
+       foreach id [lindex $rowidlist $rm1] {
+           if {$id ne {}} {
+               lappend idlist $id
+           }
+       }
+       set final [lindex $rowfinal $rm1]
+    }
+    for {} {$row < $endrow} {incr row} {
+       set rm1 [expr {$row - 1}]
+       if {$rm1 < 0 || $idlist eq {}} {
+           set idlist [make_idlist $row]
+           set final 1
+       } else {
+           set id [lindex $displayorder $rm1]
+           set col [lsearch -exact $idlist $id]
+           set idlist [lreplace $idlist $col $col]
+           foreach p [lindex $parentlist $rm1] {
+               if {[lsearch -exact $idlist $p] < 0} {
+                   set col [idcol $idlist $p $col]
+                   set idlist [linsert $idlist $col $p]
+                   # if not the first child, we have to insert a line going up
+                   if {$id ne [lindex $children($curview,$p) 0]} {
+                       makeupline $p $rm1 $row $col
+                   }
+               }
+           }
+           set id [lindex $displayorder $row]
+           if {$row > $downarrowlen} {
+               set termrow [expr {$row - $downarrowlen - 1}]
+               foreach p [lindex $parentlist $termrow] {
+                   set i [lsearch -exact $idlist $p]
+                   if {$i < 0} continue
+                   set nr [nextuse $p $termrow]
+                   if {$nr < 0 || $nr >= $row + $mingaplen + $uparrowlen} {
+                       set idlist [lreplace $idlist $i $i]
+                   }
+               }
+           }
+           set col [lsearch -exact $idlist $id]
+           if {$col < 0} {
+               set col [idcol $idlist $id]
+               set idlist [linsert $idlist $col $id]
+               if {$children($curview,$id) ne {}} {
+                   makeupline $id $rm1 $row $col
+               }
+           }
+           set r [expr {$row + $uparrowlen - 1}]
+           if {$r < $commitidx($curview)} {
+               set x $col
+               foreach p [lindex $parentlist $r] {
+                   if {[lsearch -exact $idlist $p] >= 0} continue
+                   set fk [lindex $children($curview,$p) 0]
+                   if {$commitrow($curview,$fk) < $row} {
+                       set x [idcol $idlist $p $x]
+                       set idlist [linsert $idlist $x $p]
+                   }
+               }
+               if {[incr r] < $commitidx($curview)} {
+                   set p [lindex $displayorder $r]
+                   if {[lsearch -exact $idlist $p] < 0} {
+                       set fk [lindex $children($curview,$p) 0]
+                       if {$fk ne {} && $commitrow($curview,$fk) < $row} {
+                           set x [idcol $idlist $p $x]
+                           set idlist [linsert $idlist $x $p]
+                       }
+                   }
+               }
+           }
+       }
+       if {$final && !$viewcomplete($curview) &&
+           $row + $uparrowlen + $mingaplen + $downarrowlen
+               >= $commitidx($curview)} {
+           set final 0
+       }
+       set l [llength $rowidlist]
+       if {$row == $l} {
+           lappend rowidlist $idlist
+           lappend rowisopt 0
+           lappend rowfinal $final
+       } elseif {$row < $l} {
+           if {![rowsequal $idlist [lindex $rowidlist $row]]} {
+               lset rowidlist $row $idlist
+               changedrow $row
+           }
+           lset rowfinal $row $final
+       } else {
+           set pad [ntimes [expr {$row - $l}] {}]
+           set rowidlist [concat $rowidlist $pad]
+           lappend rowidlist $idlist
+           set rowfinal [concat $rowfinal $pad]
+           lappend rowfinal $final
+           set rowisopt [concat $rowisopt [ntimes [expr {$row - $l + 1}] 0]]
+       }
+    }
+    return $row
+}
+
+proc changedrow {row} {
+    global displayorder iddrawn rowisopt need_redisplay
+
+    set l [llength $rowisopt]
+    if {$row < $l} {
+       lset rowisopt $row 0
+       if {$row + 1 < $l} {
+           lset rowisopt [expr {$row + 1}] 0
+           if {$row + 2 < $l} {
+               lset rowisopt [expr {$row + 2}] 0
+           }
+       }
+    }
+    set id [lindex $displayorder $row]
+    if {[info exists iddrawn($id)]} {
+       set need_redisplay 1
     }
 }
 
 proc insert_pad {row col npad} {
-    global rowidlist rowoffsets
+    global rowidlist
 
     set pad [ntimes $npad {}]
-    lset rowidlist $row [eval linsert [list [lindex $rowidlist $row]] $col $pad]
-    set tmp [eval linsert [list [lindex $rowoffsets $row]] $col $pad]
-    lset rowoffsets $row [incrange $tmp [expr {$col + $npad}] [expr {-$npad}]]
+    set idlist [lindex $rowidlist $row]
+    set bef [lrange $idlist 0 [expr {$col - 1}]]
+    set aft [lrange $idlist $col end]
+    set i [lsearch -exact $aft {}]
+    if {$i > 0} {
+       set aft [lreplace $aft $i $i]
+    }
+    lset rowidlist $row [concat $bef $pad $aft]
+    changedrow $row
 }
 
 proc optimize_rows {row col endrow} {
-    global rowidlist rowoffsets displayorder
+    global rowidlist rowisopt displayorder curview children
 
-    for {} {$row < $endrow} {incr row} {
-       set idlist [lindex $rowidlist $row]
-       set offs [lindex $rowoffsets $row]
+    if {$row < 1} {
+       set row 1
+    }
+    for {} {$row < $endrow} {incr row; set col 0} {
+       if {[lindex $rowisopt $row]} continue
        set haspad 0
-       for {} {$col < [llength $offs]} {incr col} {
-           if {[lindex $idlist $col] eq {}} {
+       set y0 [expr {$row - 1}]
+       set ym [expr {$row - 2}]
+       set idlist [lindex $rowidlist $row]
+       set previdlist [lindex $rowidlist $y0]
+       if {$idlist eq {} || $previdlist eq {}} continue
+       if {$ym >= 0} {
+           set pprevidlist [lindex $rowidlist $ym]
+           if {$pprevidlist eq {}} continue
+       } else {
+           set pprevidlist {}
+       }
+       set x0 -1
+       set xm -1
+       for {} {$col < [llength $idlist]} {incr col} {
+           set id [lindex $idlist $col]
+           if {[lindex $previdlist $col] eq $id} continue
+           if {$id eq {}} {
                set haspad 1
                continue
            }
-           set z [lindex $offs $col]
-           if {$z eq {}} continue
+           set x0 [lsearch -exact $previdlist $id]
+           if {$x0 < 0} continue
+           set z [expr {$x0 - $col}]
            set isarrow 0
-           set x0 [expr {$col + $z}]
-           set y0 [expr {$row - 1}]
-           set z0 [lindex $rowoffsets $y0 $x0]
+           set z0 {}
+           if {$ym >= 0} {
+               set xm [lsearch -exact $pprevidlist $id]
+               if {$xm >= 0} {
+                   set z0 [expr {$xm - $x0}]
+               }
+           }
            if {$z0 eq {}} {
-               set id [lindex $idlist $col]
-               set ranges [rowranges $id]
-               if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
+               # if row y0 is the first child of $id then it's not an arrow
+               if {[lindex $children($curview,$id) 0] ne
+                   [lindex $displayorder $y0]} {
                    set isarrow 1
                }
            }
+           if {!$isarrow && $id ne [lindex $displayorder $row] &&
+               [lsearch -exact [lindex $rowidlist [expr {$row+1}]] $id] < 0} {
+               set isarrow 1
+           }
            # Looking at lines from this row to the previous row,
            # make them go straight up if they end in an arrow on
            # the previous row; otherwise make them go straight up
@@ -3156,43 +3245,32 @@ proc optimize_rows {row col endrow} {
                # Line currently goes left too much;
                # insert pads in the previous row, then optimize it
                set npad [expr {-1 - $z + $isarrow}]
-               set offs [incrange $offs $col $npad]
                insert_pad $y0 $x0 $npad
                if {$y0 > 0} {
                    optimize_rows $y0 $x0 $row
                }
-               set z [lindex $offs $col]
-               set x0 [expr {$col + $z}]
-               set z0 [lindex $rowoffsets $y0 $x0]
+               set previdlist [lindex $rowidlist $y0]
+               set x0 [lsearch -exact $previdlist $id]
+               set z [expr {$x0 - $col}]
+               if {$z0 ne {}} {
+                   set pprevidlist [lindex $rowidlist $ym]
+                   set xm [lsearch -exact $pprevidlist $id]
+                   set z0 [expr {$xm - $x0}]
+               }
            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
                # Line currently goes right too much;
-               # insert pads in this line and adjust the next's rowoffsets
+               # insert pads in this line
                set npad [expr {$z - 1 + $isarrow}]
-               set y1 [expr {$row + 1}]
-               set offs2 [lindex $rowoffsets $y1]
-               set x1 -1
-               foreach z $offs2 {
-                   incr x1
-                   if {$z eq {} || $x1 + $z < $col} continue
-                   if {$x1 + $z > $col} {
-                       incr npad
-                   }
-                   lset rowoffsets $y1 [incrange $offs2 $x1 $npad]
-                   break
-               }
-               set pad [ntimes $npad {}]
-               set idlist [eval linsert \$idlist $col $pad]
-               set tmp [eval linsert \$offs $col $pad]
+               insert_pad $row $col $npad
+               set idlist [lindex $rowidlist $row]
                incr col $npad
-               set offs [incrange $tmp $col [expr {-$npad}]]
-               set z [lindex $offs $col]
+               set z [expr {$x0 - $col}]
                set haspad 1
            }
-           if {$z0 eq {} && !$isarrow} {
+           if {$z0 eq {} && !$isarrow && $ym >= 0} {
                # this line links to its first child on row $row-2
-               set rm2 [expr {$row - 2}]
-               set id [lindex $displayorder $rm2]
-               set xc [lsearch -exact [lindex $rowidlist $rm2] $id]
+               set id [lindex $displayorder $ym]
+               set xc [lsearch -exact $pprevidlist $id]
                if {$xc >= 0} {
                    set z0 [expr {$xc - $x0}]
                }
@@ -3200,52 +3278,35 @@ proc optimize_rows {row col endrow} {
            # avoid lines jigging left then immediately right
            if {$z0 ne {} && $z < 0 && $z0 > 0} {
                insert_pad $y0 $x0 1
-               set offs [incrange $offs $col 1]
-               optimize_rows $y0 [expr {$x0 + 1}] $row
+               incr x0
+               optimize_rows $y0 $x0 $row
+               set previdlist [lindex $rowidlist $y0]
            }
        }
        if {!$haspad} {
-           set o {}
            # Find the first column that doesn't have a line going right
            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
-               set o [lindex $offs $col]
-               if {$o eq {}} {
+               set id [lindex $idlist $col]
+               if {$id eq {}} break
+               set x0 [lsearch -exact $previdlist $id]
+               if {$x0 < 0} {
                    # check if this is the link to the first child
-                   set id [lindex $idlist $col]
-                   set ranges [rowranges $id]
-                   if {$ranges ne {} && $row == [lindex $ranges 0]} {
+                   set kid [lindex $displayorder $y0]
+                   if {[lindex $children($curview,$id) 0] eq $kid} {
                        # it is, work out offset to child
-                       set y0 [expr {$row - 1}]
-                       set id [lindex $displayorder $y0]
-                       set x0 [lsearch -exact [lindex $rowidlist $y0] $id]
-                       if {$x0 >= 0} {
-                           set o [expr {$x0 - $col}]
-                       }
+                       set x0 [lsearch -exact $previdlist $kid]
                    }
                }
-               if {$o eq {} || $o <= 0} break
+               if {$x0 <= $col} break
            }
            # Insert a pad at that column as long as it has a line and
-           # isn't the last column, and adjust the next row' offsets
-           if {$o ne {} && [incr col] < [llength $idlist]} {
-               set y1 [expr {$row + 1}]
-               set offs2 [lindex $rowoffsets $y1]
-               set x1 -1
-               foreach z $offs2 {
-                   incr x1
-                   if {$z eq {} || $x1 + $z < $col} continue
-                   lset rowoffsets $y1 [incrange $offs2 $x1 1]
-                   break
-               }
+           # isn't the last column
+           if {$x0 >= 0 && [incr col] < [llength $idlist]} {
                set idlist [linsert $idlist $col {}]
-               set tmp [linsert $offs $col {}]
-               incr col
-               set offs [incrange $tmp $col -1]
+               lset rowidlist $row $idlist
+               changedrow $row
            }
        }
-       lset rowidlist $row $idlist
-       lset rowoffsets $row $offs
-       set col 0
     }
 }
 
@@ -3270,51 +3331,64 @@ proc linewidth {id} {
 }
 
 proc rowranges {id} {
-    global phase idrowranges commitrow rowlaidout rowrangelist curview
-
-    set ranges {}
-    if {$phase eq {} ||
-       ([info exists commitrow($curview,$id)]
-        && $commitrow($curview,$id) < $rowlaidout)} {
-       set ranges [lindex $rowrangelist $commitrow($curview,$id)]
-    } elseif {[info exists idrowranges($id)]} {
-       set ranges $idrowranges($id)
-    }
-    set linenos {}
-    foreach rid $ranges {
-       lappend linenos $commitrow($curview,$rid)
-    }
-    if {$linenos ne {}} {
-       lset linenos 0 [expr {[lindex $linenos 0] + 1}]
-    }
-    return $linenos
-}
-
-# work around tk8.4 refusal to draw arrows on diagonal segments
-proc adjarrowhigh {coords} {
-    global linespc
-
-    set x0 [lindex $coords 0]
-    set x1 [lindex $coords 2]
-    if {$x0 != $x1} {
-       set y0 [lindex $coords 1]
-       set y1 [lindex $coords 3]
-       if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
-           # we have a nearby vertical segment, just trim off the diag bit
-           set coords [lrange $coords 2 end]
+    global commitrow curview children uparrowlen downarrowlen
+    global rowidlist
+
+    set kids $children($curview,$id)
+    if {$kids eq {}} {
+       return {}
+    }
+    set ret {}
+    lappend kids $id
+    foreach child $kids {
+       if {![info exists commitrow($curview,$child)]} break
+       set row $commitrow($curview,$child)
+       if {![info exists prev]} {
+           lappend ret [expr {$row + 1}]
        } else {
-           set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
-           set xi [expr {$x0 - $slope * $linespc / 2}]
-           set yi [expr {$y0 - $linespc / 2}]
-           set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
+           if {$row <= $prevrow} {
+               puts "oops children out of order [shortids $id] $row < [shortids $prev] $prevrow"
+           }
+           # see if the line extends the whole way from prevrow to row
+           if {$row > $prevrow + $uparrowlen + $downarrowlen &&
+               [lsearch -exact [lindex $rowidlist \
+                           [expr {int(($row + $prevrow) / 2)}]] $id] < 0} {
+               # it doesn't, see where it ends
+               set r [expr {$prevrow + $downarrowlen}]
+               if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
+                   while {[incr r -1] > $prevrow &&
+                          [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
+               } else {
+                   while {[incr r] <= $row &&
+                          [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
+                   incr r -1
+               }
+               lappend ret $r
+               # see where it starts up again
+               set r [expr {$row - $uparrowlen}]
+               if {[lsearch -exact [lindex $rowidlist $r] $id] < 0} {
+                   while {[incr r] < $row &&
+                          [lsearch -exact [lindex $rowidlist $r] $id] < 0} {}
+               } else {
+                   while {[incr r -1] >= $prevrow &&
+                          [lsearch -exact [lindex $rowidlist $r] $id] >= 0} {}
+                   incr r
+               }
+               lappend ret $r
+           }
+       }
+       if {$child eq $id} {
+           lappend ret $row
        }
+       set prev $id
+       set prevrow $row
     }
-    return $coords
+    return $ret
 }
 
 proc drawlineseg {id row endrow arrowlow} {
     global rowidlist displayorder iddrawn linesegs
-    global canv colormap linespc curview maxlinelen
+    global canv colormap linespc curview maxlinelen parentlist
 
     set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
     set le [expr {$row + 1}]
@@ -3389,9 +3463,11 @@ proc drawlineseg {id row endrow arrowlow} {
        set itl [lindex $lines [expr {$i-1}] 2]
        set al [$canv itemcget $itl -arrow]
        set arrowlow [expr {$al eq "last" || $al eq "both"}]
-    } elseif {$arrowlow &&
-             [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
-       set arrowlow 0
+    } elseif {$arrowlow} {
+       if {[lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0 ||
+           [lsearch -exact [lindex $parentlist [expr {$row-1}]] $id] >= 0} {
+           set arrowlow 0
+       }
     }
     set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
     for {set y $le} {[incr y -1] > $row} {} {
@@ -3410,8 +3486,19 @@ proc drawlineseg {id row endrow arrowlow} {
            set xc [lsearch -exact [lindex $rowidlist $row] $ch]
            if {$xc < 0} {
                puts "oops: drawlineseg: child $ch not on row $row"
-           } else {
-               if {$xc < $x - 1} {
+           } elseif {$xc != $x} {
+               if {($arrowhigh && $le == $row + 1) || $dir == 0} {
+                   set d [expr {int(0.5 * $linespc)}]
+                   set x1 [xc $row $x]
+                   if {$xc < $x} {
+                       set x2 [expr {$x1 - $d}]
+                   } else {
+                       set x2 [expr {$x1 + $d}]
+                   }
+                   set y2 [yc $row]
+                   set y1 [expr {$y2 + $d}]
+                   lappend coords $x1 $y1 $x2 $y2
+               } elseif {$xc < $x - 1} {
                    lappend coords [xc $row [expr {$x-1}]] [yc $row]
                } elseif {$xc > $x + 1} {
                    lappend coords [xc $row [expr {$x+1}]] [yc $row]
@@ -3422,23 +3509,9 @@ proc drawlineseg {id row endrow arrowlow} {
        } else {
            set xn [xc $row $xp]
            set yn [yc $row]
-           # work around tk8.4 refusal to draw arrows on diagonal segments
-           if {$arrowlow && $xn != [lindex $coords end-1]} {
-               if {[llength $coords] < 4 ||
-                   [lindex $coords end-3] != [lindex $coords end-1] ||
-                   [lindex $coords end] - $yn > 2 * $linespc} {
-                   set xn [xc $row [expr {$xp - 0.5 * $dir}]]
-                   set yo [yc [expr {$row + 0.5}]]
-                   lappend coords $xn $yo $xn $yn
-               }
-           } else {
-               lappend coords $xn $yn
-           }
+           lappend coords $xn $yn
        }
        if {!$joinhigh} {
-           if {$arrowhigh} {
-               set coords [adjarrowhigh $coords]
-           }
            assigncolor $id
            set t [$canv create line $coords -width [linewidth $id] \
                       -fill $colormap($id) -tags lines.$id -arrow $arrow]
@@ -3462,9 +3535,6 @@ proc drawlineseg {id row endrow arrowlow} {
        set coords [concat $coords $clow]
        if {!$joinhigh} {
            lset lines [expr {$i-1}] 1 $le
-           if {$arrowhigh} {
-               set coords [adjarrowhigh $coords]
-           }
        } else {
            # coalesce two pieces
            $canv delete $ith
@@ -3484,7 +3554,7 @@ proc drawlineseg {id row endrow arrowlow} {
 
 proc drawparentlinks {id row} {
     global rowidlist canv colormap curview parentlist
-    global idpos
+    global idpos linespc
 
     set rowids [lindex $rowidlist $row]
     set col [lsearch -exact $rowids $id]
@@ -3494,6 +3564,8 @@ proc drawparentlinks {id row} {
     set x [xc $row $col]
     set y [yc $row]
     set y2 [yc $row2]
+    set d [expr {int(0.5 * $linespc)}]
+    set ymid [expr {$y + $d}]
     set ids [lindex $rowidlist $row2]
     # rmx = right-most X coord used
     set rmx 0
@@ -3507,19 +3579,37 @@ proc drawparentlinks {id row} {
        if {$x2 > $rmx} {
            set rmx $x2
        }
-       if {[lsearch -exact $rowids $p] < 0} {
+       set j [lsearch -exact $rowids $p]
+       if {$j < 0} {
            # drawlineseg will do this one for us
            continue
        }
        assigncolor $p
        # should handle duplicated parents here...
        set coords [list $x $y]
-       if {$i < $col - 1} {
-           lappend coords [xc $row [expr {$i + 1}]] $y
-       } elseif {$i > $col + 1} {
-           lappend coords [xc $row [expr {$i - 1}]] $y
+       if {$i != $col} {
+           # if attaching to a vertical segment, draw a smaller
+           # slant for visual distinctness
+           if {$i == $j} {
+               if {$i < $col} {
+                   lappend coords [expr {$x2 + $d}] $y $x2 $ymid
+               } else {
+                   lappend coords [expr {$x2 - $d}] $y $x2 $ymid
+               }
+           } elseif {$i < $col && $i < $j} {
+               # segment slants towards us already
+               lappend coords [xc $row $j] $y
+           } else {
+               if {$i < $col - 1} {
+                   lappend coords [expr {$x2 + $linespc}] $y
+               } elseif {$i > $col + 1} {
+                   lappend coords [expr {$x2 - $linespc}] $y
+               }
+               lappend coords $x2 $y2
+           }
+       } else {
+           lappend coords $x2 $y2
        }
-       lappend coords $x2 $y2
        set t [$canv create line $coords -width [linewidth $p] \
                   -fill $colormap($p) -tags lines.$p]
        $canv lower $t
@@ -3541,8 +3631,8 @@ proc drawcmittext {id row col} {
     global linespc canv canv2 canv3 canvy0 fgcolor curview
     global commitlisted commitinfo rowidlist parentlist
     global rowtextx idpos idtags idheads idotherrefs
-    global linehtag linentag linedtag
-    global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
+    global linehtag linentag linedtag selectedline
+    global canvxmax boldrows boldnamerows fgcolor nullid nullid2
 
     # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
     set listed [lindex $commitlisted $row]
@@ -3599,15 +3689,15 @@ proc drawcmittext {id row col} {
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
     set date [formatdate $date]
-    set font $mainfont
-    set nfont $mainfont
+    set font mainfont
+    set nfont mainfont
     set isbold [ishighlighted $row]
     if {$isbold > 0} {
        lappend boldrows $row
-       lappend font bold
+       set font mainfontbold
        if {$isbold > 1} {
            lappend boldnamerows $row
-           lappend nfont bold
+           set nfont mainfontbold
        }
     }
     set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
@@ -3616,8 +3706,11 @@ proc drawcmittext {id row col} {
     set linentag($row) [$canv2 create text 3 $y -anchor w -fill $fgcolor \
                            -text $name -font $nfont -tags text]
     set linedtag($row) [$canv3 create text 3 $y -anchor w -fill $fgcolor \
-                           -text $date -font $mainfont -tags text]
-    set xr [expr {$xt + [font measure $mainfont $headline]}]
+                           -text $date -font mainfont -tags text]
+    if {[info exists selectedline] && $selectedline == $row} {
+       make_secsel $row
+    }
+    set xr [expr {$xt + [font measure $font $headline]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
        setcanvscroll
@@ -3625,10 +3718,10 @@ proc drawcmittext {id row col} {
 }
 
 proc drawcmitrow {row} {
-    global displayorder rowidlist
+    global displayorder rowidlist nrows_drawn
     global iddrawn markingmatches
     global commitinfo parentlist numcommits
-    global filehighlight fhighlights findstring nhighlights
+    global filehighlight fhighlights findpattern nhighlights
     global hlview vhighlights
     global highlight_related rhighlights
 
@@ -3641,7 +3734,7 @@ proc drawcmitrow {row} {
     if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
        askfilehighlight $row $id
     }
-    if {$findstring ne {} && ![info exists nhighlights($row)]} {
+    if {$findpattern ne {} && ![info exists nhighlights($row)]} {
        askfindhighlight $row $id
     }
     if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
@@ -3659,6 +3752,7 @@ proc drawcmitrow {row} {
        assigncolor $id
        drawcmittext $id $row $col
        set iddrawn($id) 1
+       incr nrows_drawn
     }
     if {$markingmatches} {
        markrowmatches $row $id
@@ -3666,8 +3760,8 @@ proc drawcmitrow {row} {
 }
 
 proc drawcommits {row {endrow {}}} {
-    global numcommits iddrawn displayorder curview
-    global parentlist rowidlist
+    global numcommits iddrawn displayorder curview need_redisplay
+    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
 
     if {$row < 0} {
        set row 0
@@ -3679,6 +3773,35 @@ proc drawcommits {row {endrow {}}} {
        set endrow [expr {$numcommits - 1}]
     }
 
+    set rl1 [expr {$row - $downarrowlen - 3}]
+    if {$rl1 < 0} {
+       set rl1 0
+    }
+    set ro1 [expr {$row - 3}]
+    if {$ro1 < 0} {
+       set ro1 0
+    }
+    set r2 [expr {$endrow + $uparrowlen + 3}]
+    if {$r2 > $numcommits} {
+       set r2 $numcommits
+    }
+    for {set r $rl1} {$r < $r2} {incr r} {
+       if {[lindex $rowidlist $r] ne {} && [lindex $rowfinal $r]} {
+           if {$rl1 < $r} {
+               layoutrows $rl1 $r
+           }
+           set rl1 [expr {$r + 1}]
+       }
+    }
+    if {$rl1 < $r} {
+       layoutrows $rl1 $r
+    }
+    optimize_rows $ro1 0 $r2
+    if {$need_redisplay || $nrows_drawn > 2000} {
+       clear_display
+       drawvisible
+    }
+
     # make the lines join to already-drawn rows either side
     set r [expr {$row - 1}]
     if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
@@ -3735,7 +3858,7 @@ proc drawvisible {} {
 }
 
 proc clear_display {} {
-    global iddrawn linesegs
+    global iddrawn linesegs need_redisplay nrows_drawn
     global vhighlights fhighlights nhighlights rhighlights
 
     allcanvs delete all
@@ -3745,10 +3868,12 @@ proc clear_display {} {
     catch {unset fhighlights}
     catch {unset nhighlights}
     catch {unset rhighlights}
+    set need_redisplay 0
+    set nrows_drawn 0
 }
 
 proc findcrossings {id} {
-    global rowidlist parentlist numcommits rowoffsets displayorder
+    global rowidlist parentlist numcommits displayorder
 
     set cross {}
     set ccross {}
@@ -3757,12 +3882,9 @@ proc findcrossings {id} {
            set e [expr {$numcommits - 1}]
        }
        if {$e <= $s} continue
-       set x [lsearch -exact [lindex $rowidlist $e] $id]
-       if {$x < 0} {
-           puts "findcrossings: oops, no [shortids $id] in row $e"
-           continue
-       }
        for {set row $e} {[incr row -1] >= $s} {} {
+           set x [lsearch -exact [lindex $rowidlist $row] $id]
+           if {$x < 0} break
            set olds [lindex $parentlist $row]
            set kid [lindex $displayorder $row]
            set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
@@ -3780,9 +3902,6 @@ proc findcrossings {id} {
                    }
                }
            }
-           set inc [lindex $rowoffsets $row $x]
-           if {$inc eq {}} break
-           incr x $inc
        }
     }
     return [concat $ccross {{}} $cross]
@@ -3863,7 +3982,7 @@ proc bindline {t id} {
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs mainhead
     global linespc lthickness
-    global canv mainfont commitrow rowtextx curview fgcolor bgcolor
+    global canv commitrow rowtextx curview fgcolor bgcolor
 
     set marks {}
     set ntags 0
@@ -3892,9 +4011,9 @@ proc drawtags {id x xt y1} {
     foreach tag $marks {
        incr i
        if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
-           set wid [font measure [concat $mainfont bold] $tag]
+           set wid [font measure mainfontbold $tag]
        } else {
-           set wid [font measure $mainfont $tag]
+           set wid [font measure mainfont $tag]
        }
        lappend xvals $xt
        lappend wvals $wid
@@ -3906,7 +4025,7 @@ proc drawtags {id x xt y1} {
     foreach tag $marks x $xvals wid $wvals {
        set xl [expr {$x + $delta}]
        set xr [expr {$x + $delta + $wid + $lthickness}]
-       set font $mainfont
+       set font mainfont
        if {[incr ntags -1] >= 0} {
            # draw a tag
            set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
@@ -3919,7 +4038,7 @@ proc drawtags {id x xt y1} {
            if {[incr nheads -1] >= 0} {
                set col green
                if {$tag eq $mainhead} {
-                   lappend font bold
+                   set font mainfontbold
                }
            } else {
                set col "#ddddff"
@@ -3928,7 +4047,7 @@ proc drawtags {id x xt y1} {
            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
                -width 1 -outline black -fill $col -tags tag.$id
            if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
-               set rwid [font measure $mainfont $remoteprefix]
+               set rwid [font measure mainfont $remoteprefix]
                set xi [expr {$x + 1}]
                set yti [expr {$yt + 1}]
                set xri [expr {$x + $rwid}]
@@ -3960,10 +4079,10 @@ proc xcoord {i level ln} {
 }
 
 proc show_status {msg} {
-    global canv mainfont fgcolor
+    global canv fgcolor
 
     clear_display
-    $canv create text 3 3 -anchor nw -text $msg -font $mainfont \
+    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
        -tags text -fill $fgcolor
 }
 
@@ -3972,9 +4091,9 @@ proc show_status {msg} {
 # on that row and below will move down one row.
 proc insertrow {row newcmit} {
     global displayorder parentlist commitlisted children
-    global commitrow curview rowidlist rowoffsets numcommits
-    global rowrangelist rowlaidout rowoptim numcommits
-    global selectedline rowchk commitidx
+    global commitrow curview rowidlist rowisopt rowfinal numcommits
+    global numcommits
+    global selectedline commitidx ordertok
 
     if {$row >= $numcommits} {
        puts "oops, inserting new row $row but only have $numcommits rows"
@@ -3994,45 +4113,24 @@ proc insertrow {row newcmit} {
        set commitrow($curview,$id) $r
     }
     incr commitidx($curview)
+    set ordertok($curview,$newcmit) $ordertok($curview,$p)
 
-    set idlist [lindex $rowidlist $row]
-    set offs [lindex $rowoffsets $row]
-    set newoffs {}
-    foreach x $idlist {
-       if {$x eq {} || ($x eq $p && [llength $kids] == 1)} {
-           lappend newoffs {}
-       } else {
-           lappend newoffs 0
-       }
-    }
-    if {[llength $kids] == 1} {
-       set col [lsearch -exact $idlist $p]
-       lset idlist $col $newcmit
-    } else {
-       set col [llength $idlist]
-       lappend idlist $newcmit
-       lappend offs {}
-       lset rowoffsets $row $offs
-    }
-    set rowidlist [linsert $rowidlist $row $idlist]
-    set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
-
-    set rowrangelist [linsert $rowrangelist $row {}]
-    if {[llength $kids] > 1} {
-       set rp1 [expr {$row + 1}]
-       set ranges [lindex $rowrangelist $rp1]
-       if {$ranges eq {}} {
-           set ranges [list $newcmit $p]
-       } elseif {[lindex $ranges end-1] eq $p} {
-           lset ranges end-1 $newcmit
+    if {$row < [llength $rowidlist]} {
+       set idlist [lindex $rowidlist $row]
+       if {$idlist ne {}} {
+           if {[llength $kids] == 1} {
+               set col [lsearch -exact $idlist $p]
+               lset idlist $col $newcmit
+           } else {
+               set col [llength $idlist]
+               lappend idlist $newcmit
+           }
        }
-       lset rowrangelist $rp1 $ranges
+       set rowidlist [linsert $rowidlist $row $idlist]
+       set rowisopt [linsert $rowisopt $row 0]
+       set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]]
     }
 
-    catch {unset rowchk}
-
-    incr rowlaidout
-    incr rowoptim
     incr numcommits
 
     if {[info exists selectedline] && $selectedline >= $row} {
@@ -4044,9 +4142,9 @@ proc insertrow {row newcmit} {
 # Remove a commit that was inserted with insertrow on row $row.
 proc removerow {row} {
     global displayorder parentlist commitlisted children
-    global commitrow curview rowidlist rowoffsets numcommits
-    global rowrangelist idrowranges rowlaidout rowoptim numcommits
-    global linesegends selectedline rowchk commitidx
+    global commitrow curview rowidlist rowisopt rowfinal numcommits
+    global numcommits
+    global linesegends selectedline commitidx
 
     if {$row >= $numcommits} {
        puts "oops, removing row $row but only have $numcommits rows"
@@ -4071,27 +4169,12 @@ proc removerow {row} {
     }
     incr commitidx($curview) -1
 
-    set rowidlist [lreplace $rowidlist $row $row]
-    set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
-    if {$kids ne {}} {
-       set offs [lindex $rowoffsets $row]
-       set offs [lreplace $offs end end]
-       lset rowoffsets $row $offs
-    }
-
-    set rowrangelist [lreplace $rowrangelist $row $row]
-    if {[llength $kids] > 0} {
-       set ranges [lindex $rowrangelist $row]
-       if {[lindex $ranges end-1] eq $id} {
-           set ranges [lreplace $ranges end-1 end]
-           lset rowrangelist $row $ranges
-       }
+    if {$row < [llength $rowidlist]} {
+       set rowidlist [lreplace $rowidlist $row $row]
+       set rowisopt [lreplace $rowisopt $row $row]
+       set rowfinal [lreplace $rowfinal $row $row]
     }
 
-    catch {unset rowchk}
-
-    incr rowlaidout -1
-    incr rowoptim -1
     incr numcommits -1
 
     if {[info exists selectedline] && $selectedline > $row} {
@@ -4111,20 +4194,30 @@ proc settextcursor {c} {
     set curtextcursor $c
 }
 
-proc nowbusy {what} {
-    global isbusy
+proc nowbusy {what {name {}}} {
+    global isbusy busyname statusw
 
     if {[array names isbusy] eq {}} {
        . config -cursor watch
        settextcursor watch
     }
     set isbusy($what) 1
+    set busyname($what) $name
+    if {$name ne {}} {
+       $statusw conf -text $name
+    }
 }
 
 proc notbusy {what} {
-    global isbusy maincursor textcursor
+    global isbusy maincursor textcursor busyname statusw
 
-    catch {unset isbusy($what)}
+    catch {
+       unset isbusy($what)
+       if {$busyname($what) ne {} &&
+           [$statusw cget -text] eq $busyname($what)} {
+           $statusw conf -text {}
+       }
+    }
     if {[array names isbusy] eq {}} {
        . config -cursor $maincursor
        settextcursor $textcursor
@@ -4152,148 +4245,149 @@ proc findmatches {f} {
     return $matches
 }
 
-proc dofind {{rev 0}} {
+proc dofind {{dirn 1} {wrap 1}} {
     global findstring findstartline findcurline selectedline numcommits
+    global gdttype filehighlight fh_serial find_dirn findallowwrap
 
-    unmarkmatches
-    cancel_next_highlight
+    if {[info exists find_dirn]} {
+       if {$find_dirn == $dirn} return
+       stopfinding
+    }
     focus .
     if {$findstring eq {} || $numcommits == 0} return
     if {![info exists selectedline]} {
-       set findstartline [lindex [visiblerows] $rev]
+       set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
     } else {
        set findstartline $selectedline
     }
     set findcurline $findstartline
-    nowbusy finding
-    if {!$rev} {
-       run findmore
-    } else {
-       if {$findcurline == 0} {
-           set findcurline $numcommits
-       }
-       incr findcurline -1
-       run findmorerev
-    }
-}
-
-proc findnext {restart} {
-    global findcurline
-    if {![info exists findcurline]} {
-       if {$restart} {
-           dofind
-       } else {
-           bell
-       }
-    } else {
-       run findmore
-       nowbusy finding
-    }
-}
-
-proc findprev {} {
-    global findcurline
-    if {![info exists findcurline]} {
-       dofind 1
-    } else {
-       run findmorerev
-       nowbusy finding
+    nowbusy finding "Searching"
+    if {$gdttype ne "containing:" && ![info exists filehighlight]} {
+       after cancel do_file_hl $fh_serial
+       do_file_hl $fh_serial
     }
+    set find_dirn $dirn
+    set findallowwrap $wrap
+    run findmore
 }
 
-proc findmore {} {
-    global commitdata commitinfo numcommits findstring findpattern findloc
-    global findstartline findcurline displayorder
+proc stopfinding {} {
+    global find_dirn findcurline fprogcoord
 
-    set fldtypes {Headline Author Date Committer CDate Comments}
-    set l [expr {$findcurline + 1}]
-    if {$l >= $numcommits} {
-       set l 0
-    }
-    if {$l <= $findstartline} {
-       set lim [expr {$findstartline + 1}]
-    } else {
-       set lim $numcommits
-    }
-    if {$lim - $l > 500} {
-       set lim [expr {$l + 500}]
-    }
-    set last 0
-    for {} {$l < $lim} {incr l} {
-       set id [lindex $displayorder $l]
-       # shouldn't happen unless git log doesn't give all the commits...
-       if {![info exists commitdata($id)]} continue
-       if {![doesmatch $commitdata($id)]} continue
-       if {![info exists commitinfo($id)]} {
-           getcommit $id
-       }
-       set info $commitinfo($id)
-       foreach f $info ty $fldtypes {
-           if {($findloc eq "All fields" || $findloc eq $ty) &&
-               [doesmatch $f]} {
-               findselectline $l
-               notbusy finding
-               return 0
-           }
-       }
-    }
-    if {$l == $findstartline + 1} {
-       bell
+    if {[info exists find_dirn]} {
+       unset find_dirn
        unset findcurline
        notbusy finding
-       return 0
+       set fprogcoord 0
+       adjustprogress
     }
-    set findcurline [expr {$l - 1}]
-    return 1
 }
 
-proc findmorerev {} {
-    global commitdata commitinfo numcommits findstring findpattern findloc
+proc findmore {} {
+    global commitdata commitinfo numcommits findpattern findloc
     global findstartline findcurline displayorder
+    global find_dirn gdttype fhighlights fprogcoord
+    global findallowwrap
 
+    if {![info exists find_dirn]} {
+       return 0
+    }
     set fldtypes {Headline Author Date Committer CDate Comments}
     set l $findcurline
-    if {$l == 0} {
-       set l $numcommits
-    }
-    incr l -1
-    if {$l >= $findstartline} {
-       set lim [expr {$findstartline - 1}]
+    set moretodo 0
+    if {$find_dirn > 0} {
+       incr l
+       if {$l >= $numcommits} {
+           set l 0
+       }
+       if {$l <= $findstartline} {
+           set lim [expr {$findstartline + 1}]
+       } else {
+           set lim $numcommits
+           set moretodo $findallowwrap
+       }
     } else {
-       set lim -1
-    }
-    if {$l - $lim > 500} {
-       set lim [expr {$l - 500}]
-    }
-    set last 0
-    for {} {$l > $lim} {incr l -1} {
-       set id [lindex $displayorder $l]
-       if {![doesmatch $commitdata($id)]} continue
-       if {![info exists commitinfo($id)]} {
-           getcommit $id
+       if {$l == 0} {
+           set l $numcommits
+       }
+       incr l -1
+       if {$l >= $findstartline} {
+           set lim [expr {$findstartline - 1}]
+       } else {
+           set lim -1
+           set moretodo $findallowwrap
+       }
+    }
+    set n [expr {($lim - $l) * $find_dirn}]
+    if {$n > 500} {
+       set n 500
+       set moretodo 1
+    }
+    set found 0
+    set domore 1
+    if {$gdttype eq "containing:"} {
+       for {} {$n > 0} {incr n -1; incr l $find_dirn} {
+           set id [lindex $displayorder $l]
+           # shouldn't happen unless git log doesn't give all the commits...
+           if {![info exists commitdata($id)]} continue
+           if {![doesmatch $commitdata($id)]} continue
+           if {![info exists commitinfo($id)]} {
+               getcommit $id
+           }
+           set info $commitinfo($id)
+           foreach f $info ty $fldtypes {
+               if {($findloc eq "All fields" || $findloc eq $ty) &&
+                   [doesmatch $f]} {
+                   set found 1
+                   break
+               }
+           }
+           if {$found} break
        }
-       set info $commitinfo($id)
-       foreach f $info ty $fldtypes {
-           if {($findloc eq "All fields" || $findloc eq $ty) &&
-               [doesmatch $f]} {
-               findselectline $l
-               notbusy finding
-               return 0
+    } else {
+       for {} {$n > 0} {incr n -1; incr l $find_dirn} {
+           set id [lindex $displayorder $l]
+           if {![info exists fhighlights($l)]} {
+               askfilehighlight $l $id
+               if {$domore} {
+                   set domore 0
+                   set findcurline [expr {$l - $find_dirn}]
+               }
+           } elseif {$fhighlights($l)} {
+               set found $domore
+               break
            }
        }
     }
-    if {$l == -1} {
-       bell
+    if {$found || ($domore && !$moretodo)} {
        unset findcurline
+       unset find_dirn
        notbusy finding
+       set fprogcoord 0
+       adjustprogress
+       if {$found} {
+           findselectline $l
+       } else {
+           bell
+       }
        return 0
     }
-    set findcurline [expr {$l + 1}]
-    return 1
+    if {!$domore} {
+       flushhighlights
+    } else {
+       set findcurline [expr {$l - $find_dirn}]
+    }
+    set n [expr {($findcurline - $findstartline) * $find_dirn - 1}]
+    if {$n < 0} {
+       incr n $numcommits
+    }
+    set fprogcoord [expr {$n * 1.0 / $numcommits}]
+    adjustprogress
+    return $domore
 }
 
 proc findselectline {l} {
-    global findloc commentend ctext findcurline markingmatches
+    global findloc commentend ctext findcurline markingmatches gdttype
 
     set markingmatches 1
     set findcurline $l
@@ -4336,12 +4430,11 @@ proc markmatches {canv l str tag matches font row} {
 }
 
 proc unmarkmatches {} {
-    global findids markingmatches findcurline
+    global markingmatches
 
     allcanvs delete matches
-    catch {unset findids}
     set markingmatches 0
-    catch {unset findcurline}
+    stopfinding
 }
 
 proc selcanvline {w x y} {
@@ -4377,7 +4470,7 @@ proc commit_descriptor {p} {
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
 proc appendwithlinks {text tags} {
-    global ctext commitrow linknum curview
+    global ctext commitrow linknum curview pendinglinks
 
     set start [$ctext index "end - 1c"]
     $ctext insert end $text $tags
@@ -4386,17 +4479,49 @@ proc appendwithlinks {text tags} {
        set s [lindex $l 0]
        set e [lindex $l 1]
        set linkid [string range $text $s $e]
-       if {![info exists commitrow($curview,$linkid)]} continue
        incr e
-       $ctext tag add link "$start + $s c" "$start + $e c"
+       $ctext tag delete link$linknum
        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
-       $ctext tag bind link$linknum <1> \
-           [list selectline $commitrow($curview,$linkid) 1]
+       setlink $linkid link$linknum
        incr linknum
     }
-    $ctext tag conf link -foreground blue -underline 1
-    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
-    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+}
+
+proc setlink {id lk} {
+    global curview commitrow ctext pendinglinks commitinterest
+
+    if {[info exists commitrow($curview,$id)]} {
+       $ctext tag conf $lk -foreground blue -underline 1
+       $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1]
+       $ctext tag bind $lk <Enter> {linkcursor %W 1}
+       $ctext tag bind $lk <Leave> {linkcursor %W -1}
+    } else {
+       lappend pendinglinks($id) $lk
+       lappend commitinterest($id) {makelink %I}
+    }
+}
+
+proc makelink {id} {
+    global pendinglinks
+
+    if {![info exists pendinglinks($id)]} return
+    foreach lk $pendinglinks($id) {
+       setlink $id $lk
+    }
+    unset pendinglinks($id)
+}
+
+proc linkcursor {w inc} {
+    global linkentercount curtextcursor
+
+    if {[incr linkentercount $inc] > 0} {
+       $w configure -cursor hand2
+    } else {
+       $w configure -cursor $curtextcursor
+       if {$linkentercount < 0} {
+           set linkentercount 0
+       }
+    }
 }
 
 proc viewnextline {dir} {
@@ -4443,15 +4568,7 @@ proc appendrefs {pos ids var} {
            $ctext tag delete $lk
            $ctext insert $pos $sep
            $ctext insert $pos [lindex $ti 0] $lk
-           if {[info exists commitrow($curview,$id)]} {
-               $ctext tag conf $lk -foreground blue
-               $ctext tag bind $lk <1> \
-                   [list selectline $commitrow($curview,$id) 1]
-               $ctext tag conf $lk -underline 1
-               $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
-               $ctext tag bind $lk <Leave> \
-                   { %W configure -cursor $curtextcursor }
-           }
+           setlink $id $lk
            set sep ", "
        }
     }
@@ -4509,9 +4626,27 @@ proc dispnexttag {} {
     }
 }
 
+proc make_secsel {l} {
+    global linehtag linentag linedtag canv canv2 canv3
+
+    if {![info exists linehtag($l)]} return
+    $canv delete secsel
+    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
+              -tags secsel -fill [$canv cget -selectbackground]]
+    $canv lower $t
+    $canv2 delete secsel
+    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
+              -tags secsel -fill [$canv2 cget -selectbackground]]
+    $canv2 lower $t
+    $canv3 delete secsel
+    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
+              -tags secsel -fill [$canv3 cget -selectbackground]]
+    $canv3 lower $t
+}
+
 proc selectline {l isnew} {
-    global canv canv2 canv3 ctext commitinfo selectedline
-    global displayorder linehtag linentag linedtag
+    global canv ctext commitinfo selectedline
+    global displayorder
     global canvy0 linespc parentlist children curview
     global currentid sha1entry
     global commentend idtags linknum
@@ -4521,8 +4656,8 @@ proc selectline {l isnew} {
     catch {unset pending_select}
     $canv delete hover
     normalline
-    cancel_next_highlight
     unsel_reflist
+    stopfinding
     if {$l < 0 || $l >= $numcommits} return
     set y [expr {$canvy0 + $l * $linespc}]
     set ymax [lindex [$canv cget -scrollregion] 3]
@@ -4560,19 +4695,7 @@ proc selectline {l isnew} {
        drawvisible
     }
 
-    if {![info exists linehtag($l)]} return
-    $canv delete secsel
-    set t [eval $canv create rect [$canv bbox $linehtag($l)] -outline {{}} \
-              -tags secsel -fill [$canv cget -selectbackground]]
-    $canv lower $t
-    $canv2 delete secsel
-    set t [eval $canv2 create rect [$canv2 bbox $linentag($l)] -outline {{}} \
-              -tags secsel -fill [$canv2 cget -selectbackground]]
-    $canv2 lower $t
-    $canv3 delete secsel
-    set t [eval $canv3 create rect [$canv3 bbox $linedtag($l)] -outline {{}} \
-              -tags secsel -fill [$canv3 cget -selectbackground]]
-    $canv3 lower $t
+    make_secsel $l
 
     if {$isnew} {
        addtohistory [list selectline $l 0]
@@ -4715,7 +4838,6 @@ proc unselectline {} {
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
-    cancel_next_highlight
 }
 
 proc reselectline {} {
@@ -4884,6 +5006,7 @@ proc showfile {f} {
     $ctext insert end "$f\n" filesep
     $ctext config -state disabled
     $ctext yview $commentend
+    settabs 0
 }
 
 proc getblobline {bf id} {
@@ -4909,15 +5032,18 @@ proc getblobline {bf id} {
 }
 
 proc mergediff {id l} {
-    global diffmergeid diffopts mdifffd
+    global diffmergeid mdifffd
     global diffids
     global parentlist
+    global limitdiffs viewfiles curview
 
     set diffmergeid $id
     set diffids $id
     # this doesn't seem to actually affect anything...
-    set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git diff-tree --no-commit-id --cc $id]
+    if {$limitdiffs && $viewfiles($curview) ne {}} {
+       set cmd [concat $cmd -- $viewfiles($curview)]
+    }
     if {[catch {set mdf [open $cmd r]} err]} {
        error_popup "Error getting merge diffs: $err"
        return
@@ -4925,6 +5051,7 @@ proc mergediff {id l} {
     fconfigure $mdf -blocking 0
     set mdifffd($id) $mdf
     set np [llength [lindex $parentlist $l]]
+    settabs $np
     filerun $mdf [list getmergediffline $mdf $id $np]
 }
 
@@ -5002,6 +5129,7 @@ proc getmergediffline {mdf id np} {
 proc startdiff {ids} {
     global treediffs diffids treepending diffmergeid nullid nullid2
 
+    settabs 1
     set diffids $ids
     catch {unset diffmergeid}
     if {![info exists treediffs($ids)] ||
@@ -5015,8 +5143,27 @@ proc startdiff {ids} {
     }
 }
 
+proc path_filter {filter name} {
+    foreach p $filter {
+       set l [string length $p]
+       if {[string index $p end] eq "/"} {
+           if {[string compare -length $l $p $name] == 0} {
+               return 1
+           }
+       } else {
+           if {[string compare -length $l $p $name] == 0 &&
+               ([string length $name] == $l ||
+                [string index $name $l] eq "/")} {
+               return 1
+           }
+       }
+    }
+    return 0
+}
+
 proc addtocflist {ids} {
-    global treediffs cflist
+    global treediffs
+
     add_flist $treediffs($ids)
     getblobdiffs $ids
 }
@@ -5073,7 +5220,7 @@ proc gettreediffs {ids} {
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
-    global cmitmode
+    global cmitmode viewfiles curview limitdiffs
 
     set nr 0
     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
@@ -5090,7 +5237,17 @@ proc gettreediffline {gdtf ids} {
        return [expr {$nr >= 1000? 2: 1}]
     }
     close $gdtf
-    set treediffs($ids) $treediff
+    if {$limitdiffs && $viewfiles($curview) ne {}} {
+       set flist {}
+       foreach f $treediff {
+           if {[path_filter $viewfiles($curview) $f]} {
+               lappend flist $f
+           }
+       }
+       set treediffs($ids) $flist
+    } else {
+       set treediffs($ids) $treediff
+    }
     unset treepending
     if {$cmitmode eq "tree"} {
        gettree $diffids
@@ -5121,12 +5278,16 @@ proc diffcontextchange {n1 n2 op} {
 }
 
 proc getblobdiffs {ids} {
-    global diffopts blobdifffd diffids env
+    global blobdifffd diffids env
     global diffinhdr treediffs
     global diffcontext
+    global limitdiffs viewfiles curview
 
-    set env(GIT_DIFF_OPTS) $diffopts
-    if {[catch {set bdf [open [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"] r]} err]} {
+    set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
+    if {$limitdiffs && $viewfiles($curview) ne {}} {
+       set cmd [concat $cmd -- $viewfiles($curview)]
+    }
+    if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
        return
     }
@@ -5291,6 +5452,7 @@ proc nextfile {} {
 
 proc clear_ctext {{first 1.0}} {
     global ctext smarktop smarkbot
+    global pendinglinks
 
     set l [lindex [split $first .] 0]
     if {![info exists smarktop] || [$ctext compare $first < $smarktop.0]} {
@@ -5300,6 +5462,26 @@ proc clear_ctext {{first 1.0}} {
        set smarkbot $l
     }
     $ctext delete $first end
+    if {$first eq "1.0"} {
+       catch {unset pendinglinks}
+    }
+}
+
+proc settabs {{firstab {}}} {
+    global firsttabstop tabstop ctext have_tk85
+
+    if {$firstab ne {} && $have_tk85} {
+       set firsttabstop $firstab
+    }
+    set w [font measure textfont "0"]
+    if {$firsttabstop != 0} {
+       $ctext conf -tabs [list [expr {($firsttabstop + $tabstop) * $w}] \
+                              [expr {($firsttabstop + 2 * $tabstop) * $w}]]
+    } elseif {$have_tk85 || $tabstop != 8} {
+       $ctext conf -tabs [expr {$tabstop * $w}]
+    } else {
+       $ctext conf -tabs {}
+    }
 }
 
 proc incrsearch {name ix op} {
@@ -5422,11 +5604,11 @@ proc scrolltext {f0 f1} {
 }
 
 proc setcoords {} {
-    global linespc charspc canvx0 canvy0 mainfont
+    global linespc charspc canvx0 canvy0
     global xspc1 xspc2 lthickness
 
-    set linespc [font metrics $mainfont -linespace]
-    set charspc [font measure $mainfont "m"]
+    set linespc [font metrics mainfont -linespace]
+    set charspc [font measure mainfont "m"]
     set canvy0 [expr {int(3 + 0.5 * $linespc)}]
     set canvx0 [expr {int(3 + 0.5 * $linespc)}]
     set lthickness [expr {int($linespc / 9) + 1}]
@@ -5451,26 +5633,75 @@ proc redisplay {} {
     }
 }
 
+proc parsefont {f n} {
+    global fontattr
+
+    set fontattr($f,family) [lindex $n 0]
+    set s [lindex $n 1]
+    if {$s eq {} || $s == 0} {
+       set s 10
+    } elseif {$s < 0} {
+       set s [expr {int(-$s / [winfo fpixels . 1p] + 0.5)}]
+    }
+    set fontattr($f,size) $s
+    set fontattr($f,weight) normal
+    set fontattr($f,slant) roman
+    foreach style [lrange $n 2 end] {
+       switch -- $style {
+           "normal" -
+           "bold"   {set fontattr($f,weight) $style}
+           "roman" -
+           "italic" {set fontattr($f,slant) $style}
+       }
+    }
+}
+
+proc fontflags {f {isbold 0}} {
+    global fontattr
+
+    return [list -family $fontattr($f,family) -size $fontattr($f,size) \
+               -weight [expr {$isbold? "bold": $fontattr($f,weight)}] \
+               -slant $fontattr($f,slant)]
+}
+
+proc fontname {f} {
+    global fontattr
+
+    set n [list $fontattr($f,family) $fontattr($f,size)]
+    if {$fontattr($f,weight) eq "bold"} {
+       lappend n "bold"
+    }
+    if {$fontattr($f,slant) eq "italic"} {
+       lappend n "italic"
+    }
+    return $n
+}
+
 proc incrfont {inc} {
     global mainfont textfont ctext canv phase cflist showrefstop
-    global charspc tabstop
-    global stopped entries
+    global stopped entries fontattr
+
     unmarkmatches
-    set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
-    set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
+    set s $fontattr(mainfont,size)
+    incr s $inc
+    if {$s < 1} {
+       set s 1
+    }
+    set fontattr(mainfont,size) $s
+    font config mainfont -size $s
+    font config mainfontbold -size $s
+    set mainfont [fontname mainfont]
+    set s $fontattr(textfont,size)
+    incr s $inc
+    if {$s < 1} {
+       set s 1
+    }
+    set fontattr(textfont,size) $s
+    font config textfont -size $s
+    font config textfontbold -size $s
+    set textfont [fontname textfont]
     setcoords
-    $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
-    $cflist conf -font $textfont
-    $ctext tag conf filesep -font [concat $textfont bold]
-    foreach e $entries {
-       $e conf -font $mainfont
-    }
-    if {$phase eq "getcommits"} {
-       $canv itemconf textitems -font $mainfont
-    }
-    if {[info exists showrefstop] && [winfo exists $showrefstop]} {
-       $showrefstop.list conf -font $mainfont
-    }
+    settabs
     redisplay
 }
 
@@ -5581,7 +5812,7 @@ proc lineleave {id} {
 proc linehover {} {
     global hoverx hovery hoverid hovertimer
     global canv linespc lthickness
-    global commitinfo mainfont
+    global commitinfo
 
     set text [lindex $commitinfo($hoverid) 0]
     set ymax [lindex [$canv cget -scrollregion] 3]
@@ -5591,13 +5822,13 @@ proc linehover {} {
     set y [expr {$hovery + $yfrac * $ymax - $linespc / 2}]
     set x0 [expr {$x - 2 * $lthickness}]
     set y0 [expr {$y - 2 * $lthickness}]
-    set x1 [expr {$x + [font measure $mainfont $text] + 2 * $lthickness}]
+    set x1 [expr {$x + [font measure mainfont $text] + 2 * $lthickness}]
     set y1 [expr {$y + $linespc + 2 * $lthickness}]
     set t [$canv create rectangle $x0 $y0 $x1 $y1 \
               -fill \#ffff80 -outline black -width 1 -tags hover]
     $canv raise $t
     set t [$canv create text $x $y -anchor nw -text $text -tags hover \
-              -font $mainfont]
+              -font mainfont]
     $canv raise $t
 }
 
@@ -5635,7 +5866,7 @@ proc arrowjump {id n y} {
 }
 
 proc lineclick {x y id isnew} {
-    global ctext commitinfo children canv thickerline curview
+    global ctext commitinfo children canv thickerline curview commitrow
 
     if {![info exists commitinfo($id)] && ![getcommit $id]} return
     unmarkmatches
@@ -5663,12 +5894,10 @@ proc lineclick {x y id isnew} {
     # fill the details pane with info about this line
     $ctext conf -state normal
     clear_ctext
-    $ctext tag conf link -foreground blue -underline 1
-    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
-    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
+    settabs 0
     $ctext insert end "Parent:\t"
-    $ctext insert end $id [list link link0]
-    $ctext tag bind link0 <1> [list selbyid $id]
+    $ctext insert end $id link0
+    setlink $id link0
     set info $commitinfo($id)
     $ctext insert end "\n\t[lindex $info 0]\n"
     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
@@ -5683,8 +5912,8 @@ proc lineclick {x y id isnew} {
            if {![info exists commitinfo($child)] && ![getcommit $child]} continue
            set info $commitinfo($child)
            $ctext insert end "\n\t"
-           $ctext insert end $child [list link link$i]
-           $ctext tag bind link$i <1> [list selbyid $child]
+           $ctext insert end $child link$i
+           setlink $child link$i
            $ctext insert end "\n\t[lindex $info 0]"
            $ctext insert end "\n\tAuthor:\t[lindex $info 1]"
            set date [formatdate [lindex $info 2]]
@@ -5723,6 +5952,7 @@ proc rowmenu {x y id} {
     global rowctxmenu commitrow selectedline rowmenuid curview
     global nullid nullid2 fakerowmenu mainhead
 
+    stopfinding
     set rowmenuid $id
     if {![info exists selectedline]
        || $commitrow($curview,$id) eq $selectedline} {
@@ -5765,16 +5995,13 @@ proc doseldiff {oldid newid} {
     clear_ctext
     init_flist "Top"
     $ctext insert end "From "
-    $ctext tag conf link -foreground blue -underline 1
-    $ctext tag bind link <Enter> { %W configure -cursor hand2 }
-    $ctext tag bind link <Leave> { %W configure -cursor $curtextcursor }
-    $ctext tag bind link0 <1> [list selbyid $oldid]
-    $ctext insert end $oldid [list link link0]
+    $ctext insert end $oldid link0
+    setlink $oldid link0
     $ctext insert end "\n     "
     $ctext insert end [lindex $commitinfo($oldid) 0]
     $ctext insert end "\n\nTo   "
-    $ctext tag bind link1 <1> [list selbyid $newid]
-    $ctext insert end $newid [list link link1]
+    $ctext insert end $newid link1
+    setlink $newid link1
     $ctext insert end "\n     "
     $ctext insert end [lindex $commitinfo($newid) 0]
     $ctext insert end "\n"
@@ -5855,6 +6082,8 @@ proc mkpatchgo {} {
     set newid [$patchtop.tosha1 get]
     set fname [$patchtop.fname get]
     set cmd [diffcmd [list $oldid $newid] -p]
+    # trim off the initial "|"
+    set cmd [lrange $cmd 1 end]
     lappend cmd >$fname &
     if {[catch {eval exec $cmd} err]} {
        error_popup "Error creating patch: $err"
@@ -5935,7 +6164,7 @@ proc domktag {} {
 
 proc redrawtags {id} {
     global canv linehtag commitrow idpos selectedline curview
-    global mainfont canvxmax iddrawn
+    global canvxmax iddrawn
 
     if {![info exists commitrow($curview,$id)]} return
     if {![info exists iddrawn($id)]} return
@@ -5944,7 +6173,7 @@ proc redrawtags {id} {
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
     set text [$canv itemcget $linehtag($commitrow($curview,$id)) -text]
-    set xr [expr {$xt + [font measure $mainfont $text]}]
+    set xr [expr {$xt + [font measure mainfont $text]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
        setcanvscroll
@@ -6087,7 +6316,7 @@ proc cherrypick {} {
                        included in branch $mainhead -- really re-apply it?"]
        if {!$ok} return
     }
-    nowbusy cherrypick
+    nowbusy cherrypick "Cherry-picking"
     update
     # Unfortunately git-cherry-pick writes stuff to stderr even when
     # no error occurs, and exec takes that as an indication of error...
@@ -6117,7 +6346,6 @@ proc cherrypick {} {
 
 proc resethead {} {
     global mainheadid mainhead rowmenuid confirm_ok resettype
-    global showlocalchanges
 
     set confirm_ok 0
     set w ".confirmreset"
@@ -6154,32 +6382,23 @@ proc resethead {} {
        error_popup $err
     } else {
        dohidelocalchanges
-       set w ".resetprogress"
-       filerun $fd [list readresetstat $fd $w]
-       toplevel $w
-       wm transient $w
-       wm title $w "Reset progress"
-       message $w.m -text "Reset in progress, please wait..." \
-           -justify center -aspect 1000
-       pack $w.m -side top -fill x -padx 20 -pady 5
-       canvas $w.c -width 150 -height 20 -bg white
-       $w.c create rect 0 0 0 20 -fill green -tags rect
-       pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
-       nowbusy reset
+       filerun $fd [list readresetstat $fd]
+       nowbusy reset "Resetting"
     }
 }
 
-proc readresetstat {fd w} {
-    global mainhead mainheadid showlocalchanges
+proc readresetstat {fd} {
+    global mainhead mainheadid showlocalchanges rprogcoord
 
     if {[gets $fd line] >= 0} {
        if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
-           set x [expr {($m * 150) / $n}]
-           $w.c coords rect 0 0 $x 20
+           set rprogcoord [expr {1.0 * $m / $n}]
+           adjustprogress
        }
        return 1
     }
-    destroy $w
+    set rprogcoord 0
+    adjustprogress
     notbusy reset
     if {[catch {close $fd} err]} {
        error_popup $err
@@ -6203,6 +6422,7 @@ proc readresetstat {fd w} {
 proc headmenu {x y id head} {
     global headmenuid headmenuhead headctxmenu mainhead
 
+    stopfinding
     set headmenuid $id
     set headmenuhead $head
     set state normal
@@ -6220,7 +6440,7 @@ proc cobranch {} {
 
     # check the tree is clean first??
     set oldmainhead $mainhead
-    nowbusy checkout
+    nowbusy checkout "Checking out"
     update
     dohidelocalchanges
     if {[catch {
@@ -6276,8 +6496,8 @@ proc rmbranch {} {
 
 # Display a list of tags and heads
 proc showrefs {} {
-    global showrefstop bgcolor fgcolor selectbgcolor mainfont
-    global bglist fglist uifont reflistfilter reflist maincursor
+    global showrefstop bgcolor fgcolor selectbgcolor
+    global bglist fglist reflistfilter reflist maincursor
 
     set top .showrefs
     set showrefstop $top
@@ -6289,7 +6509,7 @@ proc showrefs {} {
     toplevel $top
     wm title $top "Tags and heads: [file tail [pwd]]"
     text $top.list -background $bgcolor -foreground $fgcolor \
-       -selectbackground $selectbgcolor -font $mainfont \
+       -selectbackground $selectbgcolor -font mainfont \
        -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
        -width 30 -height 20 -cursor $maincursor \
        -spacing1 1 -spacing3 1 -state disabled
@@ -6301,15 +6521,15 @@ proc showrefs {} {
     grid $top.list $top.ysb -sticky nsew
     grid $top.xsb x -sticky ew
     frame $top.f
-    label $top.f.l -text "Filter: " -font $uifont
-    entry $top.f.e -width 20 -textvariable reflistfilter -font $uifont
+    label $top.f.l -text "Filter: " -font uifont
+    entry $top.f.e -width 20 -textvariable reflistfilter -font uifont
     set reflistfilter "*"
     trace add variable reflistfilter write reflistfilter_change
     pack $top.f.e -side right -fill x -expand 1
     pack $top.f.l -side left
     grid $top.f - -sticky ew -pady 2
     button $top.close -command [list destroy $top] -text "Close" \
-       -font $uifont
+       -font uifont
     grid $top.close -
     grid columnconfigure $top 0 -weight 1
     grid rowconfigure $top 0 -weight 1
@@ -6432,25 +6652,59 @@ proc refill_reflist {} {
 
 # Stuff for finding nearby tags
 proc getallcommits {} {
-    global allcommits allids nbmp nextarc seeds
+    global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
+    global idheads idtags idotherrefs allparents tagobjid
 
     if {![info exists allcommits]} {
-       set allids {}
-       set nbmp 0
        set nextarc 0
        set allcommits 0
        set seeds {}
+       set allcwait 0
+       set cachedarcs 0
+       set allccache [file join [gitdir] "gitk.cache"]
+       if {![catch {
+           set f [open $allccache r]
+           set allcwait 1
+           getcache $f
+       }]} return
     }
 
-    set cmd [concat | git rev-list --all --parents]
-    foreach id $seeds {
-       lappend cmd "^$id"
+    if {$allcwait} {
+       return
+    }
+    set cmd [list | git rev-list --parents]
+    set allcupdate [expr {$seeds ne {}}]
+    if {!$allcupdate} {
+       set ids "--all"
+    } else {
+       set refs [concat [array names idheads] [array names idtags] \
+                     [array names idotherrefs]]
+       set ids {}
+       set tagobjs {}
+       foreach name [array names tagobjid] {
+           lappend tagobjs $tagobjid($name)
+       }
+       foreach id [lsort -unique $refs] {
+           if {![info exists allparents($id)] &&
+               [lsearch -exact $tagobjs $id] < 0} {
+               lappend ids $id
+           }
+       }
+       if {$ids ne {}} {
+           foreach id $seeds {
+               lappend ids "^$id"
+           }
+       }
+    }
+    if {$ids ne {}} {
+       set fd [open [concat $cmd $ids] r]
+       fconfigure $fd -blocking 0
+       incr allcommits
+       nowbusy allcommits
+       filerun $fd [list getallclines $fd]
+    } else {
+       dispneartags 0
     }
-    set fd [open $cmd r]
-    fconfigure $fd -blocking 0
-    incr allcommits
-    nowbusy allcommits
-    filerun $fd [list getallclines $fd]
 }
 
 # Since most commits have 1 parent and 1 child, we group strings of
@@ -6469,10 +6723,10 @@ proc getallcommits {} {
 # coming from descendents, and "outgoing" means going towards ancestors.
 
 proc getallclines {fd} {
-    global allids allparents allchildren idtags idheads nextarc nbmp
+    global allparents allchildren idtags idheads nextarc
     global arcnos arcids arctags arcout arcend arcstart archeads growing
-    global seeds allcommits
-
+    global seeds allcommits cachedarcs allcupdate
+    
     set nid 0
     while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
        set id [lindex $line 0]
@@ -6480,7 +6734,7 @@ proc getallclines {fd} {
            # seen it already
            continue
        }
-       lappend allids $id
+       set cachedarcs 0
        set olds [lrange $line 1 end]
        set allparents($id) $olds
        if {![info exists allchildren($id)]} {
@@ -6511,7 +6765,6 @@ proc getallclines {fd} {
                continue
            }
        }
-       incr nbmp
        foreach a $arcnos($id) {
            lappend arcids($a) $id
            set arcend($a) $id
@@ -6551,9 +6804,28 @@ proc getallclines {fd} {
     if {![eof $fd]} {
        return [expr {$nid >= 1000? 2: 1}]
     }
-    close $fd
+    set cacheok 1
+    if {[catch {
+       fconfigure $fd -blocking 1
+       close $fd
+    } err]} {
+       # got an error reading the list of commits
+       # if we were updating, try rereading the whole thing again
+       if {$allcupdate} {
+           incr allcommits -1
+           dropcache $err
+           return
+       }
+       error_popup "Error reading commit topology information;\
+               branch and preceding/following tag information\
+               will be incomplete.\n($err)"
+       set cacheok 0
+    }
     if {[incr allcommits -1] == 0} {
        notbusy allcommits
+       if {$cacheok} {
+           run savecache
+       }
     }
     dispneartags 0
     return 0
@@ -6577,7 +6849,7 @@ proc recalcarc {a} {
 }
 
 proc splitarc {p} {
-    global arcnos arcids nextarc nbmp arctags archeads idtags idheads
+    global arcnos arcids nextarc arctags archeads idtags idheads
     global arcstart arcend arcout allparents growing
 
     set a $arcnos($p)
@@ -6609,7 +6881,6 @@ proc splitarc {p} {
        set growing($na) 1
        unset growing($a)
     }
-    incr nbmp
 
     foreach id $tail {
        if {[llength $arcnos($id)] == 1} {
@@ -6633,17 +6904,15 @@ proc splitarc {p} {
 # Update things for a new commit added that is a child of one
 # existing commit.  Used when cherry-picking.
 proc addnewchild {id p} {
-    global allids allparents allchildren idtags nextarc nbmp
+    global allparents allchildren idtags nextarc
     global arcnos arcids arctags arcout arcend arcstart archeads growing
     global seeds allcommits
 
     if {![info exists allcommits] || ![info exists arcnos($p)]} return
-    lappend allids $id
     set allparents($id) [list $p]
     set allchildren($id) {}
     set arcnos($id) {}
     lappend seeds $id
-    incr nbmp
     lappend allchildren($p) $id
     set a [incr nextarc]
     set arcstart($a) $id
@@ -6658,6 +6927,172 @@ proc addnewchild {id p} {
     set arcout($id) [list $a]
 }
 
+# This implements a cache for the topology information.
+# The cache saves, for each arc, the start and end of the arc,
+# the ids on the arc, and the outgoing arcs from the end.
+proc readcache {f} {
+    global arcnos arcids arcout arcstart arcend arctags archeads nextarc
+    global idtags idheads allparents cachedarcs possible_seeds seeds growing
+    global allcwait
+
+    set a $nextarc
+    set lim $cachedarcs
+    if {$lim - $a > 500} {
+       set lim [expr {$a + 500}]
+    }
+    if {[catch {
+       if {$a == $lim} {
+           # finish reading the cache and setting up arctags, etc.
+           set line [gets $f]
+           if {$line ne "1"} {error "bad final version"}
+           close $f
+           foreach id [array names idtags] {
+               if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
+                   [llength $allparents($id)] == 1} {
+                   set a [lindex $arcnos($id) 0]
+                   if {$arctags($a) eq {}} {
+                       recalcarc $a
+                   }
+               }
+           }
+           foreach id [array names idheads] {
+               if {[info exists arcnos($id)] && [llength $arcnos($id)] == 1 &&
+                   [llength $allparents($id)] == 1} {
+                   set a [lindex $arcnos($id) 0]
+                   if {$archeads($a) eq {}} {
+                       recalcarc $a
+                   }
+               }
+           }
+           foreach id [lsort -unique $possible_seeds] {
+               if {$arcnos($id) eq {}} {
+                   lappend seeds $id
+               }
+           }
+           set allcwait 0
+       } else {
+           while {[incr a] <= $lim} {
+               set line [gets $f]
+               if {[llength $line] != 3} {error "bad line"}
+               set s [lindex $line 0]
+               set arcstart($a) $s
+               lappend arcout($s) $a
+               if {![info exists arcnos($s)]} {
+                   lappend possible_seeds $s
+                   set arcnos($s) {}
+               }
+               set e [lindex $line 1]
+               if {$e eq {}} {
+                   set growing($a) 1
+               } else {
+                   set arcend($a) $e
+                   if {![info exists arcout($e)]} {
+                       set arcout($e) {}
+                   }
+               }
+               set arcids($a) [lindex $line 2]
+               foreach id $arcids($a) {
+                   lappend allparents($s) $id
+                   set s $id
+                   lappend arcnos($id) $a
+               }
+               if {![info exists allparents($s)]} {
+                   set allparents($s) {}
+               }
+               set arctags($a) {}
+               set archeads($a) {}
+           }
+           set nextarc [expr {$a - 1}]
+       }
+    } err]} {
+       dropcache $err
+       return 0
+    }
+    if {!$allcwait} {
+       getallcommits
+    }
+    return $allcwait
+}
+
+proc getcache {f} {
+    global nextarc cachedarcs possible_seeds
+
+    if {[catch {
+       set line [gets $f]
+       if {[llength $line] != 2 || [lindex $line 0] ne "1"} {error "bad version"}
+       # make sure it's an integer
+       set cachedarcs [expr {int([lindex $line 1])}]
+       if {$cachedarcs < 0} {error "bad number of arcs"}
+       set nextarc 0
+       set possible_seeds {}
+       run readcache $f
+    } err]} {
+       dropcache $err
+    }
+    return 0
+}
+
+proc dropcache {err} {
+    global allcwait nextarc cachedarcs seeds
+
+    #puts "dropping cache ($err)"
+    foreach v {arcnos arcout arcids arcstart arcend growing \
+                  arctags archeads allparents allchildren} {
+       global $v
+       catch {unset $v}
+    }
+    set allcwait 0
+    set nextarc 0
+    set cachedarcs 0
+    set seeds {}
+    getallcommits
+}
+
+proc writecache {f} {
+    global cachearc cachedarcs allccache
+    global arcstart arcend arcnos arcids arcout
+
+    set a $cachearc
+    set lim $cachedarcs
+    if {$lim - $a > 1000} {
+       set lim [expr {$a + 1000}]
+    }
+    if {[catch {
+       while {[incr a] <= $lim} {
+           if {[info exists arcend($a)]} {
+               puts $f [list $arcstart($a) $arcend($a) $arcids($a)]
+           } else {
+               puts $f [list $arcstart($a) {} $arcids($a)]
+           }
+       }
+    } err]} {
+       catch {close $f}
+       catch {file delete $allccache}
+       #puts "writing cache failed ($err)"
+       return 0
+    }
+    set cachearc [expr {$a - 1}]
+    if {$a > $cachedarcs} {
+       puts $f "1"
+       close $f
+       return 0
+    }
+    return 1
+}
+
+proc savecache {} {
+    global nextarc cachedarcs cachearc allccache
+
+    if {$nextarc == $cachedarcs} return
+    set cachearc 0
+    set cachedarcs $nextarc
+    catch {
+       set f [open $allccache w]
+       puts $f [list 1 $cachedarcs]
+       run writecache $f
+    }
+}
+
 # Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
 # or 0 if neither is true.
 proc anc_or_desc {a b} {
@@ -7355,6 +7790,7 @@ proc showtag {tag isnew} {
     }
     $ctext conf -state normal
     clear_ctext
+    settabs 0
     set linknum 0
     if {![info exists tagcontents($tag)]} {
        catch {
@@ -7378,11 +7814,135 @@ proc doquit {} {
     destroy .
 }
 
+proc mkfontdisp {font top which} {
+    global fontattr fontpref $font
+
+    set fontpref($font) [set $font]
+    button $top.${font}but -text $which -font optionfont \
+       -command [list choosefont $font $which]
+    label $top.$font -relief flat -font $font \
+       -text $fontattr($font,family) -justify left
+    grid x $top.${font}but $top.$font -sticky w
+}
+
+proc choosefont {font which} {
+    global fontparam fontlist fonttop fontattr
+
+    set fontparam(which) $which
+    set fontparam(font) $font
+    set fontparam(family) [font actual $font -family]
+    set fontparam(size) $fontattr($font,size)
+    set fontparam(weight) $fontattr($font,weight)
+    set fontparam(slant) $fontattr($font,slant)
+    set top .gitkfont
+    set fonttop $top
+    if {![winfo exists $top]} {
+       font create sample
+       eval font config sample [font actual $font]
+       toplevel $top
+       wm title $top "Gitk font chooser"
+       label $top.l -textvariable fontparam(which) -font uifont
+       pack $top.l -side top
+       set fontlist [lsort [font families]]
+       frame $top.f
+       listbox $top.f.fam -listvariable fontlist \
+           -yscrollcommand [list $top.f.sb set]
+       bind $top.f.fam <<ListboxSelect>> selfontfam
+       scrollbar $top.f.sb -command [list $top.f.fam yview]
+       pack $top.f.sb -side right -fill y
+       pack $top.f.fam -side left -fill both -expand 1
+       pack $top.f -side top -fill both -expand 1
+       frame $top.g
+       spinbox $top.g.size -from 4 -to 40 -width 4 \
+           -textvariable fontparam(size) \
+           -validatecommand {string is integer -strict %s}
+       checkbutton $top.g.bold -padx 5 \
+           -font {{Times New Roman} 12 bold} -text "B" -indicatoron 0 \
+           -variable fontparam(weight) -onvalue bold -offvalue normal
+       checkbutton $top.g.ital -padx 5 \
+           -font {{Times New Roman} 12 italic} -text "I" -indicatoron 0  \
+           -variable fontparam(slant) -onvalue italic -offvalue roman
+       pack $top.g.size $top.g.bold $top.g.ital -side left
+       pack $top.g -side top
+       canvas $top.c -width 150 -height 50 -border 2 -relief sunk \
+           -background white
+       $top.c create text 100 25 -anchor center -text $which -font sample \
+           -fill black -tags text
+       bind $top.c <Configure> [list centertext $top.c]
+       pack $top.c -side top -fill x
+       frame $top.buts
+       button $top.buts.ok -text "OK" -command fontok -default active \
+           -font uifont
+       button $top.buts.can -text "Cancel" -command fontcan -default normal \
+           -font uifont
+       grid $top.buts.ok $top.buts.can
+       grid columnconfigure $top.buts 0 -weight 1 -uniform a
+       grid columnconfigure $top.buts 1 -weight 1 -uniform a
+       pack $top.buts -side bottom -fill x
+       trace add variable fontparam write chg_fontparam
+    } else {
+       raise $top
+       $top.c itemconf text -text $which
+    }
+    set i [lsearch -exact $fontlist $fontparam(family)]
+    if {$i >= 0} {
+       $top.f.fam selection set $i
+       $top.f.fam see $i
+    }
+}
+
+proc centertext {w} {
+    $w coords text [expr {[winfo width $w] / 2}] [expr {[winfo height $w] / 2}]
+}
+
+proc fontok {} {
+    global fontparam fontpref prefstop
+
+    set f $fontparam(font)
+    set fontpref($f) [list $fontparam(family) $fontparam(size)]
+    if {$fontparam(weight) eq "bold"} {
+       lappend fontpref($f) "bold"
+    }
+    if {$fontparam(slant) eq "italic"} {
+       lappend fontpref($f) "italic"
+    }
+    set w $prefstop.$f
+    $w conf -text $fontparam(family) -font $fontpref($f)
+       
+    fontcan
+}
+
+proc fontcan {} {
+    global fonttop fontparam
+
+    if {[info exists fonttop]} {
+       catch {destroy $fonttop}
+       catch {font delete sample}
+       unset fonttop
+       unset fontparam
+    }
+}
+
+proc selfontfam {} {
+    global fonttop fontparam
+
+    set i [$fonttop.f.fam curselection]
+    if {$i ne {}} {
+       set fontparam(family) [$fonttop.f.fam get $i]
+    }
+}
+
+proc chg_fontparam {v sub op} {
+    global fontparam
+
+    font config sample -$sub $fontparam($sub)
+}
+
 proc doprefs {} {
-    global maxwidth maxgraphpct diffopts
+    global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
     global bgcolor fgcolor ctext diffcolors selectbgcolor
-    global uifont tabstop
+    global uifont tabstop limitdiffs
 
     set top .gitkprefs
     set prefstop $top
@@ -7390,13 +7950,14 @@ proc doprefs {} {
        raise $top
        return
     }
-    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
+    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
+                  limitdiffs tabstop} {
        set oldprefs($v) [set $v]
     }
     toplevel $top
     wm title $top "Gitk preferences"
     label $top.ldisp -text "Commit list display options"
-    $top.ldisp configure -font $uifont
+    $top.ldisp configure -font uifont
     grid $top.ldisp - -sticky w -pady 10
     label $top.spacer -text " "
     label $top.maxwidthl -text "Maximum graph width (lines)" \
@@ -7414,23 +7975,24 @@ proc doprefs {} {
     grid x $top.showlocal -sticky w
 
     label $top.ddisp -text "Diff display options"
-    $top.ddisp configure -font $uifont
+    $top.ddisp configure -font uifont
     grid $top.ddisp - -sticky w -pady 10
-    label $top.diffoptl -text "Options for diff program" \
-       -font optionfont
-    entry $top.diffopt -width 20 -textvariable diffopts
-    grid x $top.diffoptl $top.diffopt -sticky w
+    label $top.tabstopl -text "Tab spacing" -font optionfont
+    spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+    grid x $top.tabstopl $top.tabstop -sticky w
     frame $top.ntag
     label $top.ntag.l -text "Display nearby tags" -font optionfont
     checkbutton $top.ntag.b -variable showneartags
     pack $top.ntag.b $top.ntag.l -side left
     grid x $top.ntag -sticky w
-    label $top.tabstopl -text "tabstop" -font optionfont
-    spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
-    grid x $top.tabstopl $top.tabstop -sticky w
+    frame $top.ldiff
+    label $top.ldiff.l -text "Limit diffs to listed paths" -font optionfont
+    checkbutton $top.ldiff.b -variable limitdiffs
+    pack $top.ldiff.b $top.ldiff.l -side left
+    grid x $top.ldiff -sticky w
 
     label $top.cdisp -text "Colors: press to choose"
-    $top.cdisp configure -font $uifont
+    $top.cdisp configure -font uifont
     grid $top.cdisp - -sticky w -pady 10
     label $top.bg -padx 40 -relief sunk -background $bgcolor
     button $top.bgbut -text "Background" -font optionfont \
@@ -7461,11 +8023,18 @@ proc doprefs {} {
        -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
     grid x $top.selbgbut $top.selbgsep -sticky w
 
+    label $top.cfont -text "Fonts: press to choose"
+    $top.cfont configure -font uifont
+    grid $top.cfont - -sticky w -pady 10
+    mkfontdisp mainfont $top "Main font"
+    mkfontdisp textfont $top "Diff display font"
+    mkfontdisp uifont $top "User interface font"
+
     frame $top.buts
     button $top.buts.ok -text "OK" -command prefsok -default active
-    $top.buts.ok configure -font $uifont
+    $top.buts.ok configure -font uifont
     button $top.buts.can -text "Cancel" -command prefscan -default normal
-    $top.buts.can configure -font $uifont
+    $top.buts.can configure -font uifont
     grid $top.buts.ok $top.buts.can
     grid columnconfigure $top.buts 0 -weight 1 -uniform a
     grid columnconfigure $top.buts 1 -weight 1 -uniform a
@@ -7513,24 +8082,48 @@ proc setfg {c} {
 }
 
 proc prefscan {} {
-    global maxwidth maxgraphpct diffopts
-    global oldprefs prefstop showneartags showlocalchanges
+    global oldprefs prefstop
 
-    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
+    foreach v {maxwidth maxgraphpct showneartags showlocalchanges \
+                  limitdiffs tabstop} {
+       global $v
        set $v $oldprefs($v)
     }
     catch {destroy $prefstop}
     unset prefstop
+    fontcan
 }
 
 proc prefsok {} {
     global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
-    global charspc ctext tabstop
+    global fontpref mainfont textfont uifont
+    global limitdiffs treediffs
 
     catch {destroy $prefstop}
     unset prefstop
-    $ctext configure -tabs "[expr {$tabstop * $charspc}]"
+    fontcan
+    set fontchanged 0
+    if {$mainfont ne $fontpref(mainfont)} {
+       set mainfont $fontpref(mainfont)
+       parsefont mainfont $mainfont
+       eval font configure mainfont [fontflags mainfont]
+       eval font configure mainfontbold [fontflags mainfont 1]
+       setcoords
+       set fontchanged 1
+    }
+    if {$textfont ne $fontpref(textfont)} {
+       set textfont $fontpref(textfont)
+       parsefont textfont $textfont
+       eval font configure textfont [fontflags textfont]
+       eval font configure textfontbold [fontflags textfont 1]
+    }
+    if {$uifont ne $fontpref(uifont)} {
+       set uifont $fontpref(uifont)
+       parsefont uifont $uifont
+       eval font configure uifont [fontflags uifont]
+    }
+    settabs
     if {$showlocalchanges != $oldprefs(showlocalchanges)} {
        if {$showlocalchanges} {
            doshowlocalchanges
@@ -7538,10 +8131,15 @@ proc prefsok {} {
            dohidelocalchanges
        }
     }
-    if {$maxwidth != $oldprefs(maxwidth)
+    if {$limitdiffs != $oldprefs(limitdiffs)} {
+       # treediffs elements are limited by path
+       catch {unset treediffs}
+    }
+    if {$fontchanged || $maxwidth != $oldprefs(maxwidth)
        || $maxgraphpct != $oldprefs(maxgraphpct)} {
        redisplay
-    } elseif {$showneartags != $oldprefs(showneartags)} {
+    } elseif {$showneartags != $oldprefs(showneartags) ||
+         $limitdiffs != $oldprefs(limitdiffs)} {
        reselectline
     }
 }
@@ -7836,7 +8434,6 @@ if {[catch {package require Tk 8.4} err]} {
 
 # defaults...
 set datemode 0
-set diffopts "-U 5 -p"
 set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
@@ -7860,15 +8457,16 @@ set maxgraphpct 50
 set maxwidth 16
 set revlistorder 0
 set fastdate 0
-set uparrowlen 7
-set downarrowlen 7
-set mingaplen 30
+set uparrowlen 5
+set downarrowlen 5
+set mingaplen 100
 set cmitmode "patch"
 set wrapcomment "none"
 set showneartags 1
 set maxrefs 20
 set maxlinelen 200
 set showlocalchanges 1
+set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
 
 set colors {green red blue magenta darkgrey brown orange}
@@ -7882,6 +8480,17 @@ catch {source ~/.gitk}
 
 font create optionfont -family sans-serif -size -12
 
+parsefont mainfont $mainfont
+eval font create mainfont [fontflags mainfont]
+eval font create mainfontbold [fontflags mainfont 1]
+
+parsefont textfont $textfont
+eval font create textfont [fontflags textfont]
+eval font create textfontbold [fontflags textfont 1]
+
+parsefont uifont $uifont
+eval font create uifont [fontflags uifont]
+
 # check that we can find a .git directory somewhere...
 if {[catch {set gitdir [gitdir]}]} {
     show_error {} . "Cannot find a git repository here."
@@ -7892,6 +8501,7 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
+set mergeonly 0
 set revtreeargs {}
 set cmdline_files {}
 set i 0
@@ -7899,6 +8509,10 @@ foreach arg $argv {
     switch -- $arg {
        "" { }
        "-d" { set datemode 1 }
+       "--merge" {
+           set mergeonly 1
+           lappend revtreeargs $arg
+       }
        "--" {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
@@ -7939,9 +8553,44 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
     }
 }
 
+if {$mergeonly} {
+    # find the list of unmerged files
+    set mlist {}
+    set nr_unmerged 0
+    if {[catch {
+       set fd [open "| git ls-files -u" r]
+    } err]} {
+       show_error {} . "Couldn't get list of unmerged files: $err"
+       exit 1
+    }
+    while {[gets $fd line] >= 0} {
+       set i [string first "\t" $line]
+       if {$i < 0} continue
+       set fname [string range $line [expr {$i+1}] end]
+       if {[lsearch -exact $mlist $fname] >= 0} continue
+       incr nr_unmerged
+       if {$cmdline_files eq {} || [path_filter $cmdline_files $fname]} {
+           lappend mlist $fname
+       }
+    }
+    catch {close $fd}
+    if {$mlist eq {}} {
+       if {$nr_unmerged == 0} {
+           show_error {} . "No files selected: --merge specified but\
+                            no files are unmerged."
+       } else {
+           show_error {} . "No files selected: --merge specified but\
+                            no unmerged files are within file limit."
+       }
+       exit 1
+    }
+    set cmdline_files $mlist
+}
+
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
 
+set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 
 set runq {}
 set history {}
@@ -7949,18 +8598,23 @@ set historyindex 0
 set fh_serial 0
 set nhl_names {}
 set highlight_paths {}
+set findpattern {}
 set searchdirn -forwards
 set boldrows {}
 set boldnamerows {}
 set diffelide {0 0}
 set markingmatches 0
-
-set optim_delay 16
+set linkentercount 0
+set need_redisplay 0
+set nrows_drawn 0
+set firsttabstop 0
 
 set nextviewnum 1
 set curview 0
 set selectedview 0
 set selectedhlview None
+set highlight_related None
+set highlight_files {}
 set viewfiles(0) {}
 set viewperm(0) 0
 set viewargs(0) {}
@@ -7969,7 +8623,6 @@ set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
-set lookingforhead 0
 set localirow -1
 set localfrow -1
 set lserial 0
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..759dff1cceafb3d67f662d3876405ffcf9ebf96c 100755 (executable)
@@ -611,6 +611,15 @@ (%)
        );
        my %mapping = @mapping;
 
+       if ($params{-replay}) {
+               while (my ($name, $symbol) = each %mapping) {
+                       if (!exists $params{$name}) {
+                               # to allow for multivalued params we use arrayref form
+                               $params{$name} = [ $cgi->param($symbol) ];
+                       }
+               }
+       }
+
        $params{'project'} = $project unless exists $params{'project'};
 
        my ($use_pathinfo) = gitweb_check_feature('pathinfo');
@@ -846,6 +855,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
 
@@ -1406,20 +1432,121 @@ sub git_get_type {
        return $type;
 }
 
+# repository configuration
+our $config_file = '';
+our %config;
+
+# store multiple values for single key as anonymous array reference
+# single values stored directly in the hash, not as [ <value> ]
+sub hash_set_multi {
+       my ($hash, $key, $value) = @_;
+
+       if (!exists $hash->{$key}) {
+               $hash->{$key} = $value;
+       } elsif (!ref $hash->{$key}) {
+               $hash->{$key} = [ $hash->{$key}, $value ];
+       } else {
+               push @{$hash->{$key}}, $value;
+       }
+}
+
+# return hash of git project configuration
+# optionally limited to some section, e.g. 'gitweb'
+sub git_parse_project_config {
+       my $section_regexp = shift;
+       my %config;
+
+       local $/ = "\0";
+
+       open my $fh, "-|", git_cmd(), "config", '-z', '-l',
+               or return;
+
+       while (my $keyval = <$fh>) {
+               chomp $keyval;
+               my ($key, $value) = split(/\n/, $keyval, 2);
+
+               hash_set_multi(\%config, $key, $value)
+                       if (!defined $section_regexp || $key =~ /^(?:$section_regexp)\./o);
+       }
+       close $fh;
+
+       return %config;
+}
+
+# convert config value to boolean, 'true' or 'false'
+# no value, number > 0, 'true' and 'yes' values are true
+# rest of values are treated as false (never as error)
+sub config_to_bool {
+       my $val = shift;
+
+       # strip leading and trailing whitespace
+       $val =~ s/^\s+//;
+       $val =~ s/\s+$//;
+
+       return (!defined $val ||               # section.key
+               ($val =~ /^\d+$/ && $val) ||   # section.key = 1
+               ($val =~ /^(?:true|yes)$/i));  # section.key = true
+}
+
+# convert config value to simple decimal number
+# an optional value suffix of 'k', 'm', or 'g' will cause the value
+# to be multiplied by 1024, 1048576, or 1073741824
+sub config_to_int {
+       my $val = shift;
+
+       # strip leading and trailing whitespace
+       $val =~ s/^\s+//;
+       $val =~ s/\s+$//;
+
+       if (my ($num, $unit) = ($val =~ /^([0-9]*)([kmg])$/i)) {
+               $unit = lc($unit);
+               # unknown unit is treated as 1
+               return $num * ($unit eq 'g' ? 1073741824 :
+                              $unit eq 'm' ?    1048576 :
+                              $unit eq 'k' ?       1024 : 1);
+       }
+       return $val;
+}
+
+# convert config value to array reference, if needed
+sub config_to_multi {
+       my $val = shift;
+
+       return ref($val) ? $val : [ $val ];
+}
+
 sub git_get_project_config {
        my ($key, $type) = @_;
 
+       # key sanity check
        return unless ($key);
        $key =~ s/^gitweb\.//;
        return if ($key =~ m/\W/);
 
-       my @x = (git_cmd(), 'config');
-       if (defined $type) { push @x, $type; }
-       push @x, "--get";
-       push @x, "gitweb.$key";
-       my $val = qx(@x);
-       chomp $val;
-       return ($val);
+       # type sanity check
+       if (defined $type) {
+               $type =~ s/^--//;
+               $type = undef
+                       unless ($type eq 'bool' || $type eq 'int');
+       }
+
+       # get config
+       if (!defined $config_file ||
+           $config_file ne "$git_dir/config") {
+               %config = git_parse_project_config('gitweb');
+               $config_file = "$git_dir/config";
+       }
+
+       # ensure given type
+       if (!defined $type) {
+               return $config{"gitweb.$key"};
+       } elsif ($type eq 'bool') {
+               # backward compatibility: 'git config --bool' returns true/false
+               return config_to_bool($config{"gitweb.$key"}) ? 'true' : 'false';
+       } elsif ($type eq 'int') {
+               return config_to_int($config{"gitweb.$key"});
+       }
+       return $config{"gitweb.$key"};
 }
 
 # get hash of given path at given ref
@@ -1479,7 +1606,9 @@ sub git_get_path_by_hash {
 sub git_get_project_description {
        my $path = shift;
 
-       open my $fd, "$projectroot/$path/description" or return undef;
+       $git_dir = "$projectroot/$path";
+       open my $fd, "$projectroot/$path/description"
+               or return git_get_project_config('description');
        my $descr = <$fd>;
        close $fd;
        if (defined $descr) {
@@ -1491,7 +1620,11 @@ sub git_get_project_description {
 sub git_get_project_url_list {
        my $path = shift;
 
-       open my $fd, "$projectroot/$path/cloneurl" or return;
+       $git_dir = "$projectroot/$path";
+       open my $fd, "$projectroot/$path/cloneurl"
+               or return wantarray ?
+               @{ config_to_multi(git_get_project_config('url')) } :
+                  config_to_multi(git_get_project_config('url'));
        my @git_project_url_list = map { chomp; $_ } <$fd>;
        close $fd;
 
@@ -1973,12 +2106,12 @@ sub parse_difftree_raw_line {
                $res{'to_mode'} = $2;
                $res{'from_id'} = $3;
                $res{'to_id'} = $4;
-               $res{'status'} = $5;
+               $res{'status'} = $res{'status_str'} = $5;
                $res{'similarity'} = $6;
                if ($res{'status'} eq 'R' || $res{'status'} eq 'C') { # renamed or copied
                        ($res{'from_file'}, $res{'to_file'}) = map { unquote($_) } split("\t", $7);
                } else {
-                       $res{'file'} = unquote($7);
+                       $res{'from_file'} = $res{'to_file'} = $res{'file'} = unquote($7);
                }
        }
        # '::100755 100755 100755 60e79ca1b01bc8b057abe17ddab484699a7f5fdb 94067cc5f73388f33722d52ae02f44692bc07490 94067cc5f73388f33722d52ae02f44692bc07490 MR git-gui/git-gui.sh'
@@ -1989,6 +2122,7 @@ sub parse_difftree_raw_line {
                $res{'to_mode'} = pop @{$res{'from_mode'}};
                $res{'from_id'} = [ split(' ', $3) ];
                $res{'to_id'} = pop @{$res{'from_id'}};
+               $res{'status_str'} = $4;
                $res{'status'} = [ split('', $4) ];
                $res{'to_file'} = unquote($5);
        }
@@ -2000,6 +2134,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;
@@ -2032,7 +2179,10 @@ sub parse_from_to_diffinfo {
                fill_from_file_info($diffinfo, @parents)
                        unless exists $diffinfo->{'from_file'};
                for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-                       $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+                       $from->{'file'}[$i] =
+                               defined $diffinfo->{'from_file'}[$i] ?
+                                       $diffinfo->{'from_file'}[$i] :
+                                       $diffinfo->{'to_file'};
                        if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
                                $from->{'href'}[$i] = href(action=>"blob",
                                                           hash_base=>$parents[$i],
@@ -2043,7 +2193,8 @@ sub parse_from_to_diffinfo {
                        }
                }
        } else {
-               $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+               # ordinary (not combined) diff
+               $from->{'file'} = $diffinfo->{'from_file'};
                if ($diffinfo->{'status'} ne "A") { # not new (added) file
                        $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
                                               hash=>$diffinfo->{'from_id'},
@@ -2053,7 +2204,7 @@ sub parse_from_to_diffinfo {
                }
        }
 
-       $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+       $to->{'file'} = $diffinfo->{'to_file'};
        if (!is_deleted($diffinfo)) { # file exists in result
                $to->{'href'} = href(action=>"blob", hash_base=>$hash,
                                     hash=>$diffinfo->{'to_id'},
@@ -2474,7 +2625,7 @@ sub format_paging_nav {
 
        if ($page > 0) {
                $paging_nav .= " &sdot; " .
-                       $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1),
+                       $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                $paging_nav .= " &sdot; prev";
@@ -2482,7 +2633,7 @@ sub format_paging_nav {
 
        if ($nrevs >= (100 * ($page+1)-1)) {
                $paging_nav .= " &sdot; " .
-                       $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1),
+                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
        } else {
                $paging_nav .= " &sdot; next";
@@ -2766,6 +2917,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 +2934,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) = @_;
+# is current raw difftree line of file deletion
+sub is_deleted {
+       my $diffinfo = shift;
 
-       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;
-       }
+       return $diffinfo->{'status_str'} =~ /D/;
 }
 
-sub is_deleted {
-       my $diffinfo = shift;
+# 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 $diffinfo->{'to_id'} eq ('0' x 40);
+       return defined $diffinfo && defined $patchinfo
+               && $diffinfo->{'to_file'} eq $patchinfo->{'to_file'};
 }
 
+
 sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
        my ($parent) = $parents[0];
@@ -2840,13 +2989,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 +3260,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 +3279,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 +3328,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 +3382,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 +3514,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 +3556,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 +3607,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 +3764,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) {
@@ -3896,11 +4018,11 @@ sub git_blame2 {
                or die_error(undef, "Open git-blame failed");
        git_header_html();
        my $formats_nav =
-               $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
+               $cgi->a({-href => href(action=>"blob", -replay=>1)},
                        "blob") .
                " | " .
-               $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)},
-                       "history") .
+               $cgi->a({-href => href(action=>"history", -replay=>1)},
+                       "history") .
                " | " .
                $cgi->a({-href => href(action=>"blame", file_name=>$file_name)},
                        "HEAD");
@@ -4176,18 +4298,15 @@ sub git_blob {
                if (defined $file_name) {
                        if ($have_blame) {
                                $formats_nav .=
-                                       $cgi->a({-href => href(action=>"blame", hash_base=>$hash_base,
-                                                              hash=>$hash, file_name=>$file_name)},
+                                       $cgi->a({-href => href(action=>"blame", -replay=>1)},
                                                "blame") .
                                        " | ";
                        }
                        $formats_nav .=
-                               $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
-                                                      hash=>$hash, file_name=>$file_name)},
+                               $cgi->a({-href => href(action=>"history", -replay=>1)},
                                        "history") .
                                " | " .
-                               $cgi->a({-href => href(action=>"blob_plain",
-                                                      hash=>$hash, file_name=>$file_name)},
+                               $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
                                        "raw") .
                                " | " .
                                $cgi->a({-href => href(action=>"blob",
@@ -4195,7 +4314,8 @@ sub git_blob {
                                        "HEAD");
                } else {
                        $formats_nav .=
-                               $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "raw");
+                               $cgi->a({-href => href(action=>"blob_plain", -replay=>1)},
+                                       "raw");
                }
                git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav);
                git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
@@ -4258,8 +4378,7 @@ sub git_tree {
                my @views_nav = ();
                if (defined $file_name) {
                        push @views_nav,
-                               $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
-                                                      hash=>$hash, file_name=>$file_name)},
+                               $cgi->a({-href => href(action=>"history", -replay=>1)},
                                        "history"),
                                $cgi->a({-href => href(action=>"tree",
                                                       hash_base=>"HEAD", file_name=>$file_name)},
@@ -4433,7 +4552,7 @@ sub git_log {
        }
        if ($#commitlist >= 100) {
                print "<div class=\"page_nav\">\n";
-               print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
+               print $cgi->a({-href => href(-replay=>1, page=>$page+1),
                               -accesskey => "n", -title => "Alt-n"}, "next");
                print "</div>\n";
        }
@@ -4665,8 +4784,8 @@ sub git_blobdiff {
                }
 
                %diffinfo = parse_difftree_raw_line($difftree[0]);
-               $file_parent ||= $diffinfo{'from_file'} || $file_name || $diffinfo{'file'};
-               $file_name   ||= $diffinfo{'to_file'}   || $diffinfo{'file'};
+               $file_parent ||= $diffinfo{'from_file'} || $file_name;
+               $file_name   ||= $diffinfo{'to_file'};
 
                $hash_parent ||= $diffinfo{'from_id'};
                $hash        ||= $diffinfo{'to_id'};
@@ -4727,10 +4846,7 @@ sub git_blobdiff {
        # header
        if ($format eq 'html') {
                my $formats_nav =
-                       $cgi->a({-href => href(action=>"blobdiff_plain",
-                                              hash=>$hash, hash_parent=>$hash_parent,
-                                              hash_base=>$hash_base, hash_parent_base=>$hash_parent_base,
-                                              file_name=>$file_name, file_parent=>$file_parent)},
+                       $cgi->a({-href => href(action=>"blobdiff_plain", -replay=>1)},
                                "raw");
                git_header_html(undef, $expires);
                if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -4804,8 +4920,7 @@ sub git_commitdiff {
        my $formats_nav;
        if ($format eq 'html') {
                $formats_nav =
-                       $cgi->a({-href => href(action=>"commitdiff_plain",
-                                              hash=>$hash, hash_parent=>$hash_parent)},
+                       $cgi->a({-href => href(action=>"commitdiff_plain", -replay=>1)},
                                "raw");
 
                if (defined $hash_parent &&
@@ -5000,27 +5115,20 @@ sub git_history {
                                               file_name=>$file_name)},
                                "first");
                $paging_nav .= " &sdot; " .
-                       $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
-                                              file_name=>$file_name, page=>$page-1),
+                       $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                 -accesskey => "p", -title => "Alt-p"}, "prev");
        } else {
                $paging_nav .= "first";
                $paging_nav .= " &sdot; prev";
        }
-       if ($#commitlist >= 100) {
-               $paging_nav .= " &sdot; " .
-                       $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
-                                              file_name=>$file_name, page=>$page+1),
-                                -accesskey => "n", -title => "Alt-n"}, "next");
-       } else {
-               $paging_nav .= " &sdot; next";
-       }
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =
-                       $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
-                                              file_name=>$file_name, page=>$page+1),
+                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
+               $paging_nav .= " &sdot; $next_link";
+       } else {
+               $paging_nav .= " &sdot; next";
        }
 
        git_header_html();
@@ -5090,30 +5198,23 @@ sub git_search {
                                                       searchtext=>$searchtext, searchtype=>$searchtype)},
                                        "first");
                        $paging_nav .= " &sdot; " .
-                               $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page-1),
+                               $cgi->a({-href => href(-replay=>1, page=>$page-1),
                                         -accesskey => "p", -title => "Alt-p"}, "prev");
                } else {
                        $paging_nav .= "first";
                        $paging_nav .= " &sdot; prev";
                }
+               my $next_link = '';
                if ($#commitlist >= 100) {
-                       $paging_nav .= " &sdot; " .
-                               $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
+                       $next_link =
+                               $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                         -accesskey => "n", -title => "Alt-n"}, "next");
+                       $paging_nav .= " &sdot; $next_link";
                } else {
                        $paging_nav .= " &sdot; next";
                }
-               my $next_link = '';
+
                if ($#commitlist >= 100) {
-                       $next_link =
-                               $cgi->a({-href => href(action=>"search", hash=>$hash,
-                                                      searchtext=>$searchtext, searchtype=>$searchtype,
-                                                      page=>$page+1),
-                                        -accesskey => "n", -title => "Alt-n"}, "next");
                }
 
                git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
@@ -5157,12 +5258,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'},
@@ -5311,7 +5413,7 @@ sub git_shortlog {
        my $next_link = '';
        if ($#commitlist >= 100) {
                $next_link =
-                       $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
+                       $cgi->a({-href => href(-replay=>1, page=>$page+1),
                                 -accesskey => "n", -title => "Alt-n"}, "next");
        }
 
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 c02a3af63450fd7cf22118b481b4f0d9dd35b156..9314621a11030e66eeba7f0da6b88a813e1ba759 100644 (file)
@@ -2241,7 +2241,11 @@ static int delete_remote_branch(char *pattern, int force)
 
                /* Remote branch must be an ancestor of remote HEAD */
                if (!verify_merge_base(head_sha1, remote_ref->old_sha1)) {
-                       return error("The branch '%s' is not a strict subset of your current HEAD.\nIf you are sure you want to delete it, run:\n\t'git http-push -D %s %s'", remote_ref->name, remote->url, pattern);
+                       return error("The branch '%s' is not an ancestor "
+                                    "of your current HEAD.\n"
+                                    "If you are sure you want to delete it,"
+                                    " run:\n\t'git http-push -D %s %s'",
+                                    remote_ref->name, remote->url, pattern);
                }
        }
 
@@ -2417,16 +2421,17 @@ int main(int argc, char **argv)
                        if (!has_sha1_file(ref->old_sha1) ||
                            !ref_newer(ref->peer_ref->new_sha1,
                                       ref->old_sha1)) {
-                               /* We do not have the remote ref, or
+                               /*
+                                * We do not have the remote ref, or
                                 * we know that the remote ref is not
                                 * an ancestor of what we are trying to
                                 * push.  Either way this can be losing
                                 * commits at the remote end and likely
                                 * we were not up to date to begin with.
                                 */
-                               error("remote '%s' is not a strict "
-                                     "subset of local ref '%s'. "
-                                     "maybe you are not up-to-date and "
+                               error("remote '%s' is not an ancestor of\n"
+                                     "local '%s'.\n"
+                                     "Maybe you are not up-to-date and "
                                      "need to pull first?",
                                      ref->name,
                                      ref->peer_ref->name);
index db58e050410197f6eda4b91705e20a8bfc87b088..715a5bb7a6e42694b3e35de703e44e792a5442fe 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);
 }
@@ -683,18 +683,31 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
        }
 }
 
+static int git_index_pack_config(const char *k, const char *v)
+{
+       if (!strcmp(k, "pack.indexversion")) {
+               pack_idx_default_version = git_config_int(k, v);
+               if (pack_idx_default_version > 2)
+                       die("bad pack.indexversion=%d", pack_idx_default_version);
+               return 0;
+       }
+       return git_default_config(k, v);
+}
+
 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];
 
+       git_config(git_index_pack_config);
+
        for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
+               char *arg = argv[i];
 
                if (*arg == '-') {
                        if (!strcmp(arg, "--stdin")) {
@@ -774,8 +787,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 +800,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 +826,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 4a5c77c3b632a9c0636848b933ef5395b01b7103..6c6f595fbc7da09a41228e09cd2c5ef48b91f3f0 100644 (file)
@@ -1572,7 +1572,7 @@ static int merge(struct commit *h1,
 {
        struct commit_list *iter;
        struct commit *merged_common_ancestors;
-       struct tree *mrtree;
+       struct tree *mrtree = mrtree;
        int clean;
 
        if (show(4)) {
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);
diff --git a/parse-options.c b/parse-options.c
new file mode 100644 (file)
index 0000000..cc09c98
--- /dev/null
@@ -0,0 +1,322 @@
+#include "git-compat-util.h"
+#include "parse-options.h"
+
+#define OPT_SHORT 1
+#define OPT_UNSET 2
+
+struct optparse_t {
+       const char **argv;
+       int argc;
+       const char *opt;
+};
+
+static inline const char *get_arg(struct optparse_t *p)
+{
+       if (p->opt) {
+               const char *res = p->opt;
+               p->opt = NULL;
+               return res;
+       }
+       p->argc--;
+       return *++p->argv;
+}
+
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+       size_t len = strlen(prefix);
+       return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+static int opterror(const struct option *opt, const char *reason, int flags)
+{
+       if (flags & OPT_SHORT)
+               return error("switch `%c' %s", opt->short_name, reason);
+       if (flags & OPT_UNSET)
+               return error("option `no-%s' %s", opt->long_name, reason);
+       return error("option `%s' %s", opt->long_name, reason);
+}
+
+static int get_value(struct optparse_t *p,
+                     const struct option *opt, int flags)
+{
+       const char *s, *arg;
+       arg = p->opt ? p->opt : (p->argc > 1 ? p->argv[1] : NULL);
+
+       if (p->opt && (flags & OPT_UNSET))
+               return opterror(opt, "takes no value", flags);
+
+       switch (opt->type) {
+       case OPTION_BOOLEAN:
+               if (!(flags & OPT_SHORT) && p->opt)
+                       return opterror(opt, "takes no value", flags);
+               if (flags & OPT_UNSET)
+                       *(int *)opt->value = 0;
+               else
+                       (*(int *)opt->value)++;
+               return 0;
+
+       case OPTION_STRING:
+               if (flags & OPT_UNSET) {
+                       *(const char **)opt->value = (const char *)NULL;
+                       return 0;
+               }
+               if (opt->flags & PARSE_OPT_OPTARG && (!arg || *arg == '-')) {
+                       *(const char **)opt->value = (const char *)opt->defval;
+                       return 0;
+               }
+               if (!arg)
+                       return opterror(opt, "requires a value", flags);
+               *(const char **)opt->value = get_arg(p);
+               return 0;
+
+       case OPTION_CALLBACK:
+               if (flags & OPT_UNSET)
+                       return (*opt->callback)(opt, NULL, 1);
+               if (opt->flags & PARSE_OPT_NOARG) {
+                       if (p->opt && !(flags & OPT_SHORT))
+                               return opterror(opt, "takes no value", flags);
+                       return (*opt->callback)(opt, NULL, 0);
+               }
+               if (opt->flags & PARSE_OPT_OPTARG && (!arg || *arg == '-'))
+                       return (*opt->callback)(opt, NULL, 0);
+               if (!arg)
+                       return opterror(opt, "requires a value", flags);
+               return (*opt->callback)(opt, get_arg(p), 0);
+
+       case OPTION_INTEGER:
+               if (flags & OPT_UNSET) {
+                       *(int *)opt->value = 0;
+                       return 0;
+               }
+               if (opt->flags & PARSE_OPT_OPTARG && (!arg || !isdigit(*arg))) {
+                       *(int *)opt->value = opt->defval;
+                       return 0;
+               }
+               if (!arg)
+                       return opterror(opt, "requires a value", flags);
+               *(int *)opt->value = strtol(get_arg(p), (char **)&s, 10);
+               if (*s)
+                       return opterror(opt, "expects a numerical value", flags);
+               return 0;
+
+       default:
+               die("should not happen, someone must be hit on the forehead");
+       }
+}
+
+static int parse_short_opt(struct optparse_t *p, const struct option *options)
+{
+       for (; options->type != OPTION_END; options++) {
+               if (options->short_name == *p->opt) {
+                       p->opt = p->opt[1] ? p->opt + 1 : NULL;
+                       return get_value(p, options, OPT_SHORT);
+               }
+       }
+       return error("unknown switch `%c'", *p->opt);
+}
+
+static int parse_long_opt(struct optparse_t *p, const char *arg,
+                          const struct option *options)
+{
+       const char *arg_end = strchr(arg, '=');
+       const struct option *abbrev_option = NULL;
+       int abbrev_flags = 0;
+
+       if (!arg_end)
+               arg_end = arg + strlen(arg);
+
+       for (; options->type != OPTION_END; options++) {
+               const char *rest;
+               int flags = 0;
+
+               if (!options->long_name)
+                       continue;
+
+               rest = skip_prefix(arg, options->long_name);
+               if (!rest) {
+                       /* abbreviated? */
+                       if (!strncmp(options->long_name, arg, arg_end - arg)) {
+is_abbreviated:
+                               if (abbrev_option)
+                                       return error("Ambiguous option: %s "
+                                               "(could be --%s%s or --%s%s)",
+                                               arg,
+                                               (flags & OPT_UNSET) ?
+                                                       "no-" : "",
+                                               options->long_name,
+                                               (abbrev_flags & OPT_UNSET) ?
+                                                       "no-" : "",
+                                               abbrev_option->long_name);
+                               if (!(flags & OPT_UNSET) && *arg_end)
+                                       p->opt = arg_end + 1;
+                               abbrev_option = options;
+                               abbrev_flags = flags;
+                               continue;
+                       }
+                       /* negated and abbreviated very much? */
+                       if (!prefixcmp("no-", arg)) {
+                               flags |= OPT_UNSET;
+                               goto is_abbreviated;
+                       }
+                       /* negated? */
+                       if (strncmp(arg, "no-", 3))
+                               continue;
+                       flags |= OPT_UNSET;
+                       rest = skip_prefix(arg + 3, options->long_name);
+                       /* abbreviated and negated? */
+                       if (!rest && !prefixcmp(options->long_name, arg + 3))
+                               goto is_abbreviated;
+                       if (!rest)
+                               continue;
+               }
+               if (*rest) {
+                       if (*rest != '=')
+                               continue;
+                       p->opt = rest + 1;
+               }
+               return get_value(p, options, flags);
+       }
+       if (abbrev_option)
+               return get_value(p, abbrev_option, abbrev_flags);
+       return error("unknown option `%s'", arg);
+}
+
+int parse_options(int argc, const char **argv, const struct option *options,
+                  const char * const usagestr[], int flags)
+{
+       struct optparse_t args = { argv + 1, argc - 1, NULL };
+       int j = 0;
+
+       for (; args.argc; args.argc--, args.argv++) {
+               const char *arg = args.argv[0];
+
+               if (*arg != '-' || !arg[1]) {
+                       argv[j++] = args.argv[0];
+                       continue;
+               }
+
+               if (arg[1] != '-') {
+                       args.opt = arg + 1;
+                       do {
+                               if (*args.opt == 'h')
+                                       usage_with_options(usagestr, options);
+                               if (parse_short_opt(&args, options) < 0)
+                                       usage_with_options(usagestr, options);
+                       } while (args.opt);
+                       continue;
+               }
+
+               if (!arg[2]) { /* "--" */
+                       if (!(flags & PARSE_OPT_KEEP_DASHDASH)) {
+                               args.argc--;
+                               args.argv++;
+                       }
+                       break;
+               }
+
+               if (!strcmp(arg + 2, "help"))
+                       usage_with_options(usagestr, options);
+               if (parse_long_opt(&args, arg + 2, options))
+                       usage_with_options(usagestr, options);
+       }
+
+       memmove(argv + j, args.argv, args.argc * sizeof(*argv));
+       argv[j + args.argc] = NULL;
+       return j + args.argc;
+}
+
+#define USAGE_OPTS_WIDTH 24
+#define USAGE_GAP         2
+
+void usage_with_options(const char * const *usagestr,
+                        const struct option *opts)
+{
+       fprintf(stderr, "usage: %s\n", *usagestr++);
+       while (*usagestr && **usagestr)
+               fprintf(stderr, "   or: %s\n", *usagestr++);
+       while (*usagestr)
+               fprintf(stderr, "    %s\n", *usagestr++);
+
+       if (opts->type != OPTION_GROUP)
+               fputc('\n', stderr);
+
+       for (; opts->type != OPTION_END; opts++) {
+               size_t pos;
+               int pad;
+
+               if (opts->type == OPTION_GROUP) {
+                       fputc('\n', stderr);
+                       if (*opts->help)
+                               fprintf(stderr, "%s\n", opts->help);
+                       continue;
+               }
+
+               pos = fprintf(stderr, "    ");
+               if (opts->short_name)
+                       pos += fprintf(stderr, "-%c", opts->short_name);
+               if (opts->long_name && opts->short_name)
+                       pos += fprintf(stderr, ", ");
+               if (opts->long_name)
+                       pos += fprintf(stderr, "--%s", opts->long_name);
+
+               switch (opts->type) {
+               case OPTION_INTEGER:
+                       if (opts->flags & PARSE_OPT_OPTARG)
+                               pos += fprintf(stderr, " [<n>]");
+                       else
+                               pos += fprintf(stderr, " <n>");
+                       break;
+               case OPTION_CALLBACK:
+                       if (opts->flags & PARSE_OPT_NOARG)
+                               break;
+                       /* FALLTHROUGH */
+               case OPTION_STRING:
+                       if (opts->argh) {
+                               if (opts->flags & PARSE_OPT_OPTARG)
+                                       pos += fprintf(stderr, " [<%s>]", opts->argh);
+                               else
+                                       pos += fprintf(stderr, " <%s>", opts->argh);
+                       } else {
+                               if (opts->flags & PARSE_OPT_OPTARG)
+                                       pos += fprintf(stderr, " [...]");
+                               else
+                                       pos += fprintf(stderr, " ...");
+                       }
+                       break;
+               default:
+                       break;
+               }
+
+               if (pos <= USAGE_OPTS_WIDTH)
+                       pad = USAGE_OPTS_WIDTH - pos;
+               else {
+                       fputc('\n', stderr);
+                       pad = USAGE_OPTS_WIDTH;
+               }
+               fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+       }
+       fputc('\n', stderr);
+
+       exit(129);
+}
+
+/*----- some often used options -----*/
+#include "cache.h"
+int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
+{
+       int v;
+
+       if (!arg) {
+               v = unset ? 0 : DEFAULT_ABBREV;
+       } else {
+               v = strtol(arg, (char **)&arg, 10);
+               if (*arg)
+                       return opterror(opt, "expects a numerical value", 0);
+               if (v && v < MINIMUM_ABBREV)
+                       v = MINIMUM_ABBREV;
+               else if (v > 40)
+                       v = 40;
+       }
+       *(int *)(opt->value) = v;
+       return 0;
+}
diff --git a/parse-options.h b/parse-options.h
new file mode 100644 (file)
index 0000000..3a470e5
--- /dev/null
@@ -0,0 +1,70 @@
+#ifndef PARSE_OPTIONS_H
+#define PARSE_OPTIONS_H
+
+enum parse_opt_type {
+       OPTION_END,
+       OPTION_GROUP,
+       OPTION_BOOLEAN,
+       OPTION_STRING,
+       OPTION_INTEGER,
+       OPTION_CALLBACK,
+};
+
+enum parse_opt_flags {
+       PARSE_OPT_KEEP_DASHDASH = 1,
+};
+
+enum parse_opt_option_flags {
+       PARSE_OPT_OPTARG  = 1,
+       PARSE_OPT_NOARG   = 2,
+};
+
+struct option;
+typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
+
+struct option {
+       enum parse_opt_type type;
+       int short_name;
+       const char *long_name;
+       void *value;
+       const char *argh;
+       const char *help;
+
+       int flags;
+       parse_opt_cb *callback;
+       /* holds default value for PARSE_OPT_OPTARG,
+          though callbacks can use it like they want */
+       intptr_t defval;
+};
+
+#define OPT_END()                   { OPTION_END }
+#define OPT_GROUP(h)                { OPTION_GROUP, 0, NULL, NULL, NULL, (h) }
+#define OPT_BOOLEAN(s, l, v, h)     { OPTION_BOOLEAN, (s), (l), (v), NULL, (h) }
+#define OPT_INTEGER(s, l, v, h)     { OPTION_INTEGER, (s), (l), (v), NULL, (h) }
+#define OPT_STRING(s, l, v, a, h)   { OPTION_STRING,  (s), (l), (v), (a), (h) }
+#define OPT_CALLBACK(s, l, v, a, h, f) \
+       { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
+
+/* parse_options() will filter out the processed options and leave the
+ * non-option argments in argv[].
+ * Returns the number of arguments left in argv[].
+ */
+extern int parse_options(int argc, const char **argv,
+                         const struct option *options,
+                         const char * const usagestr[], int flags);
+
+extern NORETURN void usage_with_options(const char * const *usagestr,
+                                        const struct option *options);
+
+/*----- some often used options -----*/
+extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
+
+#define OPT__VERBOSE(var)  OPT_BOOLEAN('v', "verbose", (var), "be verbose")
+#define OPT__QUIET(var)    OPT_BOOLEAN('q', "quiet",   (var), "be quiet")
+#define OPT__DRY_RUN(var)  OPT_BOOLEAN('n', "dry-run", (var), "dry run")
+#define OPT__ABBREV(var)  \
+       { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
+         "use <n> digits to display SHA-1s", \
+         PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+#endif
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 170015aabfc18d028b22a5200cbddc5a5ec3f042..bec2ba1adbed02af54572be924dcfbb47bf9c423 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -857,7 +857,7 @@ struct ref *get_remote_ref(struct ref *remote_refs, const char *name)
        struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);
 
        if (!ref)
-               die("Couldn't find remote ref %s\n", name);
+               return NULL;
 
        return copy_ref(ref);
 }
@@ -889,20 +889,24 @@ static struct ref *get_local_ref(const char *name)
 
 int get_fetch_map(struct ref *remote_refs,
                  const struct refspec *refspec,
-                 struct ref ***tail)
+                 struct ref ***tail,
+                 int missing_ok)
 {
        struct ref *ref_map, *rm;
 
        if (refspec->pattern) {
                ref_map = get_expanded_map(remote_refs, refspec);
        } else {
-               ref_map = get_remote_ref(remote_refs,
-                                        refspec->src[0] ?
-                                        refspec->src : "HEAD");
-
-               ref_map->peer_ref = get_local_ref(refspec->dst);
-               if (ref_map->peer_ref && refspec->force)
-                       ref_map->peer_ref->force = 1;
+               const char *name = refspec->src[0] ? refspec->src : "HEAD";
+
+               ref_map = get_remote_ref(remote_refs, name);
+               if (!missing_ok && !ref_map)
+                       die("Couldn't find remote ref %s", name);
+               if (ref_map) {
+                       ref_map->peer_ref = get_local_ref(refspec->dst);
+                       if (ref_map->peer_ref && refspec->force)
+                               ref_map->peer_ref->force = 1;
+               }
        }
 
        for (rm = ref_map; rm; rm = rm->next) {
index c62636d78ef996b97aef73cf37cfb5de3f279145..878b4ecc32a2a4b5be4e0444ae4510e1a7ab01cb 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -67,9 +67,12 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
  * *tail is the pointer to the tail pointer of the list of results
  * beforehand, and will be set to the tail pointer of the list of
  * results afterward.
+ *
+ * missing_ok is usually false, but when we are adding branch.$name.merge
+ * it is Ok if the branch is not at the remote anymore.
  */
 int get_fetch_map(struct ref *remote_refs, const struct refspec *refspec,
-                 struct ref ***tail);
+                 struct ref ***tail, int missing_ok);
 
 struct ref *get_remote_ref(struct ref *remote_refs, const char *name);
 
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..b74fd454f2f09d23ffde7db015d4a7458dfcd348 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;
@@ -268,9 +297,9 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
                                 * commits at the remote end and likely
                                 * we were not up to date to begin with.
                                 */
-                               error("remote '%s' is not a strict "
-                                     "subset of local ref '%s'. "
-                                     "maybe you are not up-to-date and "
+                               error("remote '%s' is not an ancestor of\n"
+                                     " local  '%s'.\n"
+                                     " Maybe you are not up-to-date and "
                                      "need to pull first?",
                                      ref->name,
                                      ref->peer_ref->name);
@@ -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;
 }
index 83a06a7aed84715db191b703e529d3501df0a8f2..f007874cbb034ec9efa7f73c42831e0037d452fa 100644 (file)
@@ -521,13 +521,15 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
                        munmap(idx_map, idx_size);
                        return error("wrong index v2 file size in %s", path);
                }
-               if (idx_size != min_size) {
-                       /* make sure we can deal with large pack offsets */
-                       off_t x = 0x7fffffffUL, y = 0xffffffffUL;
-                       if (x > (x + 1) || y > (y + 1)) {
-                               munmap(idx_map, idx_size);
-                               return error("pack too large for current definition of off_t in %s", path);
-                       }
+               if (idx_size != min_size &&
+                   /*
+                    * make sure we can deal with large pack offsets.
+                    * 31-bit signed offset won't be enough, neither
+                    * 32-bit unsigned one will be.
+                    */
+                   (sizeof(off_t) <= 4)) {
+                       munmap(idx_map, idx_size);
+                       return error("pack too large for current definition of off_t in %s", path);
                }
        }
 
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
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
new file mode 100755 (executable)
index 0000000..ae49424
--- /dev/null
@@ -0,0 +1,93 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Schindelin
+#
+
+test_description='our own option parser'
+
+. ./test-lib.sh
+
+cat > expect.err << EOF
+usage: test-parse-options <options>
+
+    -b, --boolean         get a boolean
+    -i, --integer <n>     get a integer
+    -j <n>                get a integer, too
+
+string options
+    -s, --string <string>
+                          get a string
+    --string2 <str>       get another string
+
+EOF
+
+test_expect_success 'test help' '
+       ! test-parse-options -h > output 2> output.err &&
+       test ! -s output &&
+       git diff expect.err output.err
+'
+
+cat > expect << EOF
+boolean: 2
+integer: 1729
+string: 123
+EOF
+
+test_expect_success 'short options' '
+       test-parse-options -s123 -b -i 1729 -b > output 2> output.err &&
+       git diff expect output &&
+       test ! -s output.err
+'
+cat > expect << EOF
+boolean: 2
+integer: 1729
+string: 321
+EOF
+
+test_expect_success 'long options' '
+       test-parse-options --boolean --integer 1729 --boolean --string2=321 \
+               > output 2> output.err &&
+       test ! -s output.err &&
+       git diff expect output
+'
+
+cat > expect << EOF
+boolean: 1
+integer: 13
+string: 123
+arg 00: a1
+arg 01: b1
+arg 02: --boolean
+EOF
+
+test_expect_success 'intermingled arguments' '
+       test-parse-options a1 --string 123 b1 --boolean -j 13 -- --boolean \
+               > output 2> output.err &&
+       test ! -s output.err &&
+       git diff expect output
+'
+
+cat > expect << EOF
+boolean: 0
+integer: 2
+string: (not set)
+EOF
+
+test_expect_success 'unambiguously abbreviated option' '
+       test-parse-options --int 2 --boolean --no-bo > output 2> output.err &&
+       test ! -s output.err &&
+       git diff expect output
+'
+
+test_expect_success 'unambiguously abbreviated option with "="' '
+       test-parse-options --int=2 > output 2> output.err &&
+       test ! -s output.err &&
+       git diff expect output
+'
+
+test_expect_failure 'ambiguously abbreviated option' '
+       test-parse-options --strin 123;
+        test $? != 129
+'
+
+test_done
index 552af1c4d2f7ecc78e3264290e4d82580f464ead..2dbe04fb20aea9a18e6fe751539bcb48e5e7b4d3 100755 (executable)
@@ -44,7 +44,7 @@ test_expect_success setup '
 test_expect_success 'cherry-pick after renaming branch' '
 
        git checkout rename2 &&
-       EDITOR=: VISUAL=: git cherry-pick added &&
+       git cherry-pick added &&
        test -f opos &&
        grep "Add extra line at the end" opos
 
@@ -53,7 +53,7 @@ test_expect_success 'cherry-pick after renaming branch' '
 test_expect_success 'revert after renaming branch' '
 
        git checkout rename1 &&
-       EDITOR=: VISUAL=: git revert added &&
+       git revert added &&
        test -f spoo &&
        ! grep "Add extra line at the end" spoo
 
index 28e9e372f34818ab5427ef0178dc2003a8018656..235f372832cb32aefff0a00c4f2ac0e19de2e55d 100755 (executable)
@@ -154,7 +154,7 @@ test_expect_success 'cherry-pick(U/U)' '
        git reset --hard master &&
        git cherry-pick side^ &&
        git cherry-pick side &&
-       EDITOR=: VISUAL=: git revert HEAD &&
+       git revert HEAD &&
 
        check_encoding 3
 '
@@ -169,7 +169,7 @@ test_expect_success 'cherry-pick(L/L)' '
        git reset --hard master &&
        git cherry-pick side^ &&
        git cherry-pick side &&
-       EDITOR=: VISUAL=: git revert HEAD &&
+       git revert HEAD &&
 
        check_encoding 3 8859
 '
@@ -184,7 +184,7 @@ test_expect_success 'cherry-pick(U/L)' '
        git reset --hard master &&
        git cherry-pick side^ &&
        git cherry-pick side &&
-       EDITOR=: VISUAL=: git revert HEAD &&
+       git revert HEAD &&
 
        check_encoding 3
 '
@@ -200,7 +200,7 @@ test_expect_success 'cherry-pick(L/U)' '
        git reset --hard master &&
        git cherry-pick side^ &&
        git cherry-pick side &&
-       EDITOR=: VISUAL=: git revert HEAD &&
+       git revert HEAD &&
 
        check_encoding 3 8859
 '
index b80c981c165e9c82f56f826a0542f3bef3f13eb3..070c1661b9be8530e619cd0c297673d1e5e958a3 100644 (file)
@@ -1,3 +1,6 @@
+    
+       
+    
 From nobody Mon Sep 17 00:00:00 2001
 From: A U Thor <a.u.thor@example.com>
 Date: Fri, 9 Jun 2006 00:44:16 -0700
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
old mode 100644 (file)
new mode 100755 (executable)
index f64b1cbf75e82c22efc80dc817c340feb036a752..cea9afb764226d3086450cb7c53c881bb9803eec 100755 (executable)
@@ -402,4 +402,11 @@ test_expect_success 'test resetting the index at give paths' '
 
 '
 
+test_expect_success 'resetting an unmodified path is a no-op' '
+       git reset --hard &&
+       git reset -- file1 &&
+       git diff-files --exit-code &&
+       git diff-index --cached --exit-code HEAD
+'
+
 test_done
index f7bad5bb2f20cf274eb30f8ceed34a3f83000989..35fff3ddbad562d264b4a65f43021a5f7f912e70 100755 (executable)
@@ -31,7 +31,6 @@ our \$projects_list = "";
 our \$export_ok = "";
 our \$strict_export = "";
 
-CGI::Carp::set_programname("gitweb/gitweb.cgi");
 EOF
 
        cat >.git/description <<EOF
@@ -558,4 +557,27 @@ test_expect_success \
        'gitweb_run "p=.git;a=tree;opt=--no-merges"'
 test_debug 'cat gitweb.log'
 
+# ----------------------------------------------------------------------
+# gitweb config and repo config
+
+cat >>gitweb_config.perl <<EOF
+
+\$feature{'blame'}{'override'} = 1;
+\$feature{'snapshot'}{'override'} = 1;
+EOF
+
+test_expect_success \
+       'config override: tree view, features disabled in repo config' \
+       'git config gitweb.blame no &&
+        git config gitweb.snapshot none &&
+        gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'config override: tree view, features enabled in repo config' \
+       'git config gitweb.blame yes &&
+        git config gitweb.snapshot "zip,tgz, tbz2" &&
+        gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
 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 &&
diff --git a/test-parse-options.c b/test-parse-options.c
new file mode 100644 (file)
index 0000000..277cfe4
--- /dev/null
@@ -0,0 +1,35 @@
+#include "cache.h"
+#include "parse-options.h"
+
+static int boolean = 0;
+static int integer = 0;
+static char *string = NULL;
+
+int main(int argc, const char **argv)
+{
+       const char *usage[] = {
+               "test-parse-options <options>",
+               NULL
+       };
+       struct option options[] = {
+               OPT_BOOLEAN('b', "boolean", &boolean, "get a boolean"),
+               OPT_INTEGER('i', "integer", &integer, "get a integer"),
+               OPT_INTEGER('j', NULL, &integer, "get a integer, too"),
+               OPT_GROUP("string options"),
+               OPT_STRING('s', "string", &string, "string", "get a string"),
+               OPT_STRING(0, "string2", &string, "str", "get another string"),
+               OPT_END(),
+       };
+       int i;
+
+       argc = parse_options(argc, argv, options, usage, 0);
+
+       printf("boolean: %d\n", boolean);
+       printf("integer: %d\n", integer);
+       printf("string: %s\n", string ? string : "(not set)");
+
+       for (i = 0; i < argc; i++)
+               printf("arg %02d: %s\n", i, argv[i]);
+
+       return 0;
+}
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);