Merge branch 'js/rebase-i-rerere'
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Dec 2007 07:01:02 +0000 (23:01 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Dec 2007 07:01:02 +0000 (23:01 -0800)
* js/rebase-i-rerere:
rebase -i: give rerere a chance

60 files changed:
Documentation/Makefile
Documentation/RelNotes-1.5.3.7.txt [new file with mode: 0644]
Documentation/RelNotes-1.5.4.txt
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/git-branch.txt
Documentation/git-config.txt
Documentation/git-pull.txt
Documentation/git-tag.txt
Documentation/git.txt
Documentation/user-manual.txt
Makefile
builtin-branch.c
builtin-config.c
builtin-fetch--tool.c
builtin-fetch.c
builtin-revert.c
builtin-tag.c
cache.h
color.c
command-list.txt [new file with mode: 0644]
configure.ac
diffcore-break.c
diffcore-rename.c
dir.c
dir.h
generate-cmdlist.sh
git-am.sh
git-cvsimport.perl
git-pull.sh
git-rebase--interactive.sh
git-stash.sh
git-svn.perl
gitk [deleted file]
gitk-git/Makefile [new file with mode: 0644]
gitk-git/gitk [new file with mode: 0644]
gitweb/README
gitweb/gitweb.perl
help.c
parse-options.c
parse-options.h
perl/Git.pm
receive-pack.c
t/t3201-branch-contains.sh [new file with mode: 0755]
t/t4000-diff-format.sh
t/t4001-diff-rename.sh
t/t4008-diff-break-rewrite.sh
t/t4023-diff-rename-typechange.sh [new file with mode: 0755]
t/t4100/t-apply-1.patch
t/t4100/t-apply-2.patch
t/t4100/t-apply-5.patch
t/t4100/t-apply-6.patch
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t9600-cvsimport.sh [new file with mode: 0755]
tree-walk.h
unpack-trees.c
wt-status.c
index d88664177da52ef92c25959ba396b4b823859225..de11ee01927f032c65b0a3516393c4eadf636718 100644 (file)
@@ -122,9 +122,9 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
 
 $(cmds_txt): cmd-list.made
 
-cmd-list.made: cmd-list.perl $(MAN1_TXT)
+cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
        $(RM) $@
-       perl ./cmd-list.perl
+       perl ./cmd-list.perl ../command-list.txt
        date >$@
 
 git.7 git.html: git.txt
diff --git a/Documentation/RelNotes-1.5.3.7.txt b/Documentation/RelNotes-1.5.3.7.txt
new file mode 100644 (file)
index 0000000..2f69061
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.5.3.7 Release Notes
+==========================
+
+Fixes since v1.5.3.6
+--------------------
+
+ * git-send-email added 8-bit contents to the payload without
+   marking it as 8-bit in a CTE header.
+
+ * "git-bundle create a.bndl HEAD" dereferenced the symref and
+   did not record the ref as 'HEAD'; this prevented a bundle
+   from being used as a normal source of git-clone.
+
+ * The code to reject nonsense command line of the form
+   "git-commit -a paths..." and "git-commit --interactive
+   paths..." were broken.
+
+ * Adding a signature that is not ASCII-only to an original
+   commit that is ASCII-only would make the result non-ASCII.
+   "git-format-patch -s" did not mark such a message correctly
+   with MIME encoding header.
+
+ * git-add sometimes did not mark the resulting index entry
+   stat-clean.  This affected only cases when adding the
+   contents with the same length as the previously staged
+   contents, and the previous staging made the index entry
+   "racily clean".
+
+ * git-commit did not honor GIT_INDEX_FILE the user had in the
+   environment.
+
+ * When checking out a revision, git-checkout did not report where the
+   updated HEAD is if you happened to have a file called HEAD in the
+   work tree.
+
+ * "git-rev-list --objects" mishandled a tree that points at a
+   submodule.
+
+ * "git cvsimport" was not ready for packed refs that "git gc" can
+   produce and gave incorrect results.
+
+ * Many scripted Porcelains were confused when you happened to have a
+   file called "HEAD" in your work tree.
+
+Also it contains updates to the user manual and documentation.
index c9c537649f4f1c56b8689aa47d9cdb2a2402d4a8..44f5043ce7843e96349ad2cb4f19d0246424ff87 100644 (file)
 GIT v1.5.4 Release Notes
 ========================
 
+Removal
+-------
+
+ * "git svnimport" was removed in favor of "git svn".
+
+
+Deprecation notices
+-------------------
+
+ * Next feature release of git (this change is scheduled for v1.5.5 but
+   it could slip) will by default install dashed form of commands
+   (e.g. "git-commit") outside of users' normal $PATH, and will install
+   only selected commands ("git" itself, and "gitk") in $PATH.  This
+   implies:
+
+   - Using dashed form of git commands (e.g. "git-commit") from the
+     command line has been informally deprecated since early 2006, but
+     now it officially is, and will be removed in the future.  Use
+     dashless form (e.g. "git commit") instead.
+
+   - Using dashed from from your scripts, without first prepending the
+     return value from "git --exec-path" to the scripts' PATH, has been
+     informally deprecated since early 2006, but now it officially is.
+
+   - Use of dashed form with "PATH=$(git --exec-path):$PATH; export
+     PATH" early in your script is not deprecated with this change.
+
+  Users are strongly encouraged to adjust their habits and scripts now
+  to prepare for this.
+
+ * The post-receive hook was introduced in March 2007 to supersede
+   post-update hook, primarily to overcome the command line length
+   limitation of the latter.  Use of post-update hook will be deprecated
+   in future versions of git, perhaps in v1.5.5.
+
+ * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found
+   option, and will be removed in the future.
+
+ * "git peek-remote" is deprecated, as "git ls-remote" was written in C
+   and works for all transports, and will be removed in the future.
+
+
 Updates since v1.5.3
 --------------------
 
  * Comes with much improved gitk.
 
- * Comes with git-gui 0.9.0 with i18n.
+ * Comes with "git gui" 0.9.0 with i18n.
+
+ * progress display from many commands are a lot nicer to the eye.
+   Transfer commands show throughput data.
+
+ * many commands that pay attention to per-directory .gitignore now do
+   so lazily, which makes the usual case go much faster.
 
- * git-lost-found was deprecated in favor of git-fsck's --lost-found
-   option.
+ * Output processing for '--pretty=format:<user format>' has been
+   optimized.
 
- * git-peek-remote is deprecated, as git-ls-remote was written in C and
-   works for all transports.
+ * Rename detection of diff family, while detecting exact matches, has
+   been greatly optimized.
 
- * "progress display" from many commands are a lot nicer to the
-   eye.  Transfer commands show throughput data.
+ * Rename detection of diff family tries to make more naturally looking
+   pairing.  Earlier if more than one identical rename sources were
+   found in the preimage, they were picked pretty much at random.
 
- * git-reset is now built-in and its output can be squelched with -q.
+ * "git reset" is now built-in and its output can be squelched with -q.
 
- * git-send-email can optionally talk over ssmtp and use SMTP-AUTH.
+ * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH.
 
- * git-rebase learned --whitespace option.
+ * "git rebase" learned --whitespace option.
 
- * In git-rebase, when you decide not to replay a particular change
-   after the command stopped with a conflict, you can say "git-rebase
+ * In "git rebase", when you decide not to replay a particular change
+   after the command stopped with a conflict, you can say "git rebase
    --skip" without first running "git reset --hard", as the command now
    runs it for you.
 
- * git-remote knows --mirror mode.
+ * "git merge" can call the "post-merge" hook.
 
- * git-merge can call the "post-merge" hook.
+ * "git pack-objects" can optionally run deltification with multiple
+   threads.
 
- * git-pack-objects can optionally run deltification with multiple threads.
-
- * git-archive can optionally substitute keywords in files marked with
+ * "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 cherry-pick" made a misguided attempt to repeat the original
+   command line in the generated log message, when told to cherry-pick a
+   commit by naming a tag that points at it.  It does not anymore.
 
- * 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 for-each-ref" learned %(xxxdate:<dateformat>) syntax to show the
+   various date fields in different formats.
 
- * You need to explicitly set clean.requireForce to "false" to allow
-   git-clean without -f to do any damage (lack of the configuration
-   variable used to mean "do not require", but we now use the safer
-   default).
+ * "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-clean has been rewritten in C.
+ * You need to explicitly set clean.requireForce to "false" to allow
+   "git clean" without -f to do any damage (lack of the configuration
+   variable used to mean "do not require -f option to lose untracked
+   files", but we now use the safer default).
 
- * git-push has been rewritten in C.
+ * "git clean" has been rewritten in C.
 
- * git-push learned --dry-run option to show what would happen
-   if a push is run.
+ * "git push" learned --dry-run option to show what would happen if a
+   push is run.
 
- * git-push does not update a tracking ref on the pushing side when the
+ * "git push" does not update a tracking ref on the local side when the
    remote refused to update the corresponding ref.
 
- * git-push learned --mirror option.  This is to push the local refs
+ * "git push" learned --mirror option.  This is to push the local refs
    one-to-one to the remote, and deletes refs from the remote that do
    not exist anymore in the repository on the pushing side.
 
- * git-remote learned "rm" subcommand.
+ * "git remote" knows --mirror mode.  This is to set up configuration to
+   push into a remote repository to store local branch heads to the same
+   branch on the remote side, and remove branch heads locally removed
+   from local repository at the same time.  Suitable for pushing into a
+   back-up repository.
 
- * git-rebase --interactive mode can now work on detached HEAD.
+ * "git remote" learned "rm" subcommand.
 
- * git-cvsserver can be run via git-shell.
+ * "git rebase --interactive" mode can now work on detached HEAD.
 
- * git-am and git-rebase are far less verbose.
+ * "git cvsserver" can be run via "git shell".
 
- * git-pull learned to pass --[no-]ff option to underlying git-merge.
+ * "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.
 
- * "git log" learned --early-output option to help interactive
-   GUI implementations.
+ * "git log" learned --early-output option to help interactive GUI
+   implementations.
+
+ * "git bisect" learned "skip" action to mark untestable commits.
+
+ * "git format-patch" learned "format.numbered" configuration variable
+   to automatically turn --numbered option on when more than one commits
+   are formatted.
 
- * git-svnimport was removed in favor of git-svn.
+ * "git ls-files" learned "--exclude-standard" to use the canned set of
+   exclude files.
 
- * git-bisect learned "skip" action to mark untestable commits.
+ * "git rebase" now detaches head during its operation, so after a
+   successful "git rebase" operation, the reflog entry branch@{1} for
+   the current branch points at the commit before the rebase was
+   started.
 
- * git-format-patch learned "format.numbered" configuration variable
-   to automatically turn --numbered option on when more than one
-   commits are formatted.
+ * "git tag -a -f existing" begins the editor session using the existing
+   annotation message.
 
- * git-ls-files learned "--exclude-standard" to use the canned
-   set of exclude files.
+ * "git tag -m one -m bar" (multiple -m options) behaves similarly to
+   "git commit"; the parameters to -m options are formatted as separate
+   paragraphs.
 
- * git-rebase now detaches head during its operation, so after a
-   successful "git rebase" operation, the reflog entry branch@{1}
-   for the current branch points at the commit before the rebase
-   was started.
+ * "git cvsexportcommit" learned -w option to specify and switch to the
+   CVS working directory.
 
- * "git-tag -a -f existing" begins the editor session using the
-   existing annotation message.
+ * "git checkout" from a subdirectory learned to use "../path" to allow
+   checking out a path outside the current directory without cd'ing up.
 
- * "git cvsexportcommit" learned -w option to specify and switch
-   to the CVS working directory.
+ * "git send-email --dry-run" shows full headers for easier diagnosis.
 
- * "git checkout" from a subdirectory learned to use "../path"
-   to allow checking out a path outside the current directory
-   without cd'ing up.
+ * "git merge-ours" is now built-in.
 
- * "git send-email --dry-run" shows full headers for easier
-   diagnosis.
+ * "git svn" learned "info" and "show-externals" subcommands.
 
- * "git merge-ours" is built-in.
+ * "git svn" run from a subdirectory failed to read settings from the
+   .git/config.
 
- * "git svn" learned "info" subcommand.
+ * "git svn" learned --use-log-author option, which picks up more
+   descriptive name from From: and Signed-off-by: lines in the commit
+   message.
 
- * "git status" from a subdirectory now shows relative paths
-   which makes copy-and-pasting for git-checkout/git-add/git-rm
-   easier.
+ * "git status" from a subdirectory now shows relative paths which makes
+   copy-and-pasting for git-checkout/git-add/git-rm easier.
 
- * Output processing for '--pretty=format:<user format>' has
-   been optimized.
+ * "git checkout" from and to detached HEAD leaves a bit more
+   information in the reflog.
 
- * Rename detection diff family, while detecting exact matches,
-   has been greatly optimized.
+ * "git branch" learned --contains option, to show only branches that
+   can reach a given commit.
 
  * Example update and post-receive hooks have been improved.
 
+ * "git push" can remove a corrupt ref at the remote site with the usual
+   ":ref" refspec.
+
  * In addition there are quite a few internal clean-ups. Notably
 
    - many fork/exec have been replaced with run-command API,
@@ -140,15 +206,14 @@ Fixes since v1.5.3
 All of the fixes in v1.5.3 maintenance series are included in
 this release, unless otherwise noted.
 
- * git-svn talking with the SVN over http will correctly quote branch
-   and project names.
+These fixes are only in v1.5.4 and not backported to v1.5.3 maintenance
+series.
 
- * "git rev-list --objects A..B" choked when the lower boundary
-   of the range involved a subproject.  This fix is also queued
-   for 'maint' (but not in there yet).
+ * "git svn" talking with the SVN over http will correctly quote branch
+   and project names.
 
 --
 exec >/var/tmp/1
-O=v1.5.3.6-950-gda03a58
+O=v1.5.3.7-966-g6bda21b
 echo O=`git describe refs/heads/master`
 git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index b709551726989036c7232cb2a5f72b9578b5e4af..c2d55cdb5e8720be8892abf90b10379ca9cf3d65 100755 (executable)
@@ -28,8 +28,8 @@ sub format_one {
        }
        if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) {
                print $out "gitlink:$name\[1\]::\n\t";
-               if ($attr) {
-                       print $out "($attr) ";
+               if ($attr =~ / deprecated /) {
+                       print $out "(deprecated) ";
                }
                print $out "$text.\n\n";
        }
@@ -39,12 +39,13 @@ sub format_one {
 }
 
 my %cmds = ();
-while (<DATA>) {
+for (sort <>) {
        next if /^#/;
 
        chomp;
        my ($name, $cat, $attr) = /^(\S+)\s+(.*?)(?:\s+(.*))?$/;
-       push @{$cmds{$cat}}, [$name, $attr];
+       $attr = '' unless defined $attr;
+       push @{$cmds{$cat}}, [$name, " $attr "];
 }
 
 for my $cat (qw(ancillaryinterrogators
@@ -71,133 +72,3 @@ sub format_one {
                rename "$out+", "$out";
        }
 }
-
-# The following list is sorted with "sort -d" to make it easier
-# to find entry in the resulting git.html manual page.
-__DATA__
-git-add                                 mainporcelain
-git-am                                  mainporcelain
-git-annotate                            ancillaryinterrogators
-git-apply                               plumbingmanipulators
-git-archimport                          foreignscminterface
-git-archive                             mainporcelain
-git-bisect                              mainporcelain
-git-blame                               ancillaryinterrogators
-git-branch                              mainporcelain
-git-bundle                              mainporcelain
-git-cat-file                            plumbinginterrogators
-git-check-attr                          purehelpers
-git-checkout                            mainporcelain
-git-checkout-index                      plumbingmanipulators
-git-check-ref-format                    purehelpers
-git-cherry                              ancillaryinterrogators
-git-cherry-pick                         mainporcelain
-git-citool                              mainporcelain
-git-clean                               mainporcelain
-git-clone                               mainporcelain
-git-commit                              mainporcelain
-git-commit-tree                         plumbingmanipulators
-git-config                              ancillarymanipulators
-git-count-objects                       ancillaryinterrogators
-git-cvsexportcommit                     foreignscminterface
-git-cvsimport                           foreignscminterface
-git-cvsserver                           foreignscminterface
-git-daemon                              synchingrepositories
-git-describe                            mainporcelain
-git-diff                                mainporcelain
-git-diff-files                          plumbinginterrogators
-git-diff-index                          plumbinginterrogators
-git-diff-tree                           plumbinginterrogators
-git-fast-import                                ancillarymanipulators
-git-fetch                               mainporcelain
-git-fetch-pack                          synchingrepositories
-git-filter-branch                       ancillarymanipulators
-git-fmt-merge-msg                       purehelpers
-git-for-each-ref                        plumbinginterrogators
-git-format-patch                        mainporcelain
-git-fsck                               ancillaryinterrogators
-git-gc                                  mainporcelain
-git-get-tar-commit-id                   ancillaryinterrogators
-git-grep                                mainporcelain
-git-gui                                 mainporcelain
-git-hash-object                         plumbingmanipulators
-git-http-fetch                          synchelpers
-git-http-push                           synchelpers
-git-imap-send                           foreignscminterface
-git-index-pack                          plumbingmanipulators
-git-init                                mainporcelain
-git-instaweb                            ancillaryinterrogators
-gitk                                    mainporcelain
-git-log                                 mainporcelain
-git-lost-found                          ancillarymanipulators  deprecated
-git-ls-files                            plumbinginterrogators
-git-ls-remote                           plumbinginterrogators
-git-ls-tree                             plumbinginterrogators
-git-mailinfo                            purehelpers
-git-mailsplit                           purehelpers
-git-merge                               mainporcelain
-git-merge-base                          plumbinginterrogators
-git-merge-file                          plumbingmanipulators
-git-merge-index                         plumbingmanipulators
-git-merge-one-file                      purehelpers
-git-mergetool                           ancillarymanipulators
-git-merge-tree                          ancillaryinterrogators
-git-mktag                               plumbingmanipulators
-git-mktree                              plumbingmanipulators
-git-mv                                  mainporcelain
-git-name-rev                            plumbinginterrogators
-git-pack-objects                        plumbingmanipulators
-git-pack-redundant                      plumbinginterrogators
-git-pack-refs                           ancillarymanipulators
-git-parse-remote                        synchelpers
-git-patch-id                            purehelpers
-git-peek-remote                         purehelpers    deprecated
-git-prune                               ancillarymanipulators
-git-prune-packed                        plumbingmanipulators
-git-pull                                mainporcelain
-git-push                                mainporcelain
-git-quiltimport                         foreignscminterface
-git-read-tree                           plumbingmanipulators
-git-rebase                              mainporcelain
-git-receive-pack                        synchelpers
-git-reflog                              ancillarymanipulators
-git-relink                              ancillarymanipulators
-git-remote                              ancillarymanipulators
-git-repack                              ancillarymanipulators
-git-request-pull                        foreignscminterface
-git-rerere                              ancillaryinterrogators
-git-reset                               mainporcelain
-git-revert                              mainporcelain
-git-rev-list                            plumbinginterrogators
-git-rev-parse                           ancillaryinterrogators
-git-rm                                  mainporcelain
-git-runstatus                           ancillaryinterrogators
-git-send-email                          foreignscminterface
-git-send-pack                           synchingrepositories
-git-shell                               synchelpers
-git-shortlog                            mainporcelain
-git-show                                mainporcelain
-git-show-branch                         ancillaryinterrogators
-git-show-index                          plumbinginterrogators
-git-show-ref                            plumbinginterrogators
-git-sh-setup                            purehelpers
-git-stash                               mainporcelain
-git-status                              mainporcelain
-git-stripspace                          purehelpers
-git-submodule                           mainporcelain
-git-svn                                 foreignscminterface
-git-symbolic-ref                        plumbingmanipulators
-git-tag                                 mainporcelain
-git-tar-tree                            plumbinginterrogators  deprecated
-git-unpack-file                         plumbinginterrogators
-git-unpack-objects                      plumbingmanipulators
-git-update-index                        plumbingmanipulators
-git-update-ref                          plumbingmanipulators
-git-update-server-info                  synchingrepositories
-git-upload-archive                      synchelpers
-git-upload-pack                         synchelpers
-git-var                                 plumbinginterrogators
-git-verify-pack                         plumbinginterrogators
-git-verify-tag                          ancillaryinterrogators
-git-whatchanged                         ancillaryinterrogators
-git-write-tree                          plumbingmanipulators
index 39d1ef5298bd81868079b8d82ba56157e4785b5d..4124ad2970cfd76eb6592658c02007188c48003a 100644 (file)
@@ -346,6 +346,13 @@ branch.<name>.mergeoptions::
        option values containing whitespace characters are currently not
        supported.
 
+branch.<name>.rebase::
+       When true, rebase the branch <name> on top of the fetched branch,
+       instead of merging the default branch from the default remote.
+       *NOTE*: this is a possibly dangerous operation; do *not* use
+       it unless you understand the implications (see gitlink:git-rebase[1]
+       for details).
+
 clean.requireForce::
        A boolean to make git-clean do nothing unless given -f
        or -n.   Defaults to true.
index f87b6968b4808ebc7f831c814ca7e62924a19642..d3f21c797596e9dc633293e0cca4a172940bd4af 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 [verse]
 'git-branch' [--color | --no-color] [-r | -a]
           [-v [--abbrev=<length> | --no-abbrev]]
+          [--contains <commit>]
 'git-branch' [--track | --no-track] [-l] [-f] <branchname> [<start-point>]
 'git-branch' (-m | -M) [<oldbranch>] <newbranch>
 'git-branch' (-d | -D) [-r] <branchname>...
@@ -20,6 +21,9 @@ With no arguments given a list of existing branches
 will be shown, the current branch will be highlighted with an asterisk.
 Option `-r` causes the remote-tracking branches to be listed,
 and option `-a` shows both.
+With `--contains <commit>`, shows only the branches that
+contains the named commit (in other words, the branches whose
+tip commits are descendant of the named commit).
 
 In its second form, a new branch named <branchname> will be created.
 It will start out with a head equal to the one given as <start-point>.
index a592b61e2fe998525d0978547b3ea7e4ad2d72ac..7640450787064aac7211b5a3d5504275b62119ff 100644 (file)
@@ -20,6 +20,7 @@ SYNOPSIS
 'git-config' [<file-option>] --rename-section old_name new_name
 'git-config' [<file-option>] --remove-section name
 'git-config' [<file-option>] [-z|--null] -l | --list
+'git-config' [<file-option>] --get-color name [default]
 
 DESCRIPTION
 -----------
@@ -134,6 +135,12 @@ See also <<FILES>>.
        output without getting confused e.g. by values that
        contain line breaks.
 
+--get-color name default::
+
+       Find the color configured for `name` (e.g. `color.diff.new`) and
+       output it as the ANSI color escape sequence to the standard
+       output.  The optional `default` parameter is used instead, if
+       there is no color configured for `name`.
 
 [[FILES]]
 FILES
@@ -292,6 +299,15 @@ To add a new proxy, without altering any of the existing ones, use
 % git config core.gitproxy '"proxy-command" for example.com'
 ------------
 
+An example to use customized color from the configuration in your
+script:
+
+------------
+#!/bin/sh
+WS=$(git config --get-color color.diff.whitespace "blue reverse")
+RESET=$(git config --get-color "" "reset")
+echo "${WS}your whitespace color or blue reverse${RESET}"
+------------
 
 include::config.txt[]
 
index e1eb2c1d0037f06dbf2b5cc0bde84918157ee2bf..d4d26afea0fef29cd3e0ca2e7bb89f71d08d4c22 100644 (file)
@@ -33,6 +33,16 @@ include::urls-remotes.txt[]
 
 include::merge-strategies.txt[]
 
+\--rebase::
+       Instead of a merge, perform a rebase after fetching.
+       *NOTE:* This is a potentially _dangerous_ mode of operation.
+       It rewrites history, which does not bode well when you
+       published that history already.  Do *not* use this option
+       unless you have read gitlink:git-rebase[1] carefully.
+
+\--no-rebase::
+       Override earlier \--rebase.
+
 DEFAULT BEHAVIOUR
 -----------------
 
index 10d3e3fa950e00b6004f968ff2c41477e1d57612..784ec6d4c29879e48c13834819048d4a640dc32d 100644 (file)
@@ -65,7 +65,9 @@ OPTIONS
        Typing "git tag" without arguments, also lists all tags.
 
 -m <msg>::
-       Use the given tag message (instead of prompting)
+       Use the given tag message (instead of prompting).
+       If multiple `-m` options are given, there values are
+       concatenated as separate paragraphs.
 
 -F <file>::
        Take the tag message from the given file.  Use '-' to
index 546020100a533b99126adb1635299e7d025bca4b..9ff4659d8c021465758332270aeaa2c88a593b14 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.7.txt[1.5.3.7],
   link:RelNotes-1.5.3.6.txt[1.5.3.6],
   link:RelNotes-1.5.3.5.txt[1.5.3.5],
   link:RelNotes-1.5.3.4.txt[1.5.3.4],
index 0aaed10c761c9256dbb1c2f2dfffeaa2a3bd401f..93a47b439b4504467fc5ba7612b8205367f347c2 100644 (file)
@@ -3707,7 +3707,7 @@ should use the `--remove` and `--add` flags respectively.
 NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
 necessarily be removed: if the files still exist in your directory
 structure, the index will be updated with their new status, not
-removed. The only thing `--remove` means is that update-cache will be
+removed. The only thing `--remove` means is that update-index will be
 considering a removed file to be a valid thing, and if the file really
 does not exist any more, it will update the index accordingly.
 
index ccf522adce9fccb8b94ebc26d686a995d900ebbf..d5d2c7c0c0d76addd344559c8546c2b44f5b89e9 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -111,7 +111,7 @@ all::
 # times (my ext3 doesn't).
 #
 # Define USE_STDEV below if you want git to care about the underlying device
-# change being considered an inode change from the update-cache perspective.
+# change being considered an inode change from the update-index perspective.
 #
 # Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
 #
@@ -259,7 +259,7 @@ EXTRA_PROGRAMS =
 BUILT_INS = \
        git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
        git-get-tar-commit-id$X git-init$X git-repo-config$X \
-       git-fsck-objects$X git-cherry-pick$X \
+       git-fsck-objects$X git-cherry-pick$X git-peek-remote$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
@@ -269,9 +269,6 @@ ALL_PROGRAMS += git-merge-subtree$X
 
 # what 'all' will build but not install in gitexecdir
 OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
-ifndef NO_TCLTK
-OTHER_PROGRAMS += gitk-wish
-endif
 
 # Set paths to tools early so that they can be used for version tests.
 ifndef SHELL_PATH
@@ -775,6 +772,7 @@ endif
 all::
 ifndef NO_TCLTK
        $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) all
+       $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
@@ -782,12 +780,6 @@ endif
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-gitk-wish: gitk GIT-GUI-VARS
-       $(QUIET_GEN)$(RM) $@ $@+ && \
-       sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
-       chmod +x $@+ && \
-       mv -f $@+ $@
-
 git.o: git.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                $(ALL_CFLAGS) -c $(filter %.c,$^)
@@ -804,7 +796,7 @@ git-merge-subtree$X: git-merge-recursive$X
 $(BUILT_INS): git$X
        $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
 
-common-cmds.h: ./generate-cmdlist.sh
+common-cmds.h: ./generate-cmdlist.sh command-list.txt
 
 common-cmds.h: $(wildcard Documentation/git-*.txt)
        $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
@@ -1031,7 +1023,7 @@ install: all
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
        $(MAKE) -C perl prefix='$(prefix_SQ)' install
 ifndef NO_TCLTK
-       $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+       $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui install
 endif
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
@@ -1124,7 +1116,7 @@ clean:
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
 ifndef NO_TCLTK
-       $(RM) gitk-wish
+       $(MAKE) -C gitk-git clean
        $(MAKE) -C git-gui clean
 endif
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
@@ -1146,7 +1138,7 @@ check-docs::
                esac ; \
                test -f "Documentation/$$v.txt" || \
                echo "no doc: $$v"; \
-               sed -e '1,/^__DATA__/d' Documentation/cmd-list.perl | \
+               sed -e '/^#/d' command-list.txt | \
                grep -q "^$$v[  ]" || \
                case "$$v" in \
                git) ;; \
@@ -1154,9 +1146,9 @@ check-docs::
                esac ; \
        done; \
        ( \
-               sed -e '1,/^__DATA__/d' \
+               sed -e '/^#/d' \
                    -e 's/[     ].*//' \
-                   -e 's/^/listed /' Documentation/cmd-list.perl; \
+                   -e 's/^/listed /' command-list.txt; \
                ls -1 Documentation/git*txt | \
                sed -e 's|Documentation/|documented |' \
                    -e 's/\.txt//'; \
index 2694c9cf497bb0f20c771a560d65eae5702bf931..c64768beb2c112fc32a65fb4070f01331ebf1540 100644 (file)
@@ -184,9 +184,30 @@ struct ref_item {
 struct ref_list {
        int index, alloc, maxwidth;
        struct ref_item *list;
+       struct commit_list *with_commit;
        int kinds;
 };
 
+static int has_commit(const unsigned char *sha1, struct commit_list *with_commit)
+{
+       struct commit *commit;
+
+       if (!with_commit)
+               return 1;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+       while (with_commit) {
+               struct commit *other;
+
+               other = with_commit->item;
+               with_commit = with_commit->next;
+               if (in_merge_bases(other, &commit, 1))
+                       return 1;
+       }
+       return 0;
+}
+
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
        struct ref_list *ref_list = (struct ref_list*)(cb_data);
@@ -206,6 +227,10 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
                refname += 10;
        }
 
+       /* Filter with with_commit if specified */
+       if (!has_commit(sha1, ref_list->with_commit))
+               return 0;
+
        /* Don't add types the caller doesn't want */
        if ((kind & ref_list->kinds) == 0)
                return 0;
@@ -296,19 +321,20 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
        }
 }
 
-static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
+static void print_ref_list(int kinds, int detached, int verbose, int abbrev, struct commit_list *with_commit)
 {
        int i;
        struct ref_list ref_list;
 
        memset(&ref_list, 0, sizeof(ref_list));
        ref_list.kinds = kinds;
+       ref_list.with_commit = with_commit;
        for_each_ref(append_ref, &ref_list);
 
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached) {
+       if (detached && has_commit(head_sha1, with_commit)) {
                struct ref_item item;
                item.name = xstrdup("(no branch)");
                item.kind = REF_LOCAL_BRANCH;
@@ -505,12 +531,29 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                die("Branch is renamed, but update of config-file failed");
 }
 
+static int opt_parse_with_commit(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               die("malformed object name %s", arg);
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               die("no such commit %s", arg);
+       commit_list_insert(commit, opt->value);
+       return 0;
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
        int delete = 0, rename = 0, force_create = 0;
        int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
        int reflog = 0, track;
        int kinds = REF_LOCAL_BRANCH;
+       struct commit_list *with_commit = NULL;
 
        struct option options[] = {
                OPT_GROUP("Generic options"),
@@ -519,6 +562,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN( 0 , "color",  &branch_use_color, "use colored output"),
                OPT_SET_INT('r', NULL,     &kinds, "act on remote-tracking branches",
                        REF_REMOTE_BRANCH),
+               OPT_CALLBACK(0, "contains", &with_commit, "commit",
+                            "print only branches that contain the commit",
+                            opt_parse_with_commit),
+               {
+                       OPTION_CALLBACK, 0, "with", &with_commit, "commit",
+                       "print only branches that contain the commit",
+                       PARSE_OPT_HIDDEN, opt_parse_with_commit,
+               },
                OPT__ABBREV(&abbrev),
 
                OPT_GROUP("Specific git-branch actions:"),
@@ -554,7 +605,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        if (delete)
                return delete_branches(argc, argv, delete > 1, kinds);
        else if (argc == 0)
-               print_ref_list(kinds, detached, verbose, abbrev);
+               print_ref_list(kinds, detached, verbose, abbrev, with_commit);
        else if (rename && (argc == 1))
                rename_branch(head, argv[0], rename > 1);
        else if (rename && (argc == 2))
index f672c9cccaeb460a0f0c55ad3e9d2ada95ed1e81..4c9ded3b1a146c20d24559bbbee267e9be743fd2 100644 (file)
@@ -1,8 +1,9 @@
 #include "builtin.h"
 #include "cache.h"
+#include "color.h"
 
 static const char git_config_set_usage[] =
-"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list";
+"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default]";
 
 static char *key;
 static regex_t *key_regexp;
@@ -161,6 +162,53 @@ char *normalize_value(const char *key, const char *value)
        return normalized;
 }
 
+static int get_color_found;
+static const char *get_color_slot;
+static char parsed_color[COLOR_MAXLEN];
+
+static int git_get_color_config(const char *var, const char *value)
+{
+       if (!strcmp(var, get_color_slot)) {
+               color_parse(value, var, parsed_color);
+               get_color_found = 1;
+       }
+       return 0;
+}
+
+static int get_color(int argc, const char **argv)
+{
+       /*
+        * grab the color setting for the given slot from the configuration,
+        * or parse the default value if missing, and return ANSI color
+        * escape sequence.
+        *
+        * e.g.
+        * git config --get-color color.diff.whitespace "blue reverse"
+        */
+       const char *def_color = NULL;
+
+       switch (argc) {
+       default:
+               usage(git_config_set_usage);
+       case 2:
+               def_color = argv[1];
+               /* fallthru */
+       case 1:
+               get_color_slot = argv[0];
+               break;
+       }
+
+       get_color_found = 0;
+       parsed_color[0] = '\0';
+       git_config(git_get_color_config);
+
+       if (!get_color_found && def_color)
+               color_parse(def_color, "command line", parsed_color);
+
+       fputs(parsed_color, stdout);
+       return 0;
+}
+
 int cmd_config(int argc, const char **argv, const char *prefix)
 {
        int nongit = 0;
@@ -234,8 +282,9 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                                return 1;
                        }
                        return 0;
-               }
-               else
+               } else if (!strcmp(argv[1], "--get-color")) {
+                       return get_color(argc-2, argv+2);
+               } else
                        break;
                argc--;
                argv++;
index ed60847d9fcd0ad8803c432af8d0bc2dd3567768..7460ab7fce2a4e6a7e014f448819140e2204ccb7 100644 (file)
@@ -511,10 +511,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
        if (!strcmp("append-fetch-head", argv[1])) {
                int result;
                FILE *fp;
+               char *filename;
 
                if (argc != 8)
                        return error("append-fetch-head takes 6 args");
-               fp = fopen(git_path("FETCH_HEAD"), "a");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "a");
+               if (!fp)
+                       return error("cannot open %s: %s\n", filename, strerror(errno));
                result = append_fetch_head(fp, argv[2], argv[3],
                                           argv[4], argv[5],
                                           argv[6], !!argv[7][0],
@@ -525,10 +529,14 @@ int cmd_fetch__tool(int argc, const char **argv, const char *prefix)
        if (!strcmp("native-store", argv[1])) {
                int result;
                FILE *fp;
+               char *filename;
 
                if (argc != 5)
                        return error("fetch-native-store takes 3 args");
-               fp = fopen(git_path("FETCH_HEAD"), "a");
+               filename = git_path("FETCH_HEAD");
+               fp = fopen(filename, "a");
+               if (!fp)
+                       return error("cannot open %s: %s\n", filename, strerror(errno));
                result = fetch_native_store(fp, argv[2], argv[3], argv[4],
                                            verbose, force);
                fclose(fp);
index 31e138eab82a4a710916ff88386bd82bcf2f11d6..de9947e7ac2cc1236909fab813198b3df93bd39e 100644 (file)
@@ -255,7 +255,7 @@ static int update_local_ref(struct ref *ref,
        }
 }
 
-static void store_updated_refs(const char *url, struct ref *ref_map)
+static int store_updated_refs(const char *url, struct ref *ref_map)
 {
        FILE *fp;
        struct commit *commit;
@@ -263,8 +263,11 @@ static void store_updated_refs(const char *url, struct ref *ref_map)
        char note[1024];
        const char *what, *kind;
        struct ref *rm;
+       char *filename = git_path("FETCH_HEAD");
 
-       fp = fopen(git_path("FETCH_HEAD"), "a");
+       fp = fopen(filename, "a");
+       if (!fp)
+               return error("cannot open %s: %s\n", filename, strerror(errno));
        for (rm = ref_map; rm; rm = rm->next) {
                struct ref *ref = NULL;
 
@@ -335,6 +338,7 @@ static void store_updated_refs(const char *url, struct ref *ref_map)
                }
        }
        fclose(fp);
+       return 0;
 }
 
 /*
@@ -404,7 +408,7 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map)
        if (ret)
                ret = transport_fetch_refs(transport, ref_map);
        if (!ret)
-               store_updated_refs(transport->url, ref_map);
+               ret |= store_updated_refs(transport->url, ref_map);
        transport_unlock_pack(transport);
        return ret;
 }
@@ -487,8 +491,13 @@ static int do_fetch(struct transport *transport,
                die("Don't know how to fetch from %s", transport->url);
 
        /* if not appending, truncate FETCH_HEAD */
-       if (!append)
-               fclose(fopen(git_path("FETCH_HEAD"), "w"));
+       if (!append) {
+               char *filename = git_path("FETCH_HEAD");
+               FILE *fp = fopen(filename, "w");
+               if (!fp)
+                       return error("cannot open %s: %s\n", filename, strerror(errno));
+               fclose(fp);
+       }
 
        ref_map = get_ref_map(transport, refs, ref_count, tags, &autotags);
 
index a0586f9753189d13d5af1c790ea875a5f383f34f..4bf8eb2f58ac5a81a9d6538e31f2da661db4c3e9 100644 (file)
@@ -224,6 +224,27 @@ static int merge_recursive(const char *base_sha1,
        return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN | RUN_GIT_CMD);
 }
 
+static char *help_msg(const unsigned char *sha1)
+{
+       static char helpbuf[1024];
+       char *msg = getenv("GIT_CHERRY_PICK_HELP");
+
+       if (msg)
+               return msg;
+
+       strcpy(helpbuf, "  After resolving the conflicts,\n"
+              "mark the corrected paths with 'git add <paths>' "
+              "or 'git rm <paths>' and commit the result.");
+
+       if (action == CHERRY_PICK) {
+               sprintf(helpbuf + strlen(helpbuf),
+                       "\nWhen commiting, use the option "
+                       "'-c %s' to retain authorship and message.",
+                       find_unique_abbrev(sha1, DEFAULT_ABBREV));
+       }
+       return helpbuf;
+}
+
 static int revert_or_cherry_pick(int argc, const char **argv)
 {
        unsigned char head[20];
@@ -352,16 +373,8 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                }
                if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
                        die ("Error wrapping up %s", defmsg);
-               fprintf(stderr, "Automatic %s failed.  "
-                       "After resolving the conflicts,\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 "
-                               "'-c %s' to retain authorship and message.\n",
-                               find_unique_abbrev(commit->object.sha1,
-                                       DEFAULT_ABBREV));
-               }
+               fprintf(stderr, "Automatic %s failed.%s\n",
+                       me, help_msg(commit->object.sha1));
                exit(1);
        }
        if (close(msg_fd) || commit_lock_file(&msg_file) < 0)
index cbb0f04e85627a5509bb2ba8cbf04a54d4d20c41..114c684d246e975852bb5446ea2e7ab682ea036c 100644 (file)
 #include "refs.h"
 #include "tag.h"
 #include "run-command.h"
-
-static const char builtin_tag_usage[] =
-  "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
+#include "parse-options.h"
+
+static const char * const git_tag_usage[] = {
+       "git-tag [-a|-s|-u <key-id>] [-f] [-m <msg>|-F <file>] <tagname> [<head>]",
+       "git-tag -d <tagname>...",
+       "git-tag [-n [<num>]] -l [<pattern>]",
+       "git-tag -v <tagname>...",
+       NULL
+};
 
 static char signingkey[1000];
 
@@ -276,7 +282,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
 
 static void create_tag(const unsigned char *object, const char *tag,
                       struct strbuf *buf, int message, int sign,
-                          unsigned char *prev, unsigned char *result)
+                      unsigned char *prev, unsigned char *result)
 {
        enum object_type type;
        char header_buf[1024];
@@ -335,105 +341,101 @@ static void create_tag(const unsigned char *object, const char *tag,
                die("unable to write tag file");
 }
 
+struct msg_arg {
+       int given;
+       struct strbuf buf;
+};
+
+static int parse_msg_arg(const struct option *opt, const char *arg, int unset)
+{
+       struct msg_arg *msg = opt->value;
+
+       if (!arg)
+               return -1;
+       if (msg->buf.len)
+               strbuf_addstr(&(msg->buf), "\n\n");
+       strbuf_addstr(&(msg->buf), arg);
+       msg->given = 1;
+       return 0;
+}
+
 int cmd_tag(int argc, const char **argv, const char *prefix)
 {
        struct strbuf buf;
        unsigned char object[20], prev[20];
-       int annotate = 0, sign = 0, force = 0, lines = 0, message = 0;
        char ref[PATH_MAX];
        const char *object_ref, *tag;
-       int i;
        struct ref_lock *lock;
 
+       int annotate = 0, sign = 0, force = 0, lines = 0,
+                                       delete = 0, verify = 0;
+       char *list = NULL, *msgfile = NULL, *keyid = NULL;
+       const char *no_pattern = "NO_PATTERN";
+       struct msg_arg msg = { 0, STRBUF_INIT };
+       struct option options[] = {
+               { OPTION_STRING, 'l', NULL, &list, "pattern", "list tag names",
+                       PARSE_OPT_OPTARG, NULL, (intptr_t) no_pattern },
+               { OPTION_INTEGER, 'n', NULL, &lines, NULL,
+                               "print n lines of each tag message",
+                               PARSE_OPT_OPTARG, NULL, 1 },
+               OPT_BOOLEAN('d', NULL, &delete, "delete tags"),
+               OPT_BOOLEAN('v', NULL, &verify, "verify tags"),
+
+               OPT_GROUP("Tag creation options"),
+               OPT_BOOLEAN('a', NULL, &annotate,
+                                       "annotated tag, needs a message"),
+               OPT_CALLBACK('m', NULL, &msg, "msg",
+                            "message for the tag", parse_msg_arg),
+               OPT_STRING('F', NULL, &msgfile, "file", "message in a file"),
+               OPT_BOOLEAN('s', NULL, &sign, "annotated and GPG-signed tag"),
+               OPT_STRING('u', NULL, &keyid, "key-id",
+                                       "use another key to sign the tag"),
+               OPT_BOOLEAN('f', NULL, &force, "replace the tag if exists"),
+               OPT_END()
+       };
+
        git_config(git_tag_config);
-       strbuf_init(&buf, 0);
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
+       argc = parse_options(argc, argv, options, git_tag_usage, 0);
 
-               if (arg[0] != '-')
-                       break;
-               if (!strcmp(arg, "-a")) {
-                       annotate = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-s")) {
-                       annotate = 1;
-                       sign = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
-                       force = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-n")) {
-                       if (i + 1 == argc || *argv[i + 1] == '-')
-                               /* no argument */
-                               lines = 1;
-                       else
-                               lines = isdigit(*argv[++i]) ?
-                                       atoi(argv[i]) : 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-m")) {
-                       annotate = 1;
-                       i++;
-                       if (i == argc)
-                               die("option -m needs an argument.");
-                       if (message)
-                               die("only one -F or -m option is allowed.");
-                       strbuf_addstr(&buf, argv[i]);
-                       message = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-F")) {
-                       annotate = 1;
-                       i++;
-                       if (i == argc)
-                               die("option -F needs an argument.");
-                       if (message)
-                               die("only one -F or -m option is allowed.");
-
-                       if (!strcmp(argv[i], "-")) {
+       if (sign)
+               annotate = 1;
+
+       if (list)
+               return list_tags(list == no_pattern ? NULL : list, lines);
+       if (delete)
+               return for_each_tag_name(argv, delete_tag);
+       if (verify)
+               return for_each_tag_name(argv, verify_tag);
+
+       strbuf_init(&buf, 0);
+       if (msg.given || msgfile) {
+               if (msg.given && msgfile)
+                       die("only one -F or -m option is allowed.");
+               annotate = 1;
+               if (msg.given)
+                       strbuf_addbuf(&buf, &(msg.buf));
+               else {
+                       if (!strcmp(msgfile, "-")) {
                                if (strbuf_read(&buf, 0, 1024) < 0)
-                                       die("cannot read %s", argv[i]);
+                                       die("cannot read %s", msgfile);
                        } else {
-                               if (strbuf_read_file(&buf, argv[i], 1024) < 0)
+                               if (strbuf_read_file(&buf, msgfile, 1024) < 0)
                                        die("could not open or read '%s': %s",
-                                               argv[i], strerror(errno));
+                                               msgfile, strerror(errno));
                        }
-                       message = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-u")) {
-                       annotate = 1;
-                       sign = 1;
-                       i++;
-                       if (i == argc)
-                               die("option -u needs an argument.");
-                       if (strlcpy(signingkey, argv[i], sizeof(signingkey))
-                                                       >= sizeof(signingkey))
-                               die("argument to option -u too long");
-                       continue;
                }
-               if (!strcmp(arg, "-l"))
-                       return list_tags(argv[i + 1], lines);
-               if (!strcmp(arg, "-d"))
-                       return for_each_tag_name(argv + i + 1, delete_tag);
-               if (!strcmp(arg, "-v"))
-                       return for_each_tag_name(argv + i + 1, verify_tag);
-               usage(builtin_tag_usage);
        }
 
-       if (i == argc) {
+       if (argc == 0) {
                if (annotate)
-                       usage(builtin_tag_usage);
+                       usage_with_options(git_tag_usage, options);
                return list_tags(NULL, lines);
        }
-       tag = argv[i++];
+       tag = argv[0];
 
-       object_ref = i < argc ? argv[i] : "HEAD";
-       if (i + 1 < argc)
+       object_ref = argc == 2 ? argv[1] : "HEAD";
+       if (argc > 2)
                die("too many params");
 
        if (get_sha1(object_ref, object))
@@ -450,7 +452,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
                die("tag '%s' already exists", tag);
 
        if (annotate)
-               create_tag(object, tag, &buf, message, sign, prev, object);
+               create_tag(object, tag, &buf, msg.given || msgfile,
+                          sign, prev, object);
 
        lock = lock_any_ref_for_update(ref, prev, 0);
        if (!lock)
diff --git a/cache.h b/cache.h
index aaa135bfde23cd2529e3707e4499be7f75917336..d0e7a71c6e61e56c841f9670266a2e5d61211158 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -192,6 +192,13 @@ enum object_type {
        OBJ_MAX,
 };
 
+static inline enum object_type object_type(unsigned int mode)
+{
+       return S_ISDIR(mode) ? OBJ_TREE :
+               S_ISGITLINK(mode) ? OBJ_COMMIT :
+               OBJ_BLOB;
+}
+
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
diff --git a/color.c b/color.c
index 124ba331c7f798b9b922fc8482dbc215202b99a6..97cfbda31ac02b7e4fe747052c28821a34ee0165 100644 (file)
--- a/color.c
+++ b/color.c
@@ -118,21 +118,27 @@ void color_parse(const char *value, const char *var, char *dst)
 
 int git_config_colorbool(const char *var, const char *value)
 {
-       if (!value)
-               return 1;
-       if (!strcasecmp(value, "auto")) {
-               if (isatty(1) || (pager_in_use && pager_use_color)) {
-                       char *term = getenv("TERM");
-                       if (term && strcmp(term, "dumb"))
-                               return 1;
-               }
-               return 0;
+       if (value) {
+               if (!strcasecmp(value, "never"))
+                       return 0;
+               if (!strcasecmp(value, "always"))
+                       return 1;
+               if (!strcasecmp(value, "auto"))
+                       goto auto_color;
        }
-       if (!strcasecmp(value, "never"))
+
+       /* Missing or explicit false to turn off colorization */
+       if (!git_config_bool(var, value))
                return 0;
-       if (!strcasecmp(value, "always"))
-               return 1;
-       return git_config_bool(var, value);
+
+       /* any normal truth value defaults to 'auto' */
+ auto_color:
+       if (isatty(1) || (pager_in_use && pager_use_color)) {
+               char *term = getenv("TERM");
+               if (term && strcmp(term, "dumb"))
+                       return 1;
+       }
+       return 0;
 }
 
 static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
diff --git a/command-list.txt b/command-list.txt
new file mode 100644 (file)
index 0000000..d30e869
--- /dev/null
@@ -0,0 +1,128 @@
+# List of known git commands.
+# command name                         category [deprecated] [common]
+git-add                                 mainporcelain common
+git-am                                  mainporcelain
+git-annotate                            ancillaryinterrogators
+git-apply                               plumbingmanipulators
+git-archimport                          foreignscminterface
+git-archive                             mainporcelain
+git-bisect                              mainporcelain common
+git-blame                               ancillaryinterrogators
+git-branch                              mainporcelain common
+git-bundle                              mainporcelain
+git-cat-file                            plumbinginterrogators
+git-check-attr                          purehelpers
+git-checkout                            mainporcelain common
+git-checkout-index                      plumbingmanipulators
+git-check-ref-format                    purehelpers
+git-cherry                              ancillaryinterrogators
+git-cherry-pick                         mainporcelain
+git-citool                              mainporcelain
+git-clean                               mainporcelain
+git-clone                               mainporcelain common
+git-commit                              mainporcelain common
+git-commit-tree                         plumbingmanipulators
+git-config                              ancillarymanipulators
+git-count-objects                       ancillaryinterrogators
+git-cvsexportcommit                     foreignscminterface
+git-cvsimport                           foreignscminterface
+git-cvsserver                           foreignscminterface
+git-daemon                              synchingrepositories
+git-describe                            mainporcelain
+git-diff                                mainporcelain common
+git-diff-files                          plumbinginterrogators
+git-diff-index                          plumbinginterrogators
+git-diff-tree                           plumbinginterrogators
+git-fast-import                                ancillarymanipulators
+git-fetch                               mainporcelain common
+git-fetch-pack                          synchingrepositories
+git-filter-branch                       ancillarymanipulators
+git-fmt-merge-msg                       purehelpers
+git-for-each-ref                        plumbinginterrogators
+git-format-patch                        mainporcelain
+git-fsck                               ancillaryinterrogators
+git-gc                                  mainporcelain
+git-get-tar-commit-id                   ancillaryinterrogators
+git-grep                                mainporcelain common
+git-gui                                 mainporcelain
+git-hash-object                         plumbingmanipulators
+git-http-fetch                          synchelpers
+git-http-push                           synchelpers
+git-imap-send                           foreignscminterface
+git-index-pack                          plumbingmanipulators
+git-init                                mainporcelain common
+git-instaweb                            ancillaryinterrogators
+gitk                                    mainporcelain
+git-log                                 mainporcelain common
+git-lost-found                          ancillarymanipulators  deprecated
+git-ls-files                            plumbinginterrogators
+git-ls-remote                           plumbinginterrogators
+git-ls-tree                             plumbinginterrogators
+git-mailinfo                            purehelpers
+git-mailsplit                           purehelpers
+git-merge                               mainporcelain common
+git-merge-base                          plumbinginterrogators
+git-merge-file                          plumbingmanipulators
+git-merge-index                         plumbingmanipulators
+git-merge-one-file                      purehelpers
+git-mergetool                           ancillarymanipulators
+git-merge-tree                          ancillaryinterrogators
+git-mktag                               plumbingmanipulators
+git-mktree                              plumbingmanipulators
+git-mv                                  mainporcelain common
+git-name-rev                            plumbinginterrogators
+git-pack-objects                        plumbingmanipulators
+git-pack-redundant                      plumbinginterrogators
+git-pack-refs                           ancillarymanipulators
+git-parse-remote                        synchelpers
+git-patch-id                            purehelpers
+git-peek-remote                         purehelpers    deprecated
+git-prune                               ancillarymanipulators
+git-prune-packed                        plumbingmanipulators
+git-pull                                mainporcelain common
+git-push                                mainporcelain common
+git-quiltimport                         foreignscminterface
+git-read-tree                           plumbingmanipulators
+git-rebase                              mainporcelain common
+git-receive-pack                        synchelpers
+git-reflog                              ancillarymanipulators
+git-relink                              ancillarymanipulators
+git-remote                              ancillarymanipulators
+git-repack                              ancillarymanipulators
+git-request-pull                        foreignscminterface
+git-rerere                              ancillaryinterrogators
+git-reset                               mainporcelain common
+git-revert                              mainporcelain
+git-rev-list                            plumbinginterrogators
+git-rev-parse                           ancillaryinterrogators
+git-rm                                  mainporcelain common
+git-runstatus                           ancillaryinterrogators
+git-send-email                          foreignscminterface
+git-send-pack                           synchingrepositories
+git-shell                               synchelpers
+git-shortlog                            mainporcelain
+git-show                                mainporcelain common
+git-show-branch                         ancillaryinterrogators
+git-show-index                          plumbinginterrogators
+git-show-ref                            plumbinginterrogators
+git-sh-setup                            purehelpers
+git-stash                               mainporcelain
+git-status                              mainporcelain common
+git-stripspace                          purehelpers
+git-submodule                           mainporcelain
+git-svn                                 foreignscminterface
+git-symbolic-ref                        plumbingmanipulators
+git-tag                                 mainporcelain common
+git-tar-tree                            plumbinginterrogators  deprecated
+git-unpack-file                         plumbinginterrogators
+git-unpack-objects                      plumbingmanipulators
+git-update-index                        plumbingmanipulators
+git-update-ref                          plumbingmanipulators
+git-update-server-info                  synchingrepositories
+git-upload-archive                      synchelpers
+git-upload-pack                         synchelpers
+git-var                                 plumbinginterrogators
+git-verify-pack                         plumbinginterrogators
+git-verify-tag                          ancillaryinterrogators
+git-whatchanged                         ancillaryinterrogators
+git-write-tree                          plumbingmanipulators
index 7bcf1a488c05280b4ae1f8aad43b82ba33e2ea94..5f8a15b9f9580bea6f350e8af468f81cf2535c9e 100644 (file)
@@ -415,7 +415,7 @@ GIT_PARSE_WITH(iconv))
 # times (my ext3 doesn't).
 #
 # Define USE_STDEV below if you want git to care about the underlying device
-# change being considered an inode change from the update-cache perspective.
+# change being considered an inode change from the update-index perspective.
 
 
 ## Output files
index c71a22621a4f979f62cd21e9de83ab7129a129e9..31cdcfe8bcdae7df65b0387071846299a14bb7be 100644 (file)
@@ -52,8 +52,10 @@ static int should_break(struct diff_filespec *src,
                             * is the default.
                             */
 
-       if (!S_ISREG(src->mode) || !S_ISREG(dst->mode))
-               return 0; /* leave symlink rename alone */
+       if (S_ISREG(src->mode) != S_ISREG(dst->mode)) {
+               *merge_score_p = (int)MAX_SCORE;
+               return 1; /* even their types are different */
+       }
 
        if (src->sha1_valid && dst->sha1_valid &&
            !hashcmp(src->sha1, dst->sha1))
@@ -168,11 +170,13 @@ void diffcore_break(int break_score)
                struct diff_filepair *p = q->queue[i];
                int score;
 
-               /* We deal only with in-place edit of non directory.
+               /*
+                * We deal only with in-place edit of blobs.
                 * We do not break anything else.
                 */
                if (DIFF_FILE_VALID(p->one) && DIFF_FILE_VALID(p->two) &&
-                   !S_ISDIR(p->one->mode) && !S_ISDIR(p->two->mode) &&
+                   object_type(p->one->mode) == OBJ_BLOB &&
+                   object_type(p->two->mode) == OBJ_BLOB &&
                    !strcmp(p->one->path, p->two->path)) {
                        if (should_break(p->one, p->two,
                                         break_score, &score)) {
index f9ebea56406090af207f79951618742dcd7d397f..3d377251bef8ea843b7a7fa41f98d611daecbcc1 100644 (file)
@@ -244,28 +244,35 @@ static int find_identical_files(struct file_similarity *src,
         * Walk over all the destinations ...
         */
        do {
-               struct diff_filespec *one = dst->filespec;
+               struct diff_filespec *target = dst->filespec;
                struct file_similarity *p, *best;
-               int i = 100;
+               int i = 100, best_score = -1;
 
                /*
                 * .. to find the best source match
                 */
                best = NULL;
                for (p = src; p; p = p->next) {
-                       struct diff_filespec *two = p->filespec;
+                       int score;
+                       struct diff_filespec *source = p->filespec;
 
                        /* False hash collission? */
-                       if (hashcmp(one->sha1, two->sha1))
+                       if (hashcmp(source->sha1, target->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)
+                       if (!S_ISREG(source->mode) || !S_ISREG(target->mode)) {
+                               if (source->mode != target->mode)
                                        continue;
                        }
-                       best = p;
-                       if (basename_same(one, two))
-                               break;
+                       /* Give higher scores to sources that haven't been used already */
+                       score = !source->rename_used;
+                       score += basename_same(source, target);
+                       if (score > best_score) {
+                               best = p;
+                               best_score = score;
+                               if (score == 2)
+                                       break;
+                       }
 
                        /* Too many identical alternatives? Pick one */
                        if (!--i)
@@ -488,6 +495,19 @@ void diffcore_rename(struct diff_options *options)
        }
        /* cost matrix sorted by most to least similar pair */
        qsort(mx, num_create * num_src, sizeof(*mx), score_compare);
+       for (i = 0; i < num_create * num_src; i++) {
+               struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
+               struct diff_filespec *src;
+               if (dst->pair)
+                       continue; /* already done, either exact or fuzzy. */
+               if (mx[i].score < minimum_score)
+                       break; /* there is no more usable pair. */
+               src = rename_src[mx[i].src].one;
+               if (src->rename_used)
+                       continue;
+               record_rename_pair(mx[i].dst, mx[i].src, mx[i].score);
+               rename_count++;
+       }
        for (i = 0; i < num_create * num_src; i++) {
                struct diff_rename_dst *dst = &rename_dst[mx[i].dst];
                if (dst->pair)
diff --git a/dir.c b/dir.c
index 225fdfb52c432b7af1af1cdf9aa9a31f8022e3ec..d448902909a7da216fbd49cecc505e68c0ba5e5f 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -144,17 +144,14 @@ void add_exclude(const char *string, const char *base,
                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,
-                                          which->alloc * sizeof(x));
-       }
+       ALLOC_GROW(which->excludes, which->nr + 1, which->alloc);
        which->excludes[which->nr++] = x;
 }
 
 static int add_excludes_from_file_1(const char *fname,
                                    const char *base,
                                    int baselen,
+                                   char **buf_p,
                                    struct exclude_list *which)
 {
        struct stat st;
@@ -175,6 +172,8 @@ static int add_excludes_from_file_1(const char *fname,
                goto err;
        close(fd);
 
+       if (buf_p)
+               *buf_p = buf;
        buf[size++] = '\n';
        entry = buf;
        for (i = 0; i < size; i++) {
@@ -196,31 +195,63 @@ static int add_excludes_from_file_1(const char *fname,
 
 void add_excludes_from_file(struct dir_struct *dir, const char *fname)
 {
-       if (add_excludes_from_file_1(fname, "", 0,
+       if (add_excludes_from_file_1(fname, "", 0, NULL,
                                     &dir->exclude_list[EXC_FILE]) < 0)
                die("cannot use %s as an exclude file", fname);
 }
 
-int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)
 {
-       char exclude_file[PATH_MAX];
-       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
-       int current_nr = el->nr;
-
-       if (dir->exclude_per_dir) {
-               memcpy(exclude_file, base, baselen);
-               strcpy(exclude_file + baselen, dir->exclude_per_dir);
-               add_excludes_from_file_1(exclude_file, base, baselen, el);
+       struct exclude_list *el;
+       struct exclude_stack *stk = NULL;
+       int current;
+
+       if ((!dir->exclude_per_dir) ||
+           (baselen + strlen(dir->exclude_per_dir) >= PATH_MAX))
+               return; /* too long a path -- ignore */
+
+       /* Pop the ones that are not the prefix of the path being checked. */
+       el = &dir->exclude_list[EXC_DIRS];
+       while ((stk = dir->exclude_stack) != NULL) {
+               if (stk->baselen <= baselen &&
+                   !strncmp(dir->basebuf, base, stk->baselen))
+                       break;
+               dir->exclude_stack = stk->prev;
+               while (stk->exclude_ix < el->nr)
+                       free(el->excludes[--el->nr]);
+               free(stk->filebuf);
+               free(stk);
        }
-       return current_nr;
-}
 
-void pop_exclude_per_directory(struct dir_struct *dir, int stk)
-{
-       struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
+       /* Read from the parent directories and push them down. */
+       current = stk ? stk->baselen : -1;
+       while (current < baselen) {
+               struct exclude_stack *stk = xcalloc(1, sizeof(*stk));
+               const char *cp;
 
-       while (stk < el->nr)
-               free(el->excludes[--el->nr]);
+               if (current < 0) {
+                       cp = base;
+                       current = 0;
+               }
+               else {
+                       cp = strchr(base + current + 1, '/');
+                       if (!cp)
+                               die("oops in prep_exclude");
+                       cp++;
+               }
+               stk->prev = dir->exclude_stack;
+               stk->baselen = cp - base;
+               stk->exclude_ix = el->nr;
+               memcpy(dir->basebuf + current, base + current,
+                      stk->baselen - current);
+               strcpy(dir->basebuf + stk->baselen, dir->exclude_per_dir);
+               add_excludes_from_file_1(dir->basebuf,
+                                        dir->basebuf, stk->baselen,
+                                        &stk->filebuf, el);
+               dir->exclude_stack = stk;
+               current = stk->baselen;
+       }
+       dir->basebuf[baselen] = '\0';
 }
 
 /* Scan the list and let the last match determines the fate.
@@ -287,6 +318,7 @@ int excluded(struct dir_struct *dir, const char *pathname)
        const char *basename = strrchr(pathname, '/');
        basename = (basename) ? basename+1 : pathname;
 
+       prep_exclude(dir, pathname, basename-pathname);
        for (st = EXC_CMDL; st <= EXC_FILE; st++) {
                switch (excluded_1(pathname, pathlen, basename, &dir->exclude_list[st])) {
                case 0:
@@ -504,13 +536,10 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
        int contents = 0;
 
        if (fdir) {
-               int exclude_stk;
                struct dirent *de;
                char fullname[PATH_MAX + 1];
                memcpy(fullname, base, baselen);
 
-               exclude_stk = push_exclude_per_directory(dir, base, baselen);
-
                while ((de = readdir(fdir)) != NULL) {
                        int len, dtype;
                        int exclude;
@@ -584,8 +613,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                }
 exit_early:
                closedir(fdir);
-
-               pop_exclude_per_directory(dir, exclude_stk);
        }
 
        return contents;
@@ -654,47 +681,18 @@ static void free_simplify(struct path_simplify *simplify)
 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
 {
        struct path_simplify *simplify = create_simplify(pathspec);
-       char *pp = NULL;
-
-       /*
-        * Make sure to do the per-directory exclude for all the
-        * directories leading up to our base.
-        */
-       if (baselen) {
-               if (dir->exclude_per_dir) {
-                       char *p;
-                       pp = xmalloc(baselen+1);
-                       memcpy(pp, base, baselen+1);
-                       p = pp;
-                       while (1) {
-                               char save = *p;
-                               *p = 0;
-                               push_exclude_per_directory(dir, pp, p-pp);
-                               *p++ = save;
-                               if (!save)
-                                       break;
-                               p = strchr(p, '/');
-                               if (p)
-                                       p++;
-                               else
-                                       p = pp + baselen;
-                       }
-               }
-       }
 
        read_directory_recursive(dir, path, base, baselen, 0, simplify);
        free_simplify(simplify);
-       free(pp);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
        qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
 
-int
-file_exists(const char *f)
+int file_exists(const char *f)
 {
-  struct stat sb;
-  return stat(f, &sb) == 0;
+       struct stat sb;
+       return stat(f, &sb) == 0;
 }
 
 /*
diff --git a/dir.h b/dir.h
index 82009dc13e4b685bc6f4f6e9206d621c7d361c65..d8814dccb2dd57af21d75c91a52026e09fdf1b95 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -1,17 +1,6 @@
 #ifndef DIR_H
 #define DIR_H
 
-/*
- * We maintain three exclude pattern lists:
- * EXC_CMDL lists patterns explicitly given on the command line.
- * EXC_DIRS lists patterns obtained from per-directory ignore files.
- * EXC_FILE lists patterns from fallback ignore files.
- */
-#define EXC_CMDL 0
-#define EXC_DIRS 1
-#define EXC_FILE 2
-
-
 struct dir_entry {
        unsigned int len;
        char name[FLEX_ARRAY]; /* more */
@@ -34,6 +23,13 @@ struct exclude_list {
        } **excludes;
 };
 
+struct exclude_stack {
+       struct exclude_stack *prev;
+       char *filebuf;
+       int baselen;
+       int exclude_ix;
+};
+
 struct dir_struct {
        int nr, alloc;
        int ignored_nr, ignored_alloc;
@@ -48,6 +44,18 @@ struct dir_struct {
        /* Exclude info */
        const char *exclude_per_dir;
        struct exclude_list exclude_list[3];
+       /*
+        * We maintain three exclude pattern lists:
+        * EXC_CMDL lists patterns explicitly given on the command line.
+        * EXC_DIRS lists patterns obtained from per-directory ignore files.
+        * EXC_FILE lists patterns from fallback ignore files.
+        */
+#define EXC_CMDL 0
+#define EXC_DIRS 1
+#define EXC_FILE 2
+
+       struct exclude_stack *exclude_stack;
+       char basebuf[PATH_MAX];
 };
 
 extern int common_prefix(const char **pathspec);
@@ -58,8 +66,6 @@ extern int common_prefix(const char **pathspec);
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen, const char **pathspec);
-extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
-extern void pop_exclude_per_directory(struct dir_struct *, int);
 
 extern int excluded(struct dir_struct *, const char *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
index 17df47b95067449f03039b8ecd7715701302fa68..a2913c2a2cd1ec158157ada3e2deb666892b734b 100755 (executable)
@@ -9,35 +9,8 @@ struct cmdname_help
 
 static struct cmdname_help common_cmds[] = {"
 
-sort <<\EOF |
-add
-apply
-archive
-bisect
-branch
-checkout
-cherry-pick
-clone
-commit
-diff
-fetch
-grep
-init
-log
-merge
-mv
-prune
-pull
-push
-rebase
-reset
-revert
-rm
-show
-show-branch
-status
-tag
-EOF
+sed -n -e 's/^git-\([^         ]*\)[   ].* common.*/\1/p' command-list.txt |
+sort |
 while read cmd
 do
      sed -n '
index 76c1c844a95d8af19f64a3278706daf907dace01..2e407084260350e7fa56987a7603afdcd00acfb5 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -307,9 +307,9 @@ do
        GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$dotest/info")"
        GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$dotest/info")"
 
-       if test -z "$GIT_AUTHOR_EMAIL"
+       if test -z "$GIT_AUTHOR_EMAIL" || test -z "$GIT_AUTHOR_DATE"
        then
-               echo "Patch does not have a valid e-mail address."
+               echo "Patch does not have valid authorship information."
                stop_here $this
        fi
 
index efa6a0c41ad5a253047789e8ac2a30bf57f8646d..92648f40c98096d624a3a80feab4d3a818089b5a 100755 (executable)
@@ -108,10 +108,6 @@ sub read_repo_config {
             }
                }
        }
-    if (@ARGV == 0) {
-        chomp(my $module = `git-repo-config --get cvsimport.module`);
-        push(@ARGV, $module);
-    }
 }
 
 my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
@@ -119,6 +115,10 @@ sub read_repo_config {
 getopts($opts) or usage();
 usage if $opt_h;
 
+if (@ARGV == 0) {
+               chomp(my $module = `git-repo-config --get cvsimport.module`);
+               push(@ARGV, $module) if $? == 0;
+}
 @ARGV <= 1 or usage("You can't specify more than one CVS module");
 
 if ($opt_d) {
@@ -527,18 +527,12 @@ sub is_sha1 {
        return $s =~ /^[a-f0-9]{40}$/;
 }
 
-sub get_headref ($$) {
-    my $name    = shift;
-    my $git_dir = shift;
-
-    my $f = "$git_dir/$remote/$name";
-    if (open(my $fh, $f)) {
-           chomp(my $r = <$fh>);
-           is_sha1($r) or die "Cannot get head id for $name ($r): $!";
-           return $r;
-    }
-    die "unable to open $f: $!" unless $! == POSIX::ENOENT;
-    return undef;
+sub get_headref ($) {
+       my $name = shift;
+       my $r = `git rev-parse --verify '$name' 2>/dev/null`;
+       return undef unless $? == 0;
+       chomp $r;
+       return $r;
 }
 
 -d $git_tree
@@ -698,7 +692,8 @@ ()
 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
 
 sub commit {
-       if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) {
+       if ($branch eq $opt_o && !$index{branch} &&
+               !get_headref("$remote/$branch")) {
            # looks like an initial commit
            # use the index primed by git-init
            $ENV{GIT_INDEX_FILE} = "$git_dir/index";
@@ -722,7 +717,7 @@ sub commit {
        update_index(@old, @new);
        @old = @new = ();
        my $tree = write_tree();
-       my $parent = get_headref($last_branch, $git_dir);
+       my $parent = get_headref("$remote/$last_branch");
        print "Parent ID " . ($parent ? $parent : "(empty)") . "\n" if $opt_v;
 
        my @commit_args;
@@ -733,7 +728,7 @@ sub commit {
        foreach my $rx (@mergerx) {
                next unless $logmsg =~ $rx && $1;
                my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
-               if (my $sha1 = get_headref($mparent, $git_dir)) {
+               if (my $sha1 = get_headref("$remote/$mparent")) {
                        push @commit_args, '-p', $mparent;
                        print "Merge parent branch: $mparent\n" if $opt_v;
                }
@@ -870,29 +865,27 @@ sub commit {
                                print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
                                $ancestor = $opt_o;
                        }
-                       if (-f "$git_dir/$remote/$branch") {
+                       if (defined get_headref("$remote/$branch")) {
                                print STDERR "Branch $branch already exists!\n";
                                $state=11;
                                next;
                        }
-                       unless (open(H,"$git_dir/$remote/$ancestor")) {
+                       my $id = get_headref("$remote/$ancestor");
+                       if (!$id) {
                                print STDERR "Branch $ancestor does not exist!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
                                next;
                        }
-                       chomp(my $id = <H>);
-                       close(H);
-                       unless (open(H,"> $git_dir/$remote/$branch")) {
-                               print STDERR "Could not create branch $branch: $!\n";
+
+                       system(qw(git update-ref -m cvsimport),
+                               "$remote/$branch", $id);
+                       if($? != 0) {
+                               print STDERR "Could not create branch $branch\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
                                next;
                        }
-                       print H "$id\n"
-                               or die "Could not write branch $branch: $!";
-                       close(H)
-                               or die "Could not write branch $branch: $!";
                }
                $last_branch = $branch if $branch ne $last_branch;
                $state = 9;
@@ -1004,7 +997,7 @@ sub commit {
        $orig_branch = "master";
        print "DONE; creating $orig_branch branch\n" if $opt_v;
        system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
-               unless -f "$git_dir/refs/heads/master";
+               unless defined get_headref('refs/heads/master');
        system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
                if ($opt_r && $opt_o ne 'HEAD');
        system('git-update-ref', 'HEAD', "$orig_branch");
index 30fdc57310897207f2e931396d184e94b48ad2ef..698e82b116d64236b80b7d6b9ae687aadba07afe 100755 (executable)
@@ -17,6 +17,9 @@ test -z "$(git ls-files -u)" ||
        die "You are in the middle of a conflicted merge."
 
 strategy_args= no_summary= no_commit= squash= no_ff=
+curr_branch=$(git symbolic-ref -q HEAD)
+curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
+rebase=$(git config --bool branch.$curr_branch_short.rebase)
 while :
 do
        case "$1" in
@@ -52,6 +55,12 @@ do
                esac
                strategy_args="${strategy_args}-s $strategy "
                ;;
+       -r|--r|--re|--reb|--reba|--rebas|--rebase)
+               rebase=true
+               ;;
+       --no-r|--no-re|--no-reb|--no-reba|--no-rebas|--no-rebase)
+               rebase=false
+               ;;
        -h|--h|--he|--hel|--help)
                usage
                ;;
@@ -95,7 +104,6 @@ merge_head=$(sed -e '/       not-for-merge   /d' \
 
 case "$merge_head" in
 '')
-       curr_branch=$(git symbolic-ref -q HEAD)
        case $? in
          0) ;;
          1) echo >&2 "You are not currently on a branch; you must explicitly"
@@ -142,5 +150,6 @@ then
 fi
 
 merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
+test true = "$rebase" && exec git-rebase $merge_head
 exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
        "$merge_name" HEAD $merge_head
index 354510a3ba169910cd9c4c6fac92bec6ffdeecc9..f83e00fe8fec63d5c58f375ec49ee8bf865f1344 100755 (executable)
@@ -30,6 +30,11 @@ test -d "$REWRITTEN" && PRESERVE_MERGES=t
 test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
 test -f "$DOTEST"/verbose && VERBOSE=t
 
+GIT_CHERRY_PICK_HELP="  After resolving the conflicts,
+mark the corrected paths with 'git add <paths>', and
+run 'git rebase --continue'"
+export GIT_CHERRY_PICK_HELP
+
 warn () {
        echo "$*" >&2
 }
index b1529e28b1c4eb8b236bd497c83690c20621d369..f16fd9c3c0e5b9248623d1d65146504c603fc840 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 # Copyright (c) 2007, Nanako Shiraishi
 
-USAGE='[ | list | show | apply | clear]'
+USAGE='[  | save | list | show | apply | clear | create ]'
 
 SUBDIRECTORY_OK=Yes
 OPTIONS_SPEC=
@@ -207,6 +207,10 @@ show)
        shift
        show_stash "$@"
        ;;
+save)
+       shift
+       save_stash "$*" && git-reset --hard
+       ;;
 apply)
        shift
        apply_stash "$@"
@@ -221,14 +225,12 @@ create)
        fi
        create_stash "$*" && echo "$w_commit"
        ;;
-help | usage)
-       usage
-       ;;
 *)
-       if test $# -gt 0 && test "$1" = save
+       if test $# -eq 0
        then
-               shift
+               save_stash && git-reset --hard
+       else
+               usage
        fi
-       save_stash "$*" && git-reset --hard
        ;;
 esac
index 43e1591cef4e69a1d06463ad996190b89df6cfe6..9f884eb2132c76b86475b97256e0ed565f84bb2e 100755 (executable)
@@ -35,6 +35,7 @@
 push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
 push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
 use Carp qw/croak/;
+use Digest::MD5;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
@@ -48,8 +49,7 @@ BEGIN
        foreach (qw/command command_oneline command_noisy command_output_pipe
                    command_input_pipe command_close_pipe/) {
                for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
-                       Git::SVN::Migration Git::SVN::Log Git::SVN
-                       Git::SVN::Util),
+                       Git::SVN::Migration Git::SVN::Log Git::SVN),
                        __PACKAGE__) {
                        *{"${package}::$_"} = \&{"Git::$_"};
                }
@@ -81,6 +81,7 @@ BEGIN
                'quiet|q' => \$_q,
                'repack-flags|repack-args|repack-opts=s' =>
                   \$Git::SVN::_repack_flags,
+               'use-log-author' => \$Git::SVN::_use_log_author,
                %remote_opts );
 
 my ($_trunk, $_tags, $_branches, $_stdlayout);
@@ -142,6 +143,9 @@ BEGIN
        'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision
                        } ],
+       'show-externals' => [ \&cmd_show_externals, "Show svn:externals listings",
+                       { 'revision|r=i' => \$_revision
+                       } ],
        'multi-fetch' => [ \&cmd_multi_fetch,
                           "Deprecated alias for $0 fetch --all",
                           { 'revision|r=s' => \$_revision, %fc_opts } ],
@@ -193,23 +197,6 @@ BEGIN
        }
 };
 
-my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
-
-read_repo_config(\%opts);
-Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
-my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
-                    'minimize-connections' => \$Git::SVN::Migration::_minimize,
-                    'id|i=s' => \$Git::SVN::default_ref_id,
-                    'svn-remote|remote|R=s' => sub {
-                       $Git::SVN::no_reuse_existing = 1;
-                       $Git::SVN::default_repo_id = $_[1] });
-exit 1 if (!$rv && $cmd && $cmd ne 'log');
-
-usage(0) if $_help;
-version() if $_version;
-usage(1) unless defined $cmd;
-load_authors() if $_authors;
-
 # make sure we're always running
 unless ($cmd =~ /(?:clone|init|multi-init)$/) {
        unless (-d $ENV{GIT_DIR}) {
@@ -231,6 +218,24 @@ BEGIN
                $ENV{GIT_DIR} = $git_dir;
        }
 }
+
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+read_repo_config(\%opts);
+Getopt::Long::Configure('pass_through') if ($cmd && $cmd eq 'log');
+my $rv = GetOptions(%opts, 'help|H|h' => \$_help, 'version|V' => \$_version,
+                    'minimize-connections' => \$Git::SVN::Migration::_minimize,
+                    'id|i=s' => \$Git::SVN::default_ref_id,
+                    'svn-remote|remote|R=s' => sub {
+                       $Git::SVN::no_reuse_existing = 1;
+                       $Git::SVN::default_repo_id = $_[1] });
+exit 1 if (!$rv && $cmd && $cmd ne 'log');
+
+usage(0) if $_help;
+version() if $_version;
+usage(1) unless defined $cmd;
+load_authors() if $_authors;
+
 unless ($cmd =~ /^(?:clone|init|multi-init|commit-diff)$/) {
        Git::SVN::Migration::migration_check();
 }
@@ -545,6 +550,8 @@ sub cmd_rebase {
                exit 1;
        }
        unless ($_local) {
+               # rebase will checkout for us, so no need to do it explicitly
+               $_no_checkout = 'true';
                $_fetch_all ? $gs->fetch_all : $gs->fetch;
        }
        command_noisy(rebase_cmd(), $gs->refname);
@@ -565,6 +572,21 @@ sub cmd_show_ignore {
        });
 }
 
+sub cmd_show_externals {
+       my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+       $gs ||= Git::SVN->new;
+       my $r = (defined $_revision ? $_revision : $gs->ra->get_latest_revnum);
+       $gs->prop_walk($gs->{path}, $r, sub {
+               my ($gs, $path, $props) = @_;
+               print STDOUT "\n# $path\n";
+               my $s = $props->{'svn:externals'} or return;
+               $s =~ s/[\r\n]+/\n/g;
+               chomp $s;
+               $s =~ s#^#$path#gm;
+               print STDOUT "$s\n";
+       });
+}
+
 sub cmd_create_ignore {
        my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
        $gs ||= Git::SVN->new;
@@ -840,19 +862,19 @@ sub cmd_info {
                            command_output_pipe(qw(cat-file blob), "HEAD:$path");
                        if ($file_type eq "link") {
                                my $file_name = <$fh>;
-                               $checksum = Git::SVN::Util::md5sum("link $file_name");
+                               $checksum = md5sum("link $file_name");
                        } else {
-                               $checksum = Git::SVN::Util::md5sum($fh);
+                               $checksum = md5sum($fh);
                        }
                        command_close_pipe($fh, $ctx);
                } elsif ($file_type eq "link") {
                        my $file_name =
                            command(qw(cat-file blob), "HEAD:$path");
                        $checksum =
-                           Git::SVN::Util::md5sum("link " . $file_name);
+                           md5sum("link " . $file_name);
                } else {
                        open FILE, "<", $path or die $!;
-                       $checksum = Git::SVN::Util::md5sum(\*FILE);
+                       $checksum = md5sum(\*FILE);
                        close FILE or die $!;
                }
                $result .= "Checksum: " . $checksum . "\n";
@@ -1193,11 +1215,6 @@ sub find_file_type_and_diff_status {
        return ("file", $diff_status);
 }
 
-package Git::SVN::Util;
-use strict;
-use warnings;
-use Digest::MD5;
-
 sub md5sum {
        my $arg = shift;
        my $ref = ref $arg;
@@ -1219,7 +1236,8 @@ package Git::SVN;
 use warnings;
 use vars qw/$default_repo_id $default_ref_id $_no_metadata $_follow_parent
             $_repack $_repack_flags $_use_svm_props $_head
-            $_use_svnsync_props $no_reuse_existing $_minimize_url/;
+            $_use_svnsync_props $no_reuse_existing $_minimize_url
+           $_use_log_author/;
 use Carp qw/croak/;
 use File::Path qw/mkpath/;
 use File::Copy qw/copy/;
@@ -2059,11 +2077,17 @@ sub do_git_commit {
                croak "$log_entry->{revision} = $c already exists! ",
                      "Why are we refetching it?\n";
        }
-       $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $log_entry->{name};
-       $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
-                                                         $log_entry->{email};
+       $ENV{GIT_AUTHOR_NAME} = $log_entry->{name};
+       $ENV{GIT_AUTHOR_EMAIL} = $log_entry->{email};
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_entry->{date};
 
+       $ENV{GIT_COMMITTER_NAME} = (defined $log_entry->{commit_name})
+                                               ? $log_entry->{commit_name}
+                                               : $log_entry->{name};
+       $ENV{GIT_COMMITTER_EMAIL} = (defined $log_entry->{commit_email})
+                                               ? $log_entry->{commit_email}
+                                               : $log_entry->{email};
+
        my $tree = $log_entry->{tree};
        if (!defined $tree) {
                $tree = $self->tmp_index_do(sub {
@@ -2351,7 +2375,17 @@ sub make_log_entry {
        $log_entry{log} .= "\n";
        my $author = $log_entry{author} = check_author($log_entry{author});
        my ($name, $email) = defined $::users{$author} ? @{$::users{$author}}
-                                                      : ($author, undef);
+                                                      : ($author, undef);
+
+       my ($commit_name, $commit_email) = ($name, $email);
+       if ($_use_log_author) {
+               if ($log_entry{log} =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
+                       ($name, $email) = ($1, $2);
+               } elsif ($log_entry{log} =~
+                                     /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+                       ($name, $email) = ($1, $2);
+               }
+       }
        if (defined $headrev && $self->use_svm_props) {
                if ($self->rewrite_root) {
                        die "Can't have both 'useSvmProps' and 'rewriteRoot' ",
@@ -2374,23 +2408,28 @@ sub make_log_entry {
                remove_username($full_url);
                $log_entry{metadata} = "$full_url\@$r $uuid";
                $log_entry{svm_revision} = $r;
-               $email ||= "$author\@$uuid"
+               $email ||= "$author\@$uuid";
+               $commit_email ||= "$author\@$uuid";
        } elsif ($self->use_svnsync_props) {
                my $full_url = $self->svnsync->{url};
                $full_url .= "/$self->{path}" if length $self->{path};
                remove_username($full_url);
                my $uuid = $self->svnsync->{uuid};
                $log_entry{metadata} = "$full_url\@$rev $uuid";
-               $email ||= "$author\@$uuid"
+               $email ||= "$author\@$uuid";
+               $commit_email ||= "$author\@$uuid";
        } else {
                my $url = $self->metadata_url;
                remove_username($url);
                $log_entry{metadata} = "$url\@$rev " .
                                       $self->ra->get_uuid;
                $email ||= "$author\@" . $self->ra->get_uuid;
+               $commit_email ||= "$author\@" . $self->ra->get_uuid;
        }
        $log_entry{name} = $name;
        $log_entry{email} = $email;
+       $log_entry{commit_name} = $commit_name;
+       $log_entry{commit_email} = $commit_email;
        \%log_entry;
 }
 
@@ -2947,7 +2986,7 @@ sub apply_textdelta {
 
                if (defined $exp) {
                        seek $base, 0, 0 or croak $!;
-                       my $got = Git::SVN::Util::md5sum($base);
+                       my $got = ::md5sum($base);
                        die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
                            "expected: $exp\n",
                            "     got: $got\n" if ($got ne $exp);
@@ -2966,7 +3005,7 @@ sub close_file {
        if (my $fh = $fb->{fh}) {
                if (defined $exp) {
                        seek($fh, 0, 0) or croak $!;
-                       my $got = Git::SVN::Util::md5sum($fh);
+                       my $got = ::md5sum($fh);
                        if ($got ne $exp) {
                                die "Checksum mismatch: $path\n",
                                    "expected: $exp\n    got: $got\n";
@@ -3321,7 +3360,7 @@ sub chg_file {
        $fh->flush == 0 or croak $!;
        seek $fh, 0, 0 or croak $!;
 
-       my $exp = Git::SVN::Util::md5sum($fh);
+       my $exp = ::md5sum($fh);
        seek $fh, 0, 0 or croak $!;
 
        my $pool = SVN::Pool->new;
diff --git a/gitk b/gitk
deleted file mode 100755 (executable)
index 1da0b0a..0000000
--- a/gitk
+++ /dev/null
@@ -1,8661 +0,0 @@
-#!/bin/sh
-# Tcl ignores the next line -*- tcl -*- \
-exec wish "$0" -- "$@"
-
-# Copyright (C) 2005-2006 Paul Mackerras.  All rights reserved.
-# This program is free software; it may be used, copied, modified
-# and distributed under the terms of the GNU General Public Licence,
-# either version 2, or (at your option) any later version.
-
-proc gitdir {} {
-    global env
-    if {[info exists env(GIT_DIR)]} {
-       return $env(GIT_DIR)
-    } else {
-       return [exec git rev-parse --git-dir]
-    }
-}
-
-# A simple scheduler for compute-intensive stuff.
-# The aim is to make sure that event handlers for GUI actions can
-# run at least every 50-100 ms.  Unfortunately fileevent handlers are
-# run before X event handlers, so reading from a fast source can
-# make the GUI completely unresponsive.
-proc run args {
-    global isonrunq runq
-
-    set script $args
-    if {[info exists isonrunq($script)]} return
-    if {$runq eq {}} {
-       after idle dorunq
-    }
-    lappend runq [list {} $script]
-    set isonrunq($script) 1
-}
-
-proc filerun {fd script} {
-    fileevent $fd readable [list filereadable $fd $script]
-}
-
-proc filereadable {fd script} {
-    global runq
-
-    fileevent $fd readable {}
-    if {$runq eq {}} {
-       after idle dorunq
-    }
-    lappend runq [list $fd $script]
-}
-
-proc dorunq {} {
-    global isonrunq runq
-
-    set tstart [clock clicks -milliseconds]
-    set t0 $tstart
-    while {$runq ne {}} {
-       set fd [lindex $runq 0 0]
-       set script [lindex $runq 0 1]
-       set repeat [eval $script]
-       set t1 [clock clicks -milliseconds]
-       set t [expr {$t1 - $t0}]
-       set runq [lrange $runq 1 end]
-       if {$repeat ne {} && $repeat} {
-           if {$fd eq {} || $repeat == 2} {
-               # script returns 1 if it wants to be readded
-               # file readers return 2 if they could do more straight away
-               lappend runq [list $fd $script]
-           } else {
-               fileevent $fd readable [list filereadable $fd $script]
-           }
-       } elseif {$fd eq {}} {
-           unset isonrunq($script)
-       }
-       set t0 $t1
-       if {$t1 - $tstart >= 80} break
-    }
-    if {$runq ne {}} {
-       after idle dorunq
-    }
-}
-
-# Start off a git rev-list process and arrange to read its output
-proc start_rev_list {view} {
-    global startmsecs
-    global commfd leftover tclencoding datemode
-    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"
-    }
-    if {[catch {
-       set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \
-                        --boundary $viewargs($view) "--" $viewfiles($view)] r]
-    } err]} {
-       error_popup "Error executing git rev-list: $err"
-       exit 1
-    }
-    set commfd($view) $fd
-    set leftover($view) {}
-    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 "Reading"
-    if {$view == $curview} {
-       set progressdirn 1
-       set progresscoords {0 0}
-       set proglastnc 0
-    }
-}
-
-proc stop_rev_list {} {
-    global commfd curview
-
-    if {![info exists commfd($curview)]} return
-    set fd $commfd($curview)
-    catch {
-       set pid [pid $fd]
-       exec kill $pid
-    }
-    catch {close $fd}
-    unset commfd($curview)
-}
-
-proc getcommits {} {
-    global phase canv curview
-
-    set phase getcommits
-    initlayout
-    start_rev_list $curview
-    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 commitinterest
-    global leftover commfd
-    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...
-    if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
-       set stuff "\0"
-    }
-    if {$stuff == {}} {
-       if {![eof $fd]} {
-           return 1
-       }
-       # 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]} {
-           set fv {}
-           if {$view != $curview} {
-               set fv " for the \"$viewname($view)\" view"
-           }
-           if {[string range $err 0 4] == "usage"} {
-               set err "Gitk: error reading commits$fv:\
-                       bad arguments to git rev-list."
-               if {$viewname($view) eq "Command line"} {
-                   append err \
-                       "  (Note: arguments to gitk are passed to git rev-list\
-                        to allow selection of commits to be displayed.)"
-               }
-           } else {
-               set err "Error reading commits$fv: $err"
-           }
-           error_popup $err
-       }
-       if {$view == $curview} {
-           run chewcommits $view
-       }
-       return 0
-    }
-    set start 0
-    set gotsome 0
-    while 1 {
-       set i [string first "\0" $stuff $start]
-       if {$i < 0} {
-           append leftover($view) [string range $stuff $start end]
-           break
-       }
-       if {$start == 0} {
-           set cmit $leftover($view)
-           append cmit [string range $stuff 0 [expr {$i - 1}]]
-           set leftover($view) {}
-       } else {
-           set cmit [string range $stuff $start [expr {$i - 1}]]
-       }
-       set start [expr {$i + 1}]
-       set j [string first "\n" $cmit]
-       set ok 0
-       set listed 1
-       if {$j >= 0 && [string match "commit *" $cmit]} {
-           set ids [string range $cmit 7 [expr {$j - 1}]]
-           if {[string match {[-<>]*} $ids]} {
-               switch -- [string index $ids 0] {
-                   "-" {set listed 0}
-                   "<" {set listed 2}
-                   ">" {set listed 3}
-               }
-               set ids [string range $ids 1 end]
-           }
-           set ok 1
-           foreach id $ids {
-               if {[string length $id] != 40} {
-                   set ok 0
-                   break
-               }
-           }
-       }
-       if {!$ok} {
-           set shortcmit $cmit
-           if {[string length $shortcmit] > 80} {
-               set shortcmit "[string range $shortcmit 0 80]..."
-           }
-           error_popup "Can't parse git log output: {$shortcmit}"
-           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]
-           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
-               }
-           }
-       } else {
-           set olds {}
-       }
-       if {![info exists children($view,$id)]} {
-           set children($view,$id) {}
-       }
-       set commitdata($id) [string range $cmit [expr {$j + 1}] end]
-       set commitrow($view,$id) $commitidx($view)
-       incr commitidx($view)
-       if {$view == $curview} {
-           lappend parentlist $olds
-           lappend displayorder $id
-           lappend commitlisted $listed
-       } else {
-           lappend vparentlist($view) $olds
-           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 viewcomplete
-    global selectedline pending_select
-
-    if {$view == $curview} {
-       layoutmore
-       if {$viewcomplete($view)} {
-           global displayorder commitidx phase
-           global numcommits startmsecs
-
-           if {[info exists pending_select]} {
-               set row [first_real_row]
-               selectline $row 1
-           }
-           if {$commitidx($curview) > 0} {
-               #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
-               #puts "overall $ms ms for $numcommits commits"
-           } else {
-               show_status "No commits selected"
-           }
-           notbusy layout
-           set phase {}
-       }
-    }
-    if {[info exists hlview] && $view == $hlview} {
-       vhighlightmore
-    }
-    return 0
-}
-
-proc readcommit {id} {
-    if {[catch {set contents [exec git cat-file commit $id]}]} return
-    parsecommit $id $contents 0
-}
-
-proc updatecommits {} {
-    global viewdata curview phase displayorder ordertok idpending
-    global children commitrow selectedline thickerline showneartags
-
-    if {$phase ne {}} {
-       stop_rev_list
-       set phase {}
-    }
-    set n $curview
-    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}
-    catch {unset thickerline}
-    catch {unset viewdata($n)}
-    readrefs
-    changedrefs
-    if {$showneartags} {
-       getallcommits
-    }
-    showview $n
-}
-
-proc parsecommit {id contents listed} {
-    global commitinfo cdate
-
-    set inhdr 1
-    set comment {}
-    set headline {}
-    set auname {}
-    set audate {}
-    set comname {}
-    set comdate {}
-    set hdrend [string first "\n\n" $contents]
-    if {$hdrend < 0} {
-       # should never happen...
-       set hdrend [string length $contents]
-    }
-    set header [string range $contents 0 [expr {$hdrend - 1}]]
-    set comment [string range $contents [expr {$hdrend + 2}] end]
-    foreach line [split $header "\n"] {
-       set tag [lindex $line 0]
-       if {$tag == "author"} {
-           set audate [lindex $line end-1]
-           set auname [lrange $line 1 end-2]
-       } elseif {$tag == "committer"} {
-           set comdate [lindex $line end-1]
-           set comname [lrange $line 1 end-2]
-       }
-    }
-    set headline {}
-    # take the first non-blank line of the comment as the headline
-    set headline [string trimleft $comment]
-    set i [string first "\n" $headline]
-    if {$i >= 0} {
-       set headline [string range $headline 0 $i]
-    }
-    set headline [string trimright $headline]
-    set i [string first "\r" $headline]
-    if {$i >= 0} {
-       set headline [string trimright [string range $headline 0 $i]]
-    }
-    if {!$listed} {
-       # git rev-list indents the comment by 4 spaces;
-       # if we got this via git cat-file, add the indentation
-       set newcomment {}
-       foreach line [split $comment "\n"] {
-           append newcomment "    "
-           append newcomment $line
-           append newcomment "\n"
-       }
-       set comment $newcomment
-    }
-    if {$comdate != {}} {
-       set cdate($id) $comdate
-    }
-    set commitinfo($id) [list $headline $auname $audate \
-                            $comname $comdate $comment]
-}
-
-proc getcommit {id} {
-    global commitdata commitinfo
-
-    if {[info exists commitdata($id)]} {
-       parsecommit $id $commitdata($id) 1
-    } else {
-       readcommit $id
-       if {![info exists commitinfo($id)]} {
-           set commitinfo($id) {"No commit information available"}
-       }
-    }
-    return 1
-}
-
-proc readrefs {} {
-    global tagids idtags headids idheads tagobjid
-    global otherrefids idotherrefs mainhead mainheadid
-
-    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
-       catch {unset $v}
-    }
-    set refd [open [list | git show-ref -d] r]
-    while {[gets $refd line] >= 0} {
-       if {[string index $line 40] ne " "} continue
-       set id [string range $line 0 39]
-       set ref [string range $line 41 end]
-       if {![string match "refs/*" $ref]} continue
-       set name [string range $ref 5 end]
-       if {[string match "remotes/*" $name]} {
-           if {![string match "*/HEAD" $name]} {
-               set headids($name) $id
-               lappend idheads($id) $name
-           }
-       } elseif {[string match "heads/*" $name]} {
-           set name [string range $name 6 end]
-           set headids($name) $id
-           lappend idheads($id) $name
-       } elseif {[string match "tags/*" $name]} {
-           # this lets refs/tags/foo^{} overwrite refs/tags/foo,
-           # which is what we want since the former is the commit ID
-           set name [string range $name 5 end]
-           if {[string match "*^{}" $name]} {
-               set name [string range $name 0 end-3]
-           } else {
-               set tagobjid($name) $id
-           }
-           set tagids($name) $id
-           lappend idtags($id) $name
-       } else {
-           set otherrefids($name) $id
-           lappend idotherrefs($id) $name
-       }
-    }
-    catch {close $refd}
-    set mainhead {}
-    set mainheadid {}
-    catch {
-       set thehead [exec git symbolic-ref HEAD]
-       if {[string match "refs/heads/*" $thehead]} {
-           set mainhead [string range $thehead 11 end]
-           if {[info exists headids($mainhead)]} {
-               set mainheadid $headids($mainhead)
-           }
-       }
-    }
-}
-
-# skip over fake commits
-proc first_real_row {} {
-    global nullid nullid2 displayorder numcommits
-
-    for {set row 0} {$row < $numcommits} {incr row} {
-       set id [lindex $displayorder $row]
-       if {$id ne $nullid && $id ne $nullid2} {
-           break
-       }
-    }
-    return $row
-}
-
-# update things for a head moved to a child of its previous location
-proc movehead {id name} {
-    global headids idheads
-
-    removehead $headids($name) $name
-    set headids($name) $id
-    lappend idheads($id) $name
-}
-
-# update things when a head has been removed
-proc removehead {id name} {
-    global headids idheads
-
-    if {$idheads($id) eq $name} {
-       unset idheads($id)
-    } else {
-       set i [lsearch -exact $idheads($id) $name]
-       if {$i >= 0} {
-           set idheads($id) [lreplace $idheads($id) $i $i]
-       }
-    }
-    unset headids($name)
-}
-
-proc show_error {w top msg} {
-    message $w.m -text $msg -justify center -aspect 400
-    pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text OK -command "destroy $top"
-    pack $w.ok -side bottom -fill x
-    bind $top <Visibility> "grab $top; focus $top"
-    bind $top <Key-Return> "destroy $top"
-    tkwait window $top
-}
-
-proc error_popup msg {
-    set w .error
-    toplevel $w
-    wm transient $w .
-    show_error $w $w $msg
-}
-
-proc confirm_popup msg {
-    global confirm_ok
-    set confirm_ok 0
-    set w .confirm
-    toplevel $w
-    wm transient $w .
-    message $w.m -text $msg -justify center -aspect 400
-    pack $w.m -side top -fill x -padx 20 -pady 20
-    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
-    pack $w.ok -side left -fill x
-    button $w.cancel -text Cancel -command "destroy $w"
-    pack $w.cancel -side right -fill x
-    bind $w <Visibility> "grab $w; focus $w"
-    tkwait window $w
-    return $confirm_ok
-}
-
-proc makewindow {} {
-    global canv canv2 canv3 linespc charspc ctext cflist
-    global tabstop
-    global findtype findtypemenu findloc findstring fstring geometry
-    global entries sha1entry sha1string sha1but
-    global diffcontextstring diffcontext
-    global maincursor textcursor curtextcursor
-    global rowctxmenu fakerowmenu mergemax wrapcomment
-    global highlight_files gdttype
-    global searchstring sstring
-    global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
-    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
-    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
-    menu .bar.edit
-    .bar add cascade -label "Edit" -menu .bar.edit
-    .bar.edit add command -label "Preferences" -command doprefs
-    .bar.edit configure -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 \
-       -state disabled
-    .bar.view add command -label "Delete view" -command delview -state disabled
-    .bar.view add separator
-    .bar.view add radiobutton -label "All files" -command {showview 0} \
-       -variable selectedview -value 0
-
-    menu .bar.help
-    .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
-    . configure -menu .bar
-
-    # the gui has upper and lower half, parts of a paned window.
-    panedwindow .ctop -orient vertical
-
-    # possibly use assumed geometry
-    if {![info exists geometry(pwsash0)]} {
-        set geometry(topheight) [expr {15 * $linespc}]
-        set geometry(topwidth) [expr {80 * $charspc}]
-        set geometry(botheight) [expr {15 * $linespc}]
-        set geometry(botwidth) [expr {50 * $charspc}]
-        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
-        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
-    }
-
-    # the upper half will have a paned window, a scroll bar to the right, and some stuff below
-    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
-    frame .tf.histframe
-    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
-
-    # create three canvases
-    set cscroll .tf.histframe.csb
-    set canv .tf.histframe.pwclist.canv
-    canvas $canv \
-       -selectbackground $selectbgcolor \
-       -background $bgcolor -bd 0 \
-       -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
-    .tf.histframe.pwclist add $canv
-    set canv2 .tf.histframe.pwclist.canv2
-    canvas $canv2 \
-       -selectbackground $selectbgcolor \
-       -background $bgcolor -bd 0 -yscrollincr $linespc
-    .tf.histframe.pwclist add $canv2
-    set canv3 .tf.histframe.pwclist.canv3
-    canvas $canv3 \
-       -selectbackground $selectbgcolor \
-       -background $bgcolor -bd 0 -yscrollincr $linespc
-    .tf.histframe.pwclist add $canv3
-    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
-    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
-
-    # a scroll bar to rule them
-    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
-    pack $cscroll -side right -fill y
-    bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
-    lappend bglist $canv $canv2 $canv3
-    pack .tf.histframe.pwclist -fill both -expand 1 -side left
-
-    # we have two button bars at bottom of top frame. Bar 1
-    frame .tf.bar
-    frame .tf.lbar -height 15
-
-    set sha1entry .tf.bar.sha1
-    set entries $sha1entry
-    set sha1but .tf.bar.sha1label
-    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
-       -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
-    trace add variable sha1string write sha1change
-    pack $sha1entry -side left -pady 2
-
-    image create bitmap bm-left -data {
-       #define left_width 16
-       #define left_height 16
-       static unsigned char left_bits[] = {
-       0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
-       0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
-       0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
-    }
-    image create bitmap bm-right -data {
-       #define right_width 16
-       #define right_height 16
-       static unsigned char right_bits[] = {
-       0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
-       0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
-       0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
-    }
-    button .tf.bar.leftbut -image bm-left -command goback \
-       -state disabled -width 26
-    pack .tf.bar.leftbut -side left -fill y
-    button .tf.bar.rightbut -image bm-right -command goforw \
-       -state disabled -width 26
-    pack .tf.bar.rightbut -side left -fill y
-
-    # 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.lbar.findstring
-    lappend entries $fstring
-    entry $fstring -width 30 -font textfont -textvariable findstring
-    trace add variable findstring write find_change
-    set findtype Exact
-    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
-                     findtype Exact IgnCase Regexp]
-    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.lbar.findloc findloc "All fields" Headline \
-       Comments Author Committer
-    trace add variable findloc write find_change
-    .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
-    pack .tf.bar -in .tf -side bottom -fill x
-    pack .tf.histframe -fill both -side top -expand 1
-    .ctop add .tf
-    .ctop paneconfigure .tf -height $geometry(topheight)
-    .ctop paneconfigure .tf -width $geometry(topwidth)
-
-    # now build up the bottom
-    panedwindow .pwbottom -orient horizontal
-
-    # lower left, a text box over search bar, scroll bar to the right
-    # if we know window height, then that will set the lower text height, otherwise
-    # we set lower text height which will drive window height
-    if {[info exists geometry(main)]} {
-        frame .bleft -width $geometry(botwidth)
-    } else {
-        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
-    }
-    frame .bleft.top
-    frame .bleft.mid
-
-    button .bleft.top.search -text "Search" -command dosearch \
-       -font uifont
-    pack .bleft.top.search -side left -padx 5
-    set sstring .bleft.top.sstring
-    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" -font uifont \
-       -command changediffdisp -variable diffelide -value {0 0}
-    radiobutton .bleft.mid.old -text "Old version" -font uifont \
-       -command changediffdisp -variable diffelide -value {0 1}
-    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
-    pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
-    spinbox .bleft.mid.diffcontext -width 5 -font textfont \
-       -from 1 -increment 1 -to 10000000 \
-       -validate all -validatecommand "diffcontextvalidate %P" \
-       -textvariable diffcontextstring
-    .bleft.mid.diffcontext set $diffcontext
-    trace add variable diffcontextstring write diffcontextchange
-    lappend entries .bleft.mid.diffcontext
-    pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
-    set ctext .bleft.ctext
-    text $ctext -background $bgcolor -foreground $fgcolor \
-       -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
-    pack .bleft.sb -side right -fill y
-    pack $ctext -side left -fill both -expand 1
-    lappend bglist $ctext
-    lappend fglist $ctext
-
-    $ctext tag conf comment -wrap $wrapcomment
-    $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]
-    $ctext tag conf m0 -fore red
-    $ctext tag conf m1 -fore blue
-    $ctext tag conf m2 -fore green
-    $ctext tag conf m3 -fore purple
-    $ctext tag conf m4 -fore brown
-    $ctext tag conf m5 -fore "#009090"
-    $ctext tag conf m6 -fore magenta
-    $ctext tag conf m7 -fore "#808000"
-    $ctext tag conf m8 -fore "#009000"
-    $ctext tag conf m9 -fore "#ff0080"
-    $ctext tag conf m10 -fore cyan
-    $ctext tag conf m11 -fore "#b07070"
-    $ctext tag conf m12 -fore "#70b0f0"
-    $ctext tag conf m13 -fore "#70f0b0"
-    $ctext tag conf m14 -fore "#f0b070"
-    $ctext tag conf m15 -fore "#ff70b0"
-    $ctext tag conf mmax -fore darkgrey
-    set mergemax 16
-    $ctext tag conf mresult -font textfontbold
-    $ctext tag conf msep -font textfontbold
-    $ctext tag conf found -back yellow
-
-    .pwbottom add .bleft
-    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
-
-    # lower right
-    frame .bright
-    frame .bright.mode
-    radiobutton .bright.mode.patch -text "Patch" \
-       -command reselectline -variable cmitmode -value "patch"
-    .bright.mode.patch configure -font uifont
-    radiobutton .bright.mode.tree -text "Tree" \
-       -command reselectline -variable cmitmode -value "tree"
-    .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"]
-    text $cflist \
-       -selectbackground $selectbgcolor \
-       -background $bgcolor -foreground $fgcolor \
-       -font mainfont \
-       -tabs [list $indent [expr {2 * $indent}]] \
-       -yscrollcommand ".bright.sb set" \
-       -cursor [. cget -cursor] \
-       -spacing1 1 -spacing3 1
-    lappend bglist $cflist
-    lappend fglist $cflist
-    scrollbar .bright.sb -command "$cflist yview"
-    pack .bright.sb -side right -fill y
-    pack $cflist -side left -fill both -expand 1
-    $cflist tag configure highlight \
-       -background [$cflist cget -selectbackground]
-    $cflist tag configure bold -font mainfontbold
-
-    .pwbottom add .bright
-    .ctop add .pwbottom
-
-    # restore window position if known
-    if {[info exists geometry(main)]} {
-        wm geometry . "$geometry(main)"
-    }
-
-    if {[tk windowingsystem] eq {aqua}} {
-        set M1B M1
-    } else {
-        set M1B Control
-    }
-
-    bind .pwbottom <Configure> {resizecdetpanes %W %w}
-    pack .ctop -fill both -expand 1
-    bindall <1> {selcanvline %W %x %y}
-    #bindall <B1-Motion> {selcanvline %W %x %y}
-    if {[tk windowingsystem] == "win32"} {
-       bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
-       bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
-    } else {
-       bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
-       bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
-        if {[tk windowingsystem] eq "aqua"} {
-            bindall <MouseWheel> {
-                set delta [expr {- (%D)}]
-                allcanvs yview scroll $delta units
-            }
-        }
-    }
-    bindall <2> "canvscan mark %W %x %y"
-    bindall <B2-Motion> "canvscan dragto %W %x %y"
-    bindkey <Home> selfirstline
-    bindkey <End> sellastline
-    bind . <Key-Up> "selnextline -1"
-    bind . <Key-Down> "selnextline 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"
-    bind . <Key-Next> "selnextpage 1"
-    bind . <$M1B-Home> "allcanvs yview moveto 0.0"
-    bind . <$M1B-End> "allcanvs yview moveto 1.0"
-    bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
-    bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
-    bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
-    bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
-    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
-    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
-    bindkey <Key-space> "$ctext yview scroll 1 pages"
-    bindkey p "selnextline -1"
-    bindkey n "selnextline 1"
-    bindkey z "goback"
-    bindkey x "goforw"
-    bindkey i "selnextline -1"
-    bindkey k "selnextline 1"
-    bindkey j "goback"
-    bindkey l "goforw"
-    bindkey b "$ctext yview scroll -1 pages"
-    bindkey d "$ctext yview scroll 18 units"
-    bindkey u "$ctext yview scroll -18 units"
-    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 1 1}
-    bind . <$M1B-g> {dofind 1 0}
-    bind . <$M1B-r> dosearchback
-    bind . <$M1B-s> dosearch
-    bind . <$M1B-equal> {incrfont 1}
-    bind . <$M1B-KP_Add> {incrfont 1}
-    bind . <$M1B-minus> {incrfont -1}
-    bind . <$M1B-KP_Subtract> {incrfont -1}
-    wm protocol . WM_DELETE_WINDOW doquit
-    bind . <Button-1> "click %W"
-    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}
-    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
-    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
-    bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
-
-    set maincursor [. cget -cursor]
-    set textcursor [$ctext cget -cursor]
-    set curtextcursor $textcursor
-
-    set rowctxmenu .rowctxmenu
-    menu $rowctxmenu -tearoff 0
-    $rowctxmenu add command -label "Diff this -> selected" \
-       -command {diffvssel 0}
-    $rowctxmenu add command -label "Diff selected -> this" \
-       -command {diffvssel 1}
-    $rowctxmenu add command -label "Make patch" -command mkpatch
-    $rowctxmenu add command -label "Create tag" -command mktag
-    $rowctxmenu add command -label "Write commit to file" -command writecommit
-    $rowctxmenu add command -label "Create new branch" -command mkbranch
-    $rowctxmenu add command -label "Cherry-pick this commit" \
-       -command cherrypick
-    $rowctxmenu add command -label "Reset HEAD branch to here" \
-       -command resethead
-
-    set fakerowmenu .fakerowmenu
-    menu $fakerowmenu -tearoff 0
-    $fakerowmenu add command -label "Diff this -> selected" \
-       -command {diffvssel 0}
-    $fakerowmenu add command -label "Diff selected -> this" \
-       -command {diffvssel 1}
-    $fakerowmenu add command -label "Make patch" -command mkpatch
-#    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
-#    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
-#    $fakerowmenu add command -label "Revert local changes" -command revertlocal
-
-    set headctxmenu .headctxmenu
-    menu $headctxmenu -tearoff 0
-    $headctxmenu add command -label "Check out this branch" \
-       -command cobranch
-    $headctxmenu add command -label "Remove this branch" \
-       -command rmbranch
-
-    global flist_menu
-    set flist_menu .flistctxmenu
-    menu $flist_menu -tearoff 0
-    $flist_menu add command -label "Highlight this too" \
-       -command {flist_hl 0}
-    $flist_menu add command -label "Highlight this only" \
-       -command {flist_hl 1}
-}
-
-# Windows sends all mouse wheel events to the current focused window, not
-# the one where the mouse hovers, so bind those events here and redirect
-# to the correct window
-proc windows_mousewheel_redirector {W X Y D} {
-    global canv canv2 canv3
-    set w [winfo containing -displayof $W $X $Y]
-    if {$w ne ""} {
-       set u [expr {$D < 0 ? 5 : -5}]
-       if {$w == $canv || $w == $canv2 || $w == $canv3} {
-           allcanvs yview scroll $u units
-       } else {
-           catch {
-               $w yview scroll $u units
-           }
-       }
-    }
-}
-
-# mouse-2 makes all windows scan vertically, but only the one
-# the cursor is in scans horizontally
-proc canvscan {op w x y} {
-    global canv canv2 canv3
-    foreach c [list $canv $canv2 $canv3] {
-       if {$c == $w} {
-           $c scan $op $x $y
-       } else {
-           $c scan $op 0 $y
-       }
-    }
-}
-
-proc scrollcanv {cscroll f0 f1} {
-    $cscroll set $f0 $f1
-    drawfrac $f0 $f1
-    flushhighlights
-}
-
-# when we make a key binding for the toplevel, make sure
-# it doesn't get triggered when that key is pressed in the
-# find string entry widget.
-proc bindkey {ev script} {
-    global entries
-    bind . $ev $script
-    set escript [bind Entry $ev]
-    if {$escript == {}} {
-       set escript [bind Entry <Key>]
-    }
-    foreach e $entries {
-       bind $e $ev "$escript; break"
-    }
-}
-
-# set the focus back to the toplevel for any click outside
-# the entry widgets
-proc click {w} {
-    global ctext entries
-    foreach e [concat $entries $ctext] {
-       if {$w == $e} return
-    }
-    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 mainfont textfont uifont tabstop
-    global stuffsaved findmergefiles maxgraphpct
-    global maxwidth showneartags showlocalchanges
-    global viewname viewfiles viewargs viewperm nextviewnum
-    global cmitmode wrapcomment datetimeformat limitdiffs
-    global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
-
-    if {$stuffsaved} return
-    if {![winfo viewable .]} return
-    catch {
-       set f [open "~/.gitk-new" w]
-       puts $f [list set mainfont $mainfont]
-       puts $f [list set textfont $textfont]
-       puts $f [list set uifont $uifont]
-       puts $f [list set tabstop $tabstop]
-       puts $f [list set findmergefiles $findmergefiles]
-       puts $f [list set maxgraphpct $maxgraphpct]
-       puts $f [list set maxwidth $maxwidth]
-       puts $f [list set cmitmode $cmitmode]
-       puts $f [list set wrapcomment $wrapcomment]
-       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]
-       puts $f [list set diffcolors $diffcolors]
-       puts $f [list set diffcontext $diffcontext]
-       puts $f [list set selectbgcolor $selectbgcolor]
-
-       puts $f "set geometry(main) [wm geometry .]"
-       puts $f "set geometry(topwidth) [winfo width .tf]"
-       puts $f "set geometry(topheight) [winfo height .tf]"
-        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
-        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
-       puts $f "set geometry(botwidth) [winfo width .bleft]"
-       puts $f "set geometry(botheight) [winfo height .bleft]"
-
-       puts -nonewline $f "set permviews {"
-       for {set v 0} {$v < $nextviewnum} {incr v} {
-           if {$viewperm($v)} {
-               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
-           }
-       }
-       puts $f "}"
-       close $f
-       file rename -force "~/.gitk-new" "~/.gitk"
-    }
-    set stuffsaved 1
-}
-
-proc resizeclistpanes {win w} {
-    global oldwidth
-    if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
-       set s1 [$win sash coord 1]
-       if {$w < 60} {
-           set sash0 [expr {int($w/2 - 2)}]
-           set sash1 [expr {int($w*5/6 - 2)}]
-       } else {
-           set factor [expr {1.0 * $w / $oldwidth($win)}]
-           set sash0 [expr {int($factor * [lindex $s0 0])}]
-           set sash1 [expr {int($factor * [lindex $s1 0])}]
-           if {$sash0 < 30} {
-               set sash0 30
-           }
-           if {$sash1 < $sash0 + 20} {
-               set sash1 [expr {$sash0 + 20}]
-           }
-           if {$sash1 > $w - 10} {
-               set sash1 [expr {$w - 10}]
-               if {$sash0 > $sash1 - 20} {
-                   set sash0 [expr {$sash1 - 20}]
-               }
-           }
-       }
-       $win sash place 0 $sash0 [lindex $s0 1]
-       $win sash place 1 $sash1 [lindex $s1 1]
-    }
-    set oldwidth($win) $w
-}
-
-proc resizecdetpanes {win w} {
-    global oldwidth
-    if {[info exists oldwidth($win)]} {
-       set s0 [$win sash coord 0]
-       if {$w < 60} {
-           set sash0 [expr {int($w*3/4 - 2)}]
-       } else {
-           set factor [expr {1.0 * $w / $oldwidth($win)}]
-           set sash0 [expr {int($factor * [lindex $s0 0])}]
-           if {$sash0 < 45} {
-               set sash0 45
-           }
-           if {$sash0 > $w - 15} {
-               set sash0 [expr {$w - 15}]
-           }
-       }
-       $win sash place 0 $sash0 [lindex $s0 1]
-    }
-    set oldwidth($win) $w
-}
-
-proc allcanvs args {
-    global canv canv2 canv3
-    eval $canv $args
-    eval $canv2 $args
-    eval $canv3 $args
-}
-
-proc bindall {event action} {
-    global canv canv2 canv3
-    bind $canv $event $action
-    bind $canv2 $event $action
-    bind $canv3 $event $action
-}
-
-proc about {} {
-    global uifont
-    set w .about
-    if {[winfo exists $w]} {
-       raise $w
-       return
-    }
-    toplevel $w
-    wm title $w "About gitk"
-    message $w.m -text {
-Gitk - a commit viewer for git
-
-Copyright Â© 2005-2006 Paul Mackerras
-
-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
-    button $w.ok -text Close -command "destroy $w" -default active
-    pack $w.ok -side bottom
-    $w.ok configure -font uifont
-    bind $w <Visibility> "focus $w.ok"
-    bind $w <Key-Escape> "destroy $w"
-    bind $w <Key-Return> "destroy $w"
-}
-
-proc keys {} {
-    global uifont
-    set w .keys
-    if {[winfo exists $w]} {
-       raise $w
-       return
-    }
-    if {[tk windowingsystem] eq {aqua}} {
-       set M1T Cmd
-    } else {
-       set M1T Ctrl
-    }
-    toplevel $w
-    wm title $w "Gitk key bindings"
-    message $w.m -text "
-Gitk key bindings:
-
-<$M1T-Q>               Quit
-<Home>         Move to first commit
-<End>          Move to last commit
-<Up>, p, i     Move up one commit
-<Down>, n, k   Move down one commit
-<Left>, z, j   Go back in history list
-<Right>, x, l  Go forward in history list
-<PageUp>       Move up one page in commit list
-<PageDown>     Move down one page in commit list
-<$M1T-Home>    Scroll to top of commit list
-<$M1T-End>     Scroll to bottom of commit list
-<$M1T-Up>      Scroll commit list up one line
-<$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>     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
-u              Scroll diff view up 18 lines
-d              Scroll diff view down 18 lines
-<$M1T-F>               Find
-<$M1T-G>               Move to next find hit
-<Return>       Move to next find hit
-/              Move to next find hit, or redo find
-?              Move to previous find hit
-f              Scroll diff view to next file
-<$M1T-S>               Search for next hit in diff view
-<$M1T-R>               Search for previous hit in diff view
-<$M1T-KP+>     Increase font size
-<$M1T-plus>    Increase font size
-<$M1T-KP->     Decrease font size
-<$M1T-minus>   Decrease font size
-<F5>           Update
-" \
-           -justify left -bg white -border 2 -relief groove
-    pack $w.m -side top -fill both -padx 2 -pady 2
-    $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
-    bind $w <Visibility> "focus $w.ok"
-    bind $w <Key-Escape> "destroy $w"
-    bind $w <Key-Return> "destroy $w"
-}
-
-# Procedures for manipulating the file list window at the
-# bottom right of the overall window.
-
-proc treeview {w l openlevs} {
-    global treecontents treediropen treeheight treeparent treeindex
-
-    set ix 0
-    set treeindex() 0
-    set lev 0
-    set prefix {}
-    set prefixend -1
-    set prefendstack {}
-    set htstack {}
-    set ht 0
-    set treecontents() {}
-    $w conf -state normal
-    foreach f $l {
-       while {[string range $f 0 $prefixend] ne $prefix} {
-           if {$lev <= $openlevs} {
-               $w mark set e:$treeindex($prefix) "end -1c"
-               $w mark gravity e:$treeindex($prefix) left
-           }
-           set treeheight($prefix) $ht
-           incr ht [lindex $htstack end]
-           set htstack [lreplace $htstack end end]
-           set prefixend [lindex $prefendstack end]
-           set prefendstack [lreplace $prefendstack end end]
-           set prefix [string range $prefix 0 $prefixend]
-           incr lev -1
-       }
-       set tail [string range $f [expr {$prefixend+1}] end]
-       while {[set slash [string first "/" $tail]] >= 0} {
-           lappend htstack $ht
-           set ht 0
-           lappend prefendstack $prefixend
-           incr prefixend [expr {$slash + 1}]
-           set d [string range $tail 0 $slash]
-           lappend treecontents($prefix) $d
-           set oldprefix $prefix
-           append prefix $d
-           set treecontents($prefix) {}
-           set treeindex($prefix) [incr ix]
-           set treeparent($prefix) $oldprefix
-           set tail [string range $tail [expr {$slash+1}] end]
-           if {$lev <= $openlevs} {
-               set ht 1
-               set treediropen($prefix) [expr {$lev < $openlevs}]
-               set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
-               $w mark set d:$ix "end -1c"
-               $w mark gravity d:$ix left
-               set str "\n"
-               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
-               $w insert end $str
-               $w image create end -align center -image $bm -padx 1 \
-                   -name a:$ix
-               $w insert end $d [highlight_tag $prefix]
-               $w mark set s:$ix "end -1c"
-               $w mark gravity s:$ix left
-           }
-           incr lev
-       }
-       if {$tail ne {}} {
-           if {$lev <= $openlevs} {
-               incr ht
-               set str "\n"
-               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
-               $w insert end $str
-               $w insert end $tail [highlight_tag $f]
-           }
-           lappend treecontents($prefix) $tail
-       }
-    }
-    while {$htstack ne {}} {
-       set treeheight($prefix) $ht
-       incr ht [lindex $htstack end]
-       set htstack [lreplace $htstack end end]
-       set prefixend [lindex $prefendstack end]
-       set prefendstack [lreplace $prefendstack end end]
-       set prefix [string range $prefix 0 $prefixend]
-    }
-    $w conf -state disabled
-}
-
-proc linetoelt {l} {
-    global treeheight treecontents
-
-    set y 2
-    set prefix {}
-    while {1} {
-       foreach e $treecontents($prefix) {
-           if {$y == $l} {
-               return "$prefix$e"
-           }
-           set n 1
-           if {[string index $e end] eq "/"} {
-               set n $treeheight($prefix$e)
-               if {$y + $n > $l} {
-                   append prefix $e
-                   incr y
-                   break
-               }
-           }
-           incr y $n
-       }
-    }
-}
-
-proc highlight_tree {y prefix} {
-    global treeheight treecontents cflist
-
-    foreach e $treecontents($prefix) {
-       set path $prefix$e
-       if {[highlight_tag $path] ne {}} {
-           $cflist tag add bold $y.0 "$y.0 lineend"
-       }
-       incr y
-       if {[string index $e end] eq "/" && $treeheight($path) > 1} {
-           set y [highlight_tree $y $path]
-       }
-    }
-    return $y
-}
-
-proc treeclosedir {w dir} {
-    global treediropen treeheight treeparent treeindex
-
-    set ix $treeindex($dir)
-    $w conf -state normal
-    $w delete s:$ix e:$ix
-    set treediropen($dir) 0
-    $w image configure a:$ix -image tri-rt
-    $w conf -state disabled
-    set n [expr {1 - $treeheight($dir)}]
-    while {$dir ne {}} {
-       incr treeheight($dir) $n
-       set dir $treeparent($dir)
-    }
-}
-
-proc treeopendir {w dir} {
-    global treediropen treeheight treeparent treecontents treeindex
-
-    set ix $treeindex($dir)
-    $w conf -state normal
-    $w image configure a:$ix -image tri-dn
-    $w mark set e:$ix s:$ix
-    $w mark gravity e:$ix right
-    set lev 0
-    set str "\n"
-    set n [llength $treecontents($dir)]
-    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
-       incr lev
-       append str "\t"
-       incr treeheight($x) $n
-    }
-    foreach e $treecontents($dir) {
-       set de $dir$e
-       if {[string index $e end] eq "/"} {
-           set iy $treeindex($de)
-           $w mark set d:$iy e:$ix
-           $w mark gravity d:$iy left
-           $w insert e:$ix $str
-           set treediropen($de) 0
-           $w image create e:$ix -align center -image tri-rt -padx 1 \
-               -name a:$iy
-           $w insert e:$ix $e [highlight_tag $de]
-           $w mark set s:$iy e:$ix
-           $w mark gravity s:$iy left
-           set treeheight($de) 1
-       } else {
-           $w insert e:$ix $str
-           $w insert e:$ix $e [highlight_tag $de]
-       }
-    }
-    $w mark gravity e:$ix left
-    $w conf -state disabled
-    set treediropen($dir) 1
-    set top [lindex [split [$w index @0,0] .] 0]
-    set ht [$w cget -height]
-    set l [lindex [split [$w index s:$ix] .] 0]
-    if {$l < $top} {
-       $w yview $l.0
-    } elseif {$l + $n + 1 > $top + $ht} {
-       set top [expr {$l + $n + 2 - $ht}]
-       if {$l < $top} {
-           set top $l
-       }
-       $w yview $top.0
-    }
-}
-
-proc treeclick {w x y} {
-    global treediropen cmitmode ctext cflist cflist_top
-
-    if {$cmitmode ne "tree"} return
-    if {![info exists cflist_top]} return
-    set l [lindex [split [$w index "@$x,$y"] "."] 0]
-    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
-    $cflist tag add highlight $l.0 "$l.0 lineend"
-    set cflist_top $l
-    if {$l == 1} {
-       $ctext yview 1.0
-       return
-    }
-    set e [linetoelt $l]
-    if {[string index $e end] ne "/"} {
-       showfile $e
-    } elseif {$treediropen($e)} {
-       treeclosedir $w $e
-    } else {
-       treeopendir $w $e
-    }
-}
-
-proc setfilelist {id} {
-    global treefilelist cflist
-
-    treeview $cflist $treefilelist($id) 0
-}
-
-image create bitmap tri-rt -background black -foreground blue -data {
-    #define tri-rt_width 13
-    #define tri-rt_height 13
-    static unsigned char tri-rt_bits[] = {
-       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
-       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
-       0x00, 0x00};
-} -maskdata {
-    #define tri-rt-mask_width 13
-    #define tri-rt-mask_height 13
-    static unsigned char tri-rt-mask_bits[] = {
-       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
-       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
-       0x08, 0x00};
-}
-image create bitmap tri-dn -background black -foreground blue -data {
-    #define tri-dn_width 13
-    #define tri-dn_height 13
-    static unsigned char tri-dn_bits[] = {
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
-       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00};
-} -maskdata {
-    #define tri-dn-mask_width 13
-    #define tri-dn-mask_height 13
-    static unsigned char tri-dn-mask_bits[] = {
-       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
-       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
-       0x00, 0x00};
-}
-
-image create bitmap reficon-T -background black -foreground yellow -data {
-    #define tagicon_width 13
-    #define tagicon_height 9
-    static unsigned char tagicon_bits[] = {
-       0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
-       0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
-} -maskdata {
-    #define tagicon-mask_width 13
-    #define tagicon-mask_height 9
-    static unsigned char tagicon-mask_bits[] = {
-       0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
-       0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
-}
-set rectdata {
-    #define headicon_width 13
-    #define headicon_height 9
-    static unsigned char headicon_bits[] = {
-       0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
-       0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
-}
-set rectmask {
-    #define headicon-mask_width 13
-    #define headicon-mask_height 9
-    static unsigned char headicon-mask_bits[] = {
-       0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
-       0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
-}
-image create bitmap reficon-H -background black -foreground green \
-    -data $rectdata -maskdata $rectmask
-image create bitmap reficon-o -background black -foreground "#ddddff" \
-    -data $rectdata -maskdata $rectmask
-
-proc init_flist {first} {
-    global cflist cflist_top selectedline difffilestart
-
-    $cflist conf -state normal
-    $cflist delete 0.0 end
-    if {$first ne {}} {
-       $cflist insert end $first
-       set cflist_top 1
-       $cflist tag add highlight 1.0 "1.0 lineend"
-    } else {
-       catch {unset cflist_top}
-    }
-    $cflist conf -state disabled
-    set difffilestart {}
-}
-
-proc highlight_tag {f} {
-    global highlight_paths
-
-    foreach p $highlight_paths {
-       if {[string match $p $f]} {
-           return "bold"
-       }
-    }
-    return {}
-}
-
-proc highlight_filelist {} {
-    global cmitmode cflist
-
-    $cflist conf -state normal
-    if {$cmitmode ne "tree"} {
-       set end [lindex [split [$cflist index end] .] 0]
-       for {set l 2} {$l < $end} {incr l} {
-           set line [$cflist get $l.0 "$l.0 lineend"]
-           if {[highlight_tag $line] ne {}} {
-               $cflist tag add bold $l.0 "$l.0 lineend"
-           }
-       }
-    } else {
-       highlight_tree 2 {}
-    }
-    $cflist conf -state disabled
-}
-
-proc unhighlight_filelist {} {
-    global cflist
-
-    $cflist conf -state normal
-    $cflist tag remove bold 1.0 end
-    $cflist conf -state disabled
-}
-
-proc add_flist {fl} {
-    global cflist
-
-    $cflist conf -state normal
-    foreach f $fl {
-       $cflist insert end "\n"
-       $cflist insert end $f [highlight_tag $f]
-    }
-    $cflist conf -state disabled
-}
-
-proc sel_flist {w x y} {
-    global ctext difffilestart cflist cflist_top cmitmode
-
-    if {$cmitmode eq "tree"} return
-    if {![info exists cflist_top]} return
-    set l [lindex [split [$w index "@$x,$y"] "."] 0]
-    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
-    $cflist tag add highlight $l.0 "$l.0 lineend"
-    set cflist_top $l
-    if {$l == 1} {
-       $ctext yview 1.0
-    } else {
-       catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
-    }
-}
-
-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"} {
-       set e [linetoelt $l]
-       if {[string index $e end] eq "/"} return
-    } else {
-       set e [lindex $treediffs($diffids) [expr {$l-2}]]
-    }
-    set flist_menu_file $e
-    tk_popup $flist_menu $X $Y
-}
-
-proc flist_hl {only} {
-    global flist_menu_file findstring gdttype
-
-    set x [shellquote $flist_menu_file]
-    if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
-       set findstring $x
-    } else {
-       append findstring " " $x
-    }
-    set gdttype "touching paths:"
-}
-
-# Functions for adding and removing shell-type quoting
-
-proc shellquote {str} {
-    if {![string match "*\['\"\\ \t]*" $str]} {
-       return $str
-    }
-    if {![string match "*\['\"\\]*" $str]} {
-       return "\"$str\""
-    }
-    if {![string match "*'*" $str]} {
-       return "'$str'"
-    }
-    return "\"[string map {\" \\\" \\ \\\\} $str]\""
-}
-
-proc shellarglist {l} {
-    set str {}
-    foreach a $l {
-       if {$str ne {}} {
-           append str " "
-       }
-       append str [shellquote $a]
-    }
-    return $str
-}
-
-proc shelldequote {str} {
-    set ret {}
-    set used -1
-    while {1} {
-       incr used
-       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
-           append ret [string range $str $used end]
-           set used [string length $str]
-           break
-       }
-       set first [lindex $first 0]
-       set ch [string index $str $first]
-       if {$first > $used} {
-           append ret [string range $str $used [expr {$first - 1}]]
-           set used $first
-       }
-       if {$ch eq " " || $ch eq "\t"} break
-       incr used
-       if {$ch eq "'"} {
-           set first [string first "'" $str $used]
-           if {$first < 0} {
-               error "unmatched single-quote"
-           }
-           append ret [string range $str $used [expr {$first - 1}]]
-           set used $first
-           continue
-       }
-       if {$ch eq "\\"} {
-           if {$used >= [string length $str]} {
-               error "trailing backslash"
-           }
-           append ret [string index $str $used]
-           continue
-       }
-       # here ch == "\""
-       while {1} {
-           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
-               error "unmatched double-quote"
-           }
-           set first [lindex $first 0]
-           set ch [string index $str $first]
-           if {$first > $used} {
-               append ret [string range $str $used [expr {$first - 1}]]
-               set used $first
-           }
-           if {$ch eq "\""} break
-           incr used
-           append ret [string index $str $used]
-           incr used
-       }
-    }
-    return [list $used $ret]
-}
-
-proc shellsplit {str} {
-    set l {}
-    while {1} {
-       set str [string trimleft $str]
-       if {$str eq {}} break
-       set dq [shelldequote $str]
-       set n [lindex $dq 0]
-       set word [lindex $dq 1]
-       set str [string range $str $n end]
-       lappend l $word
-    }
-    return $l
-}
-
-# Code to implement multiple views
-
-proc newview {ishighlight} {
-    global nextviewnum newviewname newviewperm uifont newishighlight
-    global newviewargs revtreeargs
-
-    set newishighlight $ishighlight
-    set top .gitkview
-    if {[winfo exists $top]} {
-       raise $top
-       return
-    }
-    set newviewname($nextviewnum) "View $nextviewnum"
-    set newviewperm($nextviewnum) 0
-    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
-    vieweditor $top $nextviewnum "Gitk view definition"
-}
-
-proc editview {} {
-    global curview
-    global viewname viewperm newviewname newviewperm
-    global viewargs newviewargs
-
-    set top .gitkvedit-$curview
-    if {[winfo exists $top]} {
-       raise $top
-       return
-    }
-    set newviewname($curview) $viewname($curview)
-    set newviewperm($curview) $viewperm($curview)
-    set newviewargs($curview) [shellarglist $viewargs($curview)]
-    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
-}
-
-proc vieweditor {top n title} {
-    global newviewname newviewperm viewfiles
-    global uifont
-
-    toplevel $top
-    wm title $top $title
-    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
-    grid $top.perm - -pady 5 -sticky w
-    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
-    grid $top.args - -sticky ew -padx 5
-    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
-    if {[info exists viewfiles($n)]} {
-       foreach f $viewfiles($n) {
-           $top.t insert end $f
-           $top.t insert end "\n"
-       }
-       $top.t delete {end - 1c} end
-       $top.t mark set insert 0.0
-    }
-    grid $top.t - -sticky ew -padx 5
-    frame $top.buts
-    button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
-       -font uifont
-    button $top.buts.can -text "Cancel" -command [list destroy $top] \
-       -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
-    grid $top.buts - -pady 10 -sticky ew
-    focus $top.t
-}
-
-proc doviewmenu {m first cmd op argv} {
-    set nmenu [$m index end]
-    for {set i $first} {$i <= $nmenu} {incr i} {
-       if {[$m entrycget $i -command] eq $cmd} {
-           eval $m $op $i $argv
-           break
-       }
-    }
-}
-
-proc allviewmenus {n op args} {
-    # global viewhlmenu
-
-    doviewmenu .bar.view 5 [list showview $n] $op $args
-    # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
-}
-
-proc newviewok {top n} {
-    global nextviewnum newviewperm newviewname newishighlight
-    global viewname viewfiles viewperm selectedview curview
-    global viewargs newviewargs viewhlmenu
-
-    if {[catch {
-       set newargs [shellsplit $newviewargs($n)]
-    } err]} {
-       error_popup "Error in commit selection arguments: $err"
-       wm raise $top
-       focus $top
-       return
-    }
-    set files {}
-    foreach f [split [$top.t get 0.0 end] "\n"] {
-       set ft [string trim $f]
-       if {$ft ne {}} {
-           lappend files $ft
-       }
-    }
-    if {![info exists viewfiles($n)]} {
-       # creating a new view
-       incr nextviewnum
-       set viewname($n) $newviewname($n)
-       set viewperm($n) $newviewperm($n)
-       set viewfiles($n) $files
-       set viewargs($n) $newargs
-       addviewmenu $n
-       if {!$newishighlight} {
-           run showview $n
-       } else {
-           run addvhighlight $n
-       }
-    } else {
-       # editing an existing view
-       set viewperm($n) $newviewperm($n)
-       if {$newviewname($n) ne $viewname($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)]
-       }
-       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
-           set viewfiles($n) $files
-           set viewargs($n) $newargs
-           if {$curview == $n} {
-               run updatecommits
-           }
-       }
-    }
-    catch {destroy $top}
-}
-
-proc delview {} {
-    global curview viewdata viewperm hlview selectedhlview
-
-    if {$curview == 0} return
-    if {[info exists hlview] && $hlview == $curview} {
-       set selectedhlview None
-       unset hlview
-    }
-    allviewmenus $curview delete
-    set viewdata($curview) {}
-    set viewperm($curview) 0
-    showview 0
-}
-
-proc addviewmenu {n} {
-    global viewname viewhlmenu
-
-    .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
-}
-
-proc flatten {var} {
-    global $var
-
-    set ret {}
-    foreach i [array names $var] {
-       lappend ret $i [set $var\($i\)]
-    }
-    return $ret
-}
-
-proc unflatten {var l} {
-    global $var
-
-    catch {unset $var}
-    foreach {i v} $l {
-       set $var\($i\) $v
-    }
-}
-
-proc showview {n} {
-    global curview viewdata viewfiles
-    global displayorder parentlist rowidlist rowisopt rowfinal
-    global colormap rowtextx commitrow nextcolor canvxmax
-    global numcommits commitlisted
-    global selectedline currentid canv canvy0
-    global treediffs
-    global pending_select phase
-    global commitidx
-    global commfd
-    global selectedview selectfirst
-    global vparentlist vdisporder vcmitlisted
-    global hlview selectedhlview commitinterest
-
-    if {$n == $curview} return
-    set selid {}
-    if {[info exists selectedline]} {
-       set selid $currentid
-       set y [yc $selectedline]
-       set ymax [lindex [$canv cget -scrollregion] 3]
-       set span [$canv yview]
-       set ytop [expr {[lindex $span 0] * $ymax}]
-       set ybot [expr {[lindex $span 1] * $ymax}]
-       if {$ytop < $y && $y < $ybot} {
-           set yscreen [expr {$y - $ytop}]
-       } else {
-           set yscreen [expr {($ybot - $ytop) / 2}]
-       }
-    } elseif {[info exists pending_select]} {
-       set selid $pending_select
-       unset pending_select
-    }
-    unselectline
-    normalline
-    if {$curview >= 0} {
-       set vparentlist($curview) $parentlist
-       set vdisporder($curview) $displayorder
-       set vcmitlisted($curview) $commitlisted
-       if {$phase ne {} ||
-           ![info exists viewdata($curview)] ||
-           [lindex $viewdata($curview) 0] ne {}} {
-           set viewdata($curview) \
-               [list $phase $rowidlist $rowisopt $rowfinal]
-       }
-    }
-    catch {unset treediffs}
-    clear_display
-    if {[info exists hlview] && $hlview == $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
-       }
-       getcommits
-       return
-    }
-
-    set v $viewdata($n)
-    set phase [lindex $v 0]
-    set displayorder $vdisporder($n)
-    set parentlist $vparentlist($n)
-    set commitlisted $vcmitlisted($n)
-    set rowidlist [lindex $v 1]
-    set rowisopt [lindex $v 2]
-    set rowfinal [lindex $v 3]
-    set numcommits $commitidx($n)
-
-    catch {unset colormap}
-    catch {unset rowtextx}
-    set nextcolor 0
-    set canvxmax [$canv cget -width]
-    set curview $n
-    set row 0
-    setcanvscroll
-    set yf 0
-    set row {}
-    set selectfirst 0
-    if {$selid ne {} && [info exists commitrow($n,$selid)]} {
-       set row $commitrow($n,$selid)
-       # try to get the selected row in the same position on the screen
-       set ymax [lindex [$canv cget -scrollregion] 3]
-       set ytop [expr {[yc $row] - $yscreen}]
-       if {$ytop < 0} {
-           set ytop 0
-       }
-       set yf [expr {$ytop * 1.0 / $ymax}]
-    }
-    allcanvs yview moveto $yf
-    drawvisible
-    if {$row ne {}} {
-       selectline $row 0
-    } elseif {$selid ne {}} {
-       set pending_select $selid
-    } else {
-       set row [first_real_row]
-       if {$row < $numcommits} {
-           selectline $row 0
-       } else {
-           set selectfirst 1
-       }
-    }
-    if {$phase ne {}} {
-       if {$phase eq "getcommits"} {
-           show_status "Reading commits..."
-       }
-       run chewcommits $n
-    } elseif {$numcommits == 0} {
-       show_status "No commits selected"
-    }
-}
-
-# Stuff relating to the highlighting facility
-
-proc ishighlighted {row} {
-    global vhighlights fhighlights nhighlights rhighlights
-
-    if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
-       return $nhighlights($row)
-    }
-    if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
-       return $vhighlights($row)
-    }
-    if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
-       return $fhighlights($row)
-    }
-    if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
-       return $rhighlights($row)
-    }
-    return 0
-}
-
-proc bolden {row font} {
-    global canv linehtag selectedline boldrows
-
-    lappend boldrows $row
-    $canv itemconf $linehtag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
-       $canv delete secsel
-       set t [eval $canv create rect [$canv bbox $linehtag($row)] \
-                  -outline {{}} -tags secsel \
-                  -fill [$canv cget -selectbackground]]
-       $canv lower $t
-    }
-}
-
-proc bolden_name {row font} {
-    global canv2 linentag selectedline boldnamerows
-
-    lappend boldnamerows $row
-    $canv2 itemconf $linentag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
-       $canv2 delete secsel
-       set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
-                  -outline {{}} -tags secsel \
-                  -fill [$canv2 cget -selectbackground]]
-       $canv2 lower $t
-    }
-}
-
-proc unbolden {} {
-    global boldrows
-
-    set stillbold {}
-    foreach row $boldrows {
-       if {![ishighlighted $row]} {
-           bolden $row mainfont
-       } else {
-           lappend stillbold $row
-       }
-    }
-    set boldrows $stillbold
-}
-
-proc addvhighlight {n} {
-    global hlview curview viewdata vhl_done vhighlights commitidx
-
-    if {[info exists hlview]} {
-       delvhighlight
-    }
-    set hlview $n
-    if {$n != $curview && ![info exists viewdata($n)]} {
-       set viewdata($n) [list getcommits {{}} 0 0 0]
-       set vparentlist($n) {}
-       set vdisporder($n) {}
-       set vcmitlisted($n) {}
-       start_rev_list $n
-    }
-    set vhl_done $commitidx($hlview)
-    if {$vhl_done > 0} {
-       drawvisible
-    }
-}
-
-proc delvhighlight {} {
-    global hlview vhighlights
-
-    if {![info exists hlview]} return
-    unset hlview
-    catch {unset vhighlights}
-    unbolden
-}
-
-proc vhighlightmore {} {
-    global hlview vhl_done commitidx vhighlights
-    global displayorder vdisporder curview
-
-    set max $commitidx($hlview)
-    if {$hlview == $curview} {
-       set disp $displayorder
-    } else {
-       set disp $vdisporder($hlview)
-    }
-    set vr [visiblerows]
-    set r0 [lindex $vr 0]
-    set r1 [lindex $vr 1]
-    for {set i $vhl_done} {$i < $max} {incr i} {
-       set id [lindex $disp $i]
-       if {[info exists commitrow($curview,$id)]} {
-           set row $commitrow($curview,$id)
-           if {$r0 <= $row && $row <= $r1} {
-               if {![highlighted $row]} {
-                   bolden $row mainfontbold
-               }
-               set vhighlights($row) 1
-           }
-       }
-    }
-    set vhl_done $max
-}
-
-proc askvhighlight {row id} {
-    global hlview vhighlights commitrow iddrawn
-
-    if {[info exists commitrow($hlview,$id)]} {
-       if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
-           bolden $row mainfontbold
-       }
-       set vhighlights($row) 1
-    } else {
-       set vhighlights($row) 0
-    }
-}
-
-proc hfiles_change {} {
-    global highlight_files filehighlight fhighlights fh_serial
-    global highlight_paths gdttype
-
-    if {[info exists filehighlight]} {
-       # delete previous highlights
-       catch {close $filehighlight}
-       unset filehighlight
-       catch {unset fhighlights}
-       unbolden
-       unhighlight_filelist
-    }
-    set highlight_paths {}
-    after cancel do_file_hl $fh_serial
-    incr fh_serial
-    if {$highlight_files ne {}} {
-       after 300 do_file_hl $fh_serial
-    }
-}
-
-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 {
-       set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
-       if {[string index $ee end] eq "/"} {
-           lappend ret "$ee*"
-       } else {
-           lappend ret $ee
-           lappend ret "$ee/*"
-       }
-    }
-    return $ret
-}
-
-proc do_file_hl {serial} {
-    global highlight_files filehighlight highlight_paths gdttype fhl_list
-
-    if {$gdttype eq "touching paths:"} {
-       if {[catch {set paths [shellsplit $highlight_files]}]} return
-       set highlight_paths [makepatterns $paths]
-       highlight_filelist
-       set gdtargs [concat -- $paths]
-    } 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+]
-    fconfigure $filehighlight -blocking 0
-    filerun $filehighlight readfhighlight
-    set fhl_list {}
-    drawvisible
-    flushhighlights
-}
-
-proc flushhighlights {} {
-    global filehighlight fhl_list
-
-    if {[info exists filehighlight]} {
-       lappend fhl_list {}
-       puts $filehighlight ""
-       flush $filehighlight
-    }
-}
-
-proc askfilehighlight {row id} {
-    global filehighlight fhighlights fhl_list
-
-    lappend fhl_list $id
-    set fhighlights($row) -1
-    puts $filehighlight $id
-}
-
-proc readfhighlight {} {
-    global filehighlight fhighlights commitrow curview iddrawn
-    global fhl_list find_dirn
-
-    if {![info exists filehighlight]} {
-       return 0
-    }
-    set nr 0
-    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
-       set line [string trim $line]
-       set i [lsearch -exact $fhl_list $line]
-       if {$i < 0} continue
-       for {set j 0} {$j < $i} {incr j} {
-           set id [lindex $fhl_list $j]
-           if {[info exists commitrow($curview,$id)]} {
-               set fhighlights($commitrow($curview,$id)) 0
-           }
-       }
-       set fhl_list [lrange $fhl_list [expr {$i+1}] end]
-       if {$line eq {}} continue
-       if {![info exists commitrow($curview,$line)]} continue
-       set row $commitrow($curview,$line)
-       if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
-           bolden $row mainfontbold
-       }
-       set fhighlights($row) 1
-    }
-    if {[eof $filehighlight]} {
-       # strange...
-       puts "oops, git diff-tree died"
-       catch {close $filehighlight}
-       unset filehighlight
-       return 0
-    }
-    if {[info exists find_dirn]} {
-       run findmore
-    }
-    return 1
-}
-
-proc doesmatch {f} {
-    global findtype findpattern
-
-    if {$findtype eq "Regexp"} {
-       return [regexp $findpattern $f]
-    } elseif {$findtype eq "IgnCase"} {
-       return [string match -nocase $findpattern $f]
-    } else {
-       return [string match $findpattern $f]
-    }
-}
-
-proc askfindhighlight {row id} {
-    global nhighlights commitinfo iddrawn
-    global findloc
-    global markingmatches
-
-    if {![info exists commitinfo($id)]} {
-       getcommit $id
-    }
-    set info $commitinfo($id)
-    set isbold 0
-    set fldtypes {Headline Author Date Committer CDate Comments}
-    foreach f $info ty $fldtypes {
-       if {($findloc eq "All fields" || $findloc eq $ty) &&
-           [doesmatch $f]} {
-           if {$ty eq "Author"} {
-               set isbold 2
-               break
-           }
-           set isbold 1
-       }
-    }
-    if {$isbold && [info exists iddrawn($id)]} {
-       if {![ishighlighted $row]} {
-           bolden $row mainfontbold
-           if {$isbold > 1} {
-               bolden_name $row mainfontbold
-           }
-       }
-       if {$markingmatches} {
-           markrowmatches $row $id
-       }
-    }
-    set nhighlights($row) $isbold
-}
-
-proc markrowmatches {row id} {
-    global canv canv2 linehtag linentag commitinfo findloc
-
-    set headline [lindex $commitinfo($id) 0]
-    set author [lindex $commitinfo($id) 1]
-    $canv delete match$row
-    $canv2 delete match$row
-    if {$findloc eq "All fields" || $findloc eq "Headline"} {
-       set m [findmatches $headline]
-       if {$m ne {}} {
-           markmatches $canv $row $headline $linehtag($row) $m \
-               [$canv itemcget $linehtag($row) -font] $row
-       }
-    }
-    if {$findloc eq "All fields" || $findloc eq "Author"} {
-       set m [findmatches $author]
-       if {$m ne {}} {
-           markmatches $canv2 $row $author $linentag($row) $m \
-               [$canv2 itemcget $linentag($row) -font] $row
-       }
-    }
-}
-
-proc vrel_change {name ix op} {
-    global highlight_related
-
-    rhighlight_none
-    if {$highlight_related ne "None"} {
-       run drawvisible
-    }
-}
-
-# prepare for testing whether commits are descendents or ancestors of a
-proc rhighlight_sel {a} {
-    global descendent desc_todo ancestor anc_todo
-    global highlight_related rhighlights
-
-    catch {unset descendent}
-    set desc_todo [list $a]
-    catch {unset ancestor}
-    set anc_todo [list $a]
-    if {$highlight_related ne "None"} {
-       rhighlight_none
-       run drawvisible
-    }
-}
-
-proc rhighlight_none {} {
-    global rhighlights
-
-    catch {unset rhighlights}
-    unbolden
-}
-
-proc is_descendent {a} {
-    global curview children commitrow descendent desc_todo
-
-    set v $curview
-    set la $commitrow($v,$a)
-    set todo $desc_todo
-    set leftover {}
-    set done 0
-    for {set i 0} {$i < [llength $todo]} {incr i} {
-       set do [lindex $todo $i]
-       if {$commitrow($v,$do) < $la} {
-           lappend leftover $do
-           continue
-       }
-       foreach nk $children($v,$do) {
-           if {![info exists descendent($nk)]} {
-               set descendent($nk) 1
-               lappend todo $nk
-               if {$nk eq $a} {
-                   set done 1
-               }
-           }
-       }
-       if {$done} {
-           set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
-           return
-       }
-    }
-    set descendent($a) 0
-    set desc_todo $leftover
-}
-
-proc is_ancestor {a} {
-    global curview parentlist commitrow ancestor anc_todo
-
-    set v $curview
-    set la $commitrow($v,$a)
-    set todo $anc_todo
-    set leftover {}
-    set done 0
-    for {set i 0} {$i < [llength $todo]} {incr i} {
-       set do [lindex $todo $i]
-       if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
-           lappend leftover $do
-           continue
-       }
-       foreach np [lindex $parentlist $commitrow($v,$do)] {
-           if {![info exists ancestor($np)]} {
-               set ancestor($np) 1
-               lappend todo $np
-               if {$np eq $a} {
-                   set done 1
-               }
-           }
-       }
-       if {$done} {
-           set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
-           return
-       }
-    }
-    set ancestor($a) 0
-    set anc_todo $leftover
-}
-
-proc askrelhighlight {row id} {
-    global descendent highlight_related iddrawn rhighlights
-    global selectedline ancestor
-
-    if {![info exists selectedline]} return
-    set isbold 0
-    if {$highlight_related eq "Descendent" ||
-       $highlight_related eq "Not descendent"} {
-       if {![info exists descendent($id)]} {
-           is_descendent $id
-       }
-       if {$descendent($id) == ($highlight_related eq "Descendent")} {
-           set isbold 1
-       }
-    } elseif {$highlight_related eq "Ancestor" ||
-             $highlight_related eq "Not ancestor"} {
-       if {![info exists ancestor($id)]} {
-           is_ancestor $id
-       }
-       if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
-           set isbold 1
-       }
-    }
-    if {[info exists iddrawn($id)]} {
-       if {$isbold && ![ishighlighted $row]} {
-           bolden $row mainfontbold
-       }
-    }
-    set rhighlights($row) $isbold
-}
-
-# Graph layout functions
-
-proc shortids {ids} {
-    set res {}
-    foreach id $ids {
-       if {[llength $id] > 1} {
-           lappend res [shortids $id]
-       } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
-           lappend res [string range $id 0 7]
-       } else {
-           lappend res $id
-       }
-    }
-    return $res
-}
-
-proc ntimes {n o} {
-    set ret {}
-    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 $ret
-}
-
-# 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 t $ordertok($curview,$id)
-    if {$i >= [llength $idlist] ||
-       $t < $ordertok($curview,[lindex $idlist $i])} {
-       if {$i > [llength $idlist]} {
-           set i [llength $idlist]
-       }
-       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])} {}
-       }
-    }
-    return $i
-}
-
-proc initlayout {} {
-    global rowidlist rowisopt rowfinal displayorder commitlisted
-    global numcommits canvxmax canv
-    global nextcolor
-    global parentlist
-    global colormap rowtextx
-    global selectfirst
-
-    set numcommits 0
-    set displayorder {}
-    set commitlisted {}
-    set parentlist {}
-    set nextcolor 0
-    set rowidlist {}
-    set rowisopt {}
-    set rowfinal {}
-    set canvxmax [$canv cget -width]
-    catch {unset colormap}
-    catch {unset rowtextx}
-    set selectfirst 1
-}
-
-proc setcanvscroll {} {
-    global canv canv2 canv3 numcommits linespc canvxmax canvy0
-
-    set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
-    $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
-    $canv2 conf -scrollregion [list 0 0 0 $ymax]
-    $canv3 conf -scrollregion [list 0 0 0 $ymax]
-}
-
-proc visiblerows {} {
-    global canv numcommits linespc
-
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax eq {} || $ymax == 0} return
-    set f [$canv yview]
-    set y0 [expr {int([lindex $f 0] * $ymax)}]
-    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
-    if {$r0 < 0} {
-       set r0 0
-    }
-    set y1 [expr {int([lindex $f 1] * $ymax)}]
-    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
-    if {$r1 >= $numcommits} {
-       set r1 [expr {$numcommits - 1}]
-    }
-    return [list $r0 $r1]
-}
-
-proc layoutmore {} {
-    global commitidx viewcomplete numcommits
-    global uparrowlen downarrowlen mingaplen curview
-
-    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 mainheadid displayorder selectfirst
-    global lastscrollset commitinterest
-
-    if {$numcommits == 0} {
-       global phase
-       set phase "incrdraw"
-       allcanvs delete all
-    }
-    set r0 $numcommits
-    set prev $numcommits
-    set numcommits $canshow
-    set t [clock clicks -milliseconds]
-    if {$prev < 100 || $last || $t - $lastscrollset > 500} {
-       set lastscrollset $t
-       setcanvscroll
-    }
-    set rows [visiblerows]
-    set r1 [lindex $rows 1]
-    if {$r1 >= $canshow} {
-       set r1 [expr {$canshow - 1}]
-    }
-    if {$r0 <= $r1} {
-       drawcommits $r0 $r1
-    }
-    if {[info exists pending_select] &&
-       [info exists commitrow($curview,$pending_select)] &&
-       $commitrow($curview,$pending_select) < $numcommits} {
-       selectline $commitrow($curview,$pending_select) 1
-    }
-    if {$selectfirst} {
-       if {[info exists selectedline] || [info exists pending_select]} {
-           set selectfirst 0
-       } else {
-           set l [first_real_row]
-           selectline $l 1
-           set selectfirst 0
-       }
-    }
-}
-
-proc doshowlocalchanges {} {
-    global curview mainheadid phase commitrow
-
-    if {[info exists commitrow($curview,$mainheadid)] &&
-       ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
-       dodiffindex
-    } elseif {$phase ne {}} {
-       lappend commitinterest($mainheadid) {}
-    }
-}
-
-proc dohidelocalchanges {} {
-    global localfrow localirow lserial
-
-    if {$localfrow >= 0} {
-       removerow $localfrow
-       set localfrow -1
-       if {$localirow > 0} {
-           incr localirow -1
-       }
-    }
-    if {$localirow >= 0} {
-       removerow $localirow
-       set localirow -1
-    }
-    incr lserial
-}
-
-# spawn off a process to do git diff-index --cached HEAD
-proc dodiffindex {} {
-    global localirow localfrow lserial showlocalchanges
-
-    if {!$showlocalchanges} return
-    incr lserial
-    set localfrow -1
-    set localirow -1
-    set fd [open "|git diff-index --cached HEAD" r]
-    fconfigure $fd -blocking 0
-    filerun $fd [list readdiffindex $fd $lserial]
-}
-
-proc readdiffindex {fd serial} {
-    global localirow commitrow mainheadid nullid2 curview
-    global commitinfo commitdata lserial
-
-    set isdiff 1
-    if {[gets $fd line] < 0} {
-       if {![eof $fd]} {
-           return 1
-       }
-       set isdiff 0
-    }
-    # we only need to see one line and we don't really care what it says...
-    close $fd
-
-    # now see if there are any local changes not checked in to the index
-    if {$serial == $lserial} {
-       set fd [open "|git diff-files" r]
-       fconfigure $fd -blocking 0
-       filerun $fd [list readdifffiles $fd $serial]
-    }
-
-    if {$isdiff && $serial == $lserial && $localirow == -1} {
-       # add the line for the changes in the index to the graph
-       set localirow $commitrow($curview,$mainheadid)
-       set hl "Local changes checked in to index but not committed"
-       set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
-       set commitdata($nullid2) "\n    $hl\n"
-       insertrow $localirow $nullid2
-    }
-    return 0
-}
-
-proc readdifffiles {fd serial} {
-    global localirow localfrow commitrow mainheadid nullid curview
-    global commitinfo commitdata lserial
-
-    set isdiff 1
-    if {[gets $fd line] < 0} {
-       if {![eof $fd]} {
-           return 1
-       }
-       set isdiff 0
-    }
-    # we only need to see one line and we don't really care what it says...
-    close $fd
-
-    if {$isdiff && $serial == $lserial && $localfrow == -1} {
-       # add the line for the local diff to the graph
-       if {$localirow >= 0} {
-           set localfrow $localirow
-           incr localirow
-       } else {
-           set localfrow $commitrow($curview,$mainheadid)
-       }
-       set hl "Local uncommitted changes, not checked in to index"
-       set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
-       set commitdata($nullid) "\n    $hl\n"
-       insertrow $localfrow $nullid
-    }
-    return 0
-}
-
-proc nextuse {id row} {
-    global commitrow curview children
-
-    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)
-           }
-       }
-    }
-    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)
-           }
-       }
-    }
-    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]
-           }
-       }
-    }
-    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]
-           }
-       }
-    }
-    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 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]
-           }
-       }
-    }
-    set idlist {}
-    foreach idx [lsort -unique $ids] {
-       lappend idlist [lindex $idx 1]
-    }
-    return $idlist
-}
-
-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}]
-}
-
-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}]
-    }
-    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 layoutrows {row endrow} {
-    global rowidlist rowisopt rowfinal displayorder
-    global uparrowlen downarrowlen maxwidth mingaplen
-    global children parentlist
-    global commitidx viewcomplete curview commitrow
-
-    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
-
-    set pad [ntimes $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 rowisopt displayorder curview children
-
-    if {$row < 1} {
-       set row 1
-    }
-    for {} {$row < $endrow} {incr row; set col 0} {
-       if {[lindex $rowisopt $row]} continue
-       set haspad 0
-       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 x0 [lsearch -exact $previdlist $id]
-           if {$x0 < 0} continue
-           set z [expr {$x0 - $col}]
-           set isarrow 0
-           set z0 {}
-           if {$ym >= 0} {
-               set xm [lsearch -exact $pprevidlist $id]
-               if {$xm >= 0} {
-                   set z0 [expr {$xm - $x0}]
-               }
-           }
-           if {$z0 eq {}} {
-               # 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
-           # or at 45 degrees.
-           if {$z < -1 || ($z < 0 && $isarrow)} {
-               # Line currently goes left too much;
-               # insert pads in the previous row, then optimize it
-               set npad [expr {-1 - $z + $isarrow}]
-               insert_pad $y0 $x0 $npad
-               if {$y0 > 0} {
-                   optimize_rows $y0 $x0 $row
-               }
-               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
-               set npad [expr {$z - 1 + $isarrow}]
-               insert_pad $row $col $npad
-               set idlist [lindex $rowidlist $row]
-               incr col $npad
-               set z [expr {$x0 - $col}]
-               set haspad 1
-           }
-           if {$z0 eq {} && !$isarrow && $ym >= 0} {
-               # this line links to its first child on row $row-2
-               set id [lindex $displayorder $ym]
-               set xc [lsearch -exact $pprevidlist $id]
-               if {$xc >= 0} {
-                   set z0 [expr {$xc - $x0}]
-               }
-           }
-           # avoid lines jigging left then immediately right
-           if {$z0 ne {} && $z < 0 && $z0 > 0} {
-               insert_pad $y0 $x0 1
-               incr x0
-               optimize_rows $y0 $x0 $row
-               set previdlist [lindex $rowidlist $y0]
-           }
-       }
-       if {!$haspad} {
-           # Find the first column that doesn't have a line going right
-           for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
-               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 kid [lindex $displayorder $y0]
-                   if {[lindex $children($curview,$id) 0] eq $kid} {
-                       # it is, work out offset to child
-                       set x0 [lsearch -exact $previdlist $kid]
-                   }
-               }
-               if {$x0 <= $col} break
-           }
-           # Insert a pad at that column as long as it has a line and
-           # isn't the last column
-           if {$x0 >= 0 && [incr col] < [llength $idlist]} {
-               set idlist [linsert $idlist $col {}]
-               lset rowidlist $row $idlist
-               changedrow $row
-           }
-       }
-    }
-}
-
-proc xc {row col} {
-    global canvx0 linespc
-    return [expr {$canvx0 + $col * $linespc}]
-}
-
-proc yc {row} {
-    global canvy0 linespc
-    return [expr {$canvy0 + $row * $linespc}]
-}
-
-proc linewidth {id} {
-    global thickerline lthickness
-
-    set wid $lthickness
-    if {[info exists thickerline] && $id eq $thickerline} {
-       set wid [expr {2 * $lthickness}]
-    }
-    return $wid
-}
-
-proc rowranges {id} {
-    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 {
-           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 $ret
-}
-
-proc drawlineseg {id row endrow arrowlow} {
-    global rowidlist displayorder iddrawn linesegs
-    global canv colormap linespc curview maxlinelen parentlist
-
-    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
-    set le [expr {$row + 1}]
-    set arrowhigh 1
-    while {1} {
-       set c [lsearch -exact [lindex $rowidlist $le] $id]
-       if {$c < 0} {
-           incr le -1
-           break
-       }
-       lappend cols $c
-       set x [lindex $displayorder $le]
-       if {$x eq $id} {
-           set arrowhigh 0
-           break
-       }
-       if {[info exists iddrawn($x)] || $le == $endrow} {
-           set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
-           if {$c >= 0} {
-               lappend cols $c
-               set arrowhigh 0
-           }
-           break
-       }
-       incr le
-    }
-    if {$le <= $row} {
-       return $row
-    }
-
-    set lines {}
-    set i 0
-    set joinhigh 0
-    if {[info exists linesegs($id)]} {
-       set lines $linesegs($id)
-       foreach li $lines {
-           set r0 [lindex $li 0]
-           if {$r0 > $row} {
-               if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
-                   set joinhigh 1
-               }
-               break
-           }
-           incr i
-       }
-    }
-    set joinlow 0
-    if {$i > 0} {
-       set li [lindex $lines [expr {$i-1}]]
-       set r1 [lindex $li 1]
-       if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
-           set joinlow 1
-       }
-    }
-
-    set x [lindex $cols [expr {$le - $row}]]
-    set xp [lindex $cols [expr {$le - 1 - $row}]]
-    set dir [expr {$xp - $x}]
-    if {$joinhigh} {
-       set ith [lindex $lines $i 2]
-       set coords [$canv coords $ith]
-       set ah [$canv itemcget $ith -arrow]
-       set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
-       set x2 [lindex $cols [expr {$le + 1 - $row}]]
-       if {$x2 ne {} && $x - $x2 == $dir} {
-           set coords [lrange $coords 0 end-2]
-       }
-    } else {
-       set coords [list [xc $le $x] [yc $le]]
-    }
-    if {$joinlow} {
-       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} {
-       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} {} {
-       set x $xp
-       set xp [lindex $cols [expr {$y - 1 - $row}]]
-       set ndir [expr {$xp - $x}]
-       if {$dir != $ndir || $xp < 0} {
-           lappend coords [xc $y $x] [yc $y]
-       }
-       set dir $ndir
-    }
-    if {!$joinlow} {
-       if {$xp < 0} {
-           # join parent line to first child
-           set ch [lindex $displayorder $row]
-           set xc [lsearch -exact [lindex $rowidlist $row] $ch]
-           if {$xc < 0} {
-               puts "oops: drawlineseg: child $ch not on row $row"
-           } 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]
-               }
-               set x $xc
-           }
-           lappend coords [xc $row $x] [yc $row]
-       } else {
-           set xn [xc $row $xp]
-           set yn [yc $row]
-           lappend coords $xn $yn
-       }
-       if {!$joinhigh} {
-           assigncolor $id
-           set t [$canv create line $coords -width [linewidth $id] \
-                      -fill $colormap($id) -tags lines.$id -arrow $arrow]
-           $canv lower $t
-           bindline $t $id
-           set lines [linsert $lines $i [list $row $le $t]]
-       } else {
-           $canv coords $ith $coords
-           if {$arrow ne $ah} {
-               $canv itemconf $ith -arrow $arrow
-           }
-           lset lines $i 0 $row
-       }
-    } else {
-       set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
-       set ndir [expr {$xo - $xp}]
-       set clow [$canv coords $itl]
-       if {$dir == $ndir} {
-           set clow [lrange $clow 2 end]
-       }
-       set coords [concat $coords $clow]
-       if {!$joinhigh} {
-           lset lines [expr {$i-1}] 1 $le
-       } else {
-           # coalesce two pieces
-           $canv delete $ith
-           set b [lindex $lines [expr {$i-1}] 0]
-           set e [lindex $lines $i 1]
-           set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
-       }
-       $canv coords $itl $coords
-       if {$arrow ne $al} {
-           $canv itemconf $itl -arrow $arrow
-       }
-    }
-
-    set linesegs($id) $lines
-    return $le
-}
-
-proc drawparentlinks {id row} {
-    global rowidlist canv colormap curview parentlist
-    global idpos linespc
-
-    set rowids [lindex $rowidlist $row]
-    set col [lsearch -exact $rowids $id]
-    if {$col < 0} return
-    set olds [lindex $parentlist $row]
-    set row2 [expr {$row + 1}]
-    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
-    foreach p $olds {
-       set i [lsearch -exact $ids $p]
-       if {$i < 0} {
-           puts "oops, parent $p of $id not in list"
-           continue
-       }
-       set x2 [xc $row2 $i]
-       if {$x2 > $rmx} {
-           set rmx $x2
-       }
-       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} {
-           # 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
-       }
-       set t [$canv create line $coords -width [linewidth $p] \
-                  -fill $colormap($p) -tags lines.$p]
-       $canv lower $t
-       bindline $t $p
-    }
-    if {$rmx > [lindex $idpos($id) 1]} {
-       lset idpos($id) 1 $rmx
-       redrawtags $id
-    }
-}
-
-proc drawlines {id} {
-    global canv
-
-    $canv itemconf lines.$id -width [linewidth $id]
-}
-
-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 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]
-    if {$id eq $nullid} {
-       set ofill red
-    } elseif {$id eq $nullid2} {
-       set ofill green
-    } else {
-       set ofill [expr {$listed != 0? "blue": "white"}]
-    }
-    set x [xc $row $col]
-    set y [yc $row]
-    set orad [expr {$linespc / 3}]
-    if {$listed <= 1} {
-       set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
-                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
-                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
-    } elseif {$listed == 2} {
-       # triangle pointing left for left-side commits
-       set t [$canv create polygon \
-                  [expr {$x - $orad}] $y \
-                  [expr {$x + $orad - 1}] [expr {$y - $orad}] \
-                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
-                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
-    } else {
-       # triangle pointing right for right-side commits
-       set t [$canv create polygon \
-                  [expr {$x + $orad - 1}] $y \
-                  [expr {$x - $orad}] [expr {$y - $orad}] \
-                  [expr {$x - $orad}] [expr {$y + $orad - 1}] \
-                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
-    }
-    $canv raise $t
-    $canv bind $t <1> {selcanvline {} %x %y}
-    set rmx [llength [lindex $rowidlist $row]]
-    set olds [lindex $parentlist $row]
-    if {$olds ne {}} {
-       set nextids [lindex $rowidlist [expr {$row + 1}]]
-       foreach p $olds {
-           set i [lsearch -exact $nextids $p]
-           if {$i > $rmx} {
-               set rmx $i
-           }
-       }
-    }
-    set xt [xc $row $rmx]
-    set rowtextx($row) $xt
-    set idpos($id) [list $x $xt $y]
-    if {[info exists idtags($id)] || [info exists idheads($id)]
-       || [info exists idotherrefs($id)]} {
-       set xt [drawtags $id $x $xt $y]
-    }
-    set headline [lindex $commitinfo($id) 0]
-    set name [lindex $commitinfo($id) 1]
-    set date [lindex $commitinfo($id) 2]
-    set date [formatdate $date]
-    set font mainfont
-    set nfont mainfont
-    set isbold [ishighlighted $row]
-    if {$isbold > 0} {
-       lappend boldrows $row
-       set font mainfontbold
-       if {$isbold > 1} {
-           lappend boldnamerows $row
-           set nfont mainfontbold
-       }
-    }
-    set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
-                           -text $headline -font $font -tags text]
-    $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
-    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]
-    if {[info exists selectedline] && $selectedline == $row} {
-       make_secsel $row
-    }
-    set xr [expr {$xt + [font measure $font $headline]}]
-    if {$xr > $canvxmax} {
-       set canvxmax $xr
-       setcanvscroll
-    }
-}
-
-proc drawcmitrow {row} {
-    global displayorder rowidlist nrows_drawn
-    global iddrawn markingmatches
-    global commitinfo parentlist numcommits
-    global filehighlight fhighlights findpattern nhighlights
-    global hlview vhighlights
-    global highlight_related rhighlights
-
-    if {$row >= $numcommits} return
-
-    set id [lindex $displayorder $row]
-    if {[info exists hlview] && ![info exists vhighlights($row)]} {
-       askvhighlight $row $id
-    }
-    if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
-       askfilehighlight $row $id
-    }
-    if {$findpattern ne {} && ![info exists nhighlights($row)]} {
-       askfindhighlight $row $id
-    }
-    if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
-       askrelhighlight $row $id
-    }
-    if {![info exists iddrawn($id)]} {
-       set col [lsearch -exact [lindex $rowidlist $row] $id]
-       if {$col < 0} {
-           puts "oops, row $row id $id not in list"
-           return
-       }
-       if {![info exists commitinfo($id)]} {
-           getcommit $id
-       }
-       assigncolor $id
-       drawcmittext $id $row $col
-       set iddrawn($id) 1
-       incr nrows_drawn
-    }
-    if {$markingmatches} {
-       markrowmatches $row $id
-    }
-}
-
-proc drawcommits {row {endrow {}}} {
-    global numcommits iddrawn displayorder curview need_redisplay
-    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
-
-    if {$row < 0} {
-       set row 0
-    }
-    if {$endrow eq {}} {
-       set endrow $row
-    }
-    if {$endrow >= $numcommits} {
-       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])]} {
-       set r $row
-    }
-    set er [expr {$endrow + 1}]
-    if {$er >= $numcommits ||
-       ![info exists iddrawn([lindex $displayorder $er])]} {
-       set er $endrow
-    }
-    for {} {$r <= $er} {incr r} {
-       set id [lindex $displayorder $r]
-       set wasdrawn [info exists iddrawn($id)]
-       drawcmitrow $r
-       if {$r == $er} break
-       set nextid [lindex $displayorder [expr {$r + 1}]]
-       if {$wasdrawn && [info exists iddrawn($nextid)]} continue
-       drawparentlinks $id $r
-
-       set rowids [lindex $rowidlist $r]
-       foreach lid $rowids {
-           if {$lid eq {}} continue
-           if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
-           if {$lid eq $id} {
-               # see if this is the first child of any of its parents
-               foreach p [lindex $parentlist $r] {
-                   if {[lsearch -exact $rowids $p] < 0} {
-                       # make this line extend up to the child
-                       set lineend($p) [drawlineseg $p $r $er 0]
-                   }
-               }
-           } else {
-               set lineend($lid) [drawlineseg $lid $r $er 1]
-           }
-       }
-    }
-}
-
-proc drawfrac {f0 f1} {
-    global canv linespc
-
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax eq {} || $ymax == 0} return
-    set y0 [expr {int($f0 * $ymax)}]
-    set row [expr {int(($y0 - 3) / $linespc) - 1}]
-    set y1 [expr {int($f1 * $ymax)}]
-    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
-    drawcommits $row $endrow
-}
-
-proc drawvisible {} {
-    global canv
-    eval drawfrac [$canv yview]
-}
-
-proc clear_display {} {
-    global iddrawn linesegs need_redisplay nrows_drawn
-    global vhighlights fhighlights nhighlights rhighlights
-
-    allcanvs delete all
-    catch {unset iddrawn}
-    catch {unset linesegs}
-    catch {unset vhighlights}
-    catch {unset fhighlights}
-    catch {unset nhighlights}
-    catch {unset rhighlights}
-    set need_redisplay 0
-    set nrows_drawn 0
-}
-
-proc findcrossings {id} {
-    global rowidlist parentlist numcommits displayorder
-
-    set cross {}
-    set ccross {}
-    foreach {s e} [rowranges $id] {
-       if {$e >= $numcommits} {
-           set e [expr {$numcommits - 1}]
-       }
-       if {$e <= $s} 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]
-           if {$kidx < 0} continue
-           set nextrow [lindex $rowidlist [expr {$row + 1}]]
-           foreach p $olds {
-               set px [lsearch -exact $nextrow $p]
-               if {$px < 0} continue
-               if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
-                   if {[lsearch -exact $ccross $p] >= 0} continue
-                   if {$x == $px + ($kidx < $px? -1: 1)} {
-                       lappend ccross $p
-                   } elseif {[lsearch -exact $cross $p] < 0} {
-                       lappend cross $p
-                   }
-               }
-           }
-       }
-    }
-    return [concat $ccross {{}} $cross]
-}
-
-proc assigncolor {id} {
-    global colormap colors nextcolor
-    global commitrow parentlist children children curview
-
-    if {[info exists colormap($id)]} return
-    set ncolors [llength $colors]
-    if {[info exists children($curview,$id)]} {
-       set kids $children($curview,$id)
-    } else {
-       set kids {}
-    }
-    if {[llength $kids] == 1} {
-       set child [lindex $kids 0]
-       if {[info exists colormap($child)]
-           && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
-           set colormap($id) $colormap($child)
-           return
-       }
-    }
-    set badcolors {}
-    set origbad {}
-    foreach x [findcrossings $id] {
-       if {$x eq {}} {
-           # delimiter between corner crossings and other crossings
-           if {[llength $badcolors] >= $ncolors - 1} break
-           set origbad $badcolors
-       }
-       if {[info exists colormap($x)]
-           && [lsearch -exact $badcolors $colormap($x)] < 0} {
-           lappend badcolors $colormap($x)
-       }
-    }
-    if {[llength $badcolors] >= $ncolors} {
-       set badcolors $origbad
-    }
-    set origbad $badcolors
-    if {[llength $badcolors] < $ncolors - 1} {
-       foreach child $kids {
-           if {[info exists colormap($child)]
-               && [lsearch -exact $badcolors $colormap($child)] < 0} {
-               lappend badcolors $colormap($child)
-           }
-           foreach p [lindex $parentlist $commitrow($curview,$child)] {
-               if {[info exists colormap($p)]
-                   && [lsearch -exact $badcolors $colormap($p)] < 0} {
-                   lappend badcolors $colormap($p)
-               }
-           }
-       }
-       if {[llength $badcolors] >= $ncolors} {
-           set badcolors $origbad
-       }
-    }
-    for {set i 0} {$i <= $ncolors} {incr i} {
-       set c [lindex $colors $nextcolor]
-       if {[incr nextcolor] >= $ncolors} {
-           set nextcolor 0
-       }
-       if {[lsearch -exact $badcolors $c]} break
-    }
-    set colormap($id) $c
-}
-
-proc bindline {t id} {
-    global canv
-
-    $canv bind $t <Enter> "lineenter %x %y $id"
-    $canv bind $t <Motion> "linemotion %x %y $id"
-    $canv bind $t <Leave> "lineleave $id"
-    $canv bind $t <Button-1> "lineclick %x %y $id 1"
-}
-
-proc drawtags {id x xt y1} {
-    global idtags idheads idotherrefs mainhead
-    global linespc lthickness
-    global canv commitrow rowtextx curview fgcolor bgcolor
-
-    set marks {}
-    set ntags 0
-    set nheads 0
-    if {[info exists idtags($id)]} {
-       set marks $idtags($id)
-       set ntags [llength $marks]
-    }
-    if {[info exists idheads($id)]} {
-       set marks [concat $marks $idheads($id)]
-       set nheads [llength $idheads($id)]
-    }
-    if {[info exists idotherrefs($id)]} {
-       set marks [concat $marks $idotherrefs($id)]
-    }
-    if {$marks eq {}} {
-       return $xt
-    }
-
-    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
-    set yt [expr {$y1 - 0.5 * $linespc}]
-    set yb [expr {$yt + $linespc - 1}]
-    set xvals {}
-    set wvals {}
-    set i -1
-    foreach tag $marks {
-       incr i
-       if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
-           set wid [font measure mainfontbold $tag]
-       } else {
-           set wid [font measure mainfont $tag]
-       }
-       lappend xvals $xt
-       lappend wvals $wid
-       set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
-    }
-    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
-              -width $lthickness -fill black -tags tag.$id]
-    $canv lower $t
-    foreach tag $marks x $xvals wid $wvals {
-       set xl [expr {$x + $delta}]
-       set xr [expr {$x + $delta + $wid + $lthickness}]
-       set font mainfont
-       if {[incr ntags -1] >= 0} {
-           # draw a tag
-           set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
-                      $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
-                      -width 1 -outline black -fill yellow -tags tag.$id]
-           $canv bind $t <1> [list showtag $tag 1]
-           set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
-       } else {
-           # draw a head or other ref
-           if {[incr nheads -1] >= 0} {
-               set col green
-               if {$tag eq $mainhead} {
-                   set font mainfontbold
-               }
-           } else {
-               set col "#ddddff"
-           }
-           set xl [expr {$xl - $delta/2}]
-           $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 xi [expr {$x + 1}]
-               set yti [expr {$yt + 1}]
-               set xri [expr {$x + $rwid}]
-               $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
-                       -width 0 -fill "#ffddaa" -tags tag.$id
-           }
-       }
-       set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
-                  -font $font -tags [list tag.$id text]]
-       if {$ntags >= 0} {
-           $canv bind $t <1> [list showtag $tag 1]
-       } elseif {$nheads >= 0} {
-           $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
-       }
-    }
-    return $xt
-}
-
-proc xcoord {i level ln} {
-    global canvx0 xspc1 xspc2
-
-    set x [expr {$canvx0 + $i * $xspc1($ln)}]
-    if {$i > 0 && $i == $level} {
-       set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
-    } elseif {$i > $level} {
-       set x [expr {$x + $xspc2 - $xspc1($ln)}]
-    }
-    return $x
-}
-
-proc show_status {msg} {
-    global canv fgcolor
-
-    clear_display
-    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
-       -tags text -fill $fgcolor
-}
-
-# Insert a new commit as the child of the commit on row $row.
-# The new commit will be displayed on row $row and the commits
-# on that row and below will move down one row.
-proc insertrow {row newcmit} {
-    global displayorder parentlist commitlisted children
-    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"
-       return
-    }
-    set p [lindex $displayorder $row]
-    set displayorder [linsert $displayorder $row $newcmit]
-    set parentlist [linsert $parentlist $row $p]
-    set kids $children($curview,$p)
-    lappend kids $newcmit
-    set children($curview,$p) $kids
-    set children($curview,$newcmit) {}
-    set commitlisted [linsert $commitlisted $row 1]
-    set l [llength $displayorder]
-    for {set r $row} {$r < $l} {incr r} {
-       set id [lindex $displayorder $r]
-       set commitrow($curview,$id) $r
-    }
-    incr commitidx($curview)
-    set ordertok($curview,$newcmit) $ordertok($curview,$p)
-
-    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
-           }
-       }
-       set rowidlist [linsert $rowidlist $row $idlist]
-       set rowisopt [linsert $rowisopt $row 0]
-       set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]]
-    }
-
-    incr numcommits
-
-    if {[info exists selectedline] && $selectedline >= $row} {
-       incr selectedline
-    }
-    redisplay
-}
-
-# Remove a commit that was inserted with insertrow on row $row.
-proc removerow {row} {
-    global displayorder parentlist commitlisted children
-    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"
-       return
-    }
-    set rp1 [expr {$row + 1}]
-    set id [lindex $displayorder $row]
-    set p [lindex $parentlist $row]
-    set displayorder [lreplace $displayorder $row $row]
-    set parentlist [lreplace $parentlist $row $row]
-    set commitlisted [lreplace $commitlisted $row $row]
-    set kids $children($curview,$p)
-    set i [lsearch -exact $kids $id]
-    if {$i >= 0} {
-       set kids [lreplace $kids $i $i]
-       set children($curview,$p) $kids
-    }
-    set l [llength $displayorder]
-    for {set r $row} {$r < $l} {incr r} {
-       set id [lindex $displayorder $r]
-       set commitrow($curview,$id) $r
-    }
-    incr commitidx($curview) -1
-
-    if {$row < [llength $rowidlist]} {
-       set rowidlist [lreplace $rowidlist $row $row]
-       set rowisopt [lreplace $rowisopt $row $row]
-       set rowfinal [lreplace $rowfinal $row $row]
-    }
-
-    incr numcommits -1
-
-    if {[info exists selectedline] && $selectedline > $row} {
-       incr selectedline -1
-    }
-    redisplay
-}
-
-# Don't change the text pane cursor if it is currently the hand cursor,
-# showing that we are over a sha1 ID link.
-proc settextcursor {c} {
-    global ctext curtextcursor
-
-    if {[$ctext cget -cursor] == $curtextcursor} {
-       $ctext config -cursor $c
-    }
-    set curtextcursor $c
-}
-
-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 busyname statusw
-
-    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
-    }
-}
-
-proc findmatches {f} {
-    global findtype findstring
-    if {$findtype == "Regexp"} {
-       set matches [regexp -indices -all -inline $findstring $f]
-    } else {
-       set fs $findstring
-       if {$findtype == "IgnCase"} {
-           set f [string tolower $f]
-           set fs [string tolower $fs]
-       }
-       set matches {}
-       set i 0
-       set l [string length $fs]
-       while {[set j [string first $fs $f $i]] >= 0} {
-           lappend matches [list $j [expr {$j+$l-1}]]
-           set i [expr {$j + $l}]
-       }
-    }
-    return $matches
-}
-
-proc dofind {{dirn 1} {wrap 1}} {
-    global findstring findstartline findcurline selectedline numcommits
-    global gdttype filehighlight fh_serial find_dirn findallowwrap
-
-    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] [expr {$dirn < 0}]]
-    } else {
-       set findstartline $selectedline
-    }
-    set findcurline $findstartline
-    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 stopfinding {} {
-    global find_dirn findcurline fprogcoord
-
-    if {[info exists find_dirn]} {
-       unset find_dirn
-       unset findcurline
-       notbusy finding
-       set fprogcoord 0
-       adjustprogress
-    }
-}
-
-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
-    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 {
-       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
-       }
-    } 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 {$found || ($domore && !$moretodo)} {
-       unset findcurline
-       unset find_dirn
-       notbusy finding
-       set fprogcoord 0
-       adjustprogress
-       if {$found} {
-           findselectline $l
-       } else {
-           bell
-       }
-       return 0
-    }
-    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 gdttype
-
-    set markingmatches 1
-    set findcurline $l
-    selectline $l 1
-    if {$findloc == "All fields" || $findloc == "Comments"} {
-       # highlight the matches in the comments
-       set f [$ctext get 1.0 $commentend]
-       set matches [findmatches $f]
-       foreach match $matches {
-           set start [lindex $match 0]
-           set end [expr {[lindex $match 1] + 1}]
-           $ctext tag add found "1.0 + $start c" "1.0 + $end c"
-       }
-    }
-    drawvisible
-}
-
-# mark the bits of a headline or author that match a find string
-proc markmatches {canv l str tag matches font row} {
-    global selectedline
-
-    set bbox [$canv bbox $tag]
-    set x0 [lindex $bbox 0]
-    set y0 [lindex $bbox 1]
-    set y1 [lindex $bbox 3]
-    foreach match $matches {
-       set start [lindex $match 0]
-       set end [lindex $match 1]
-       if {$start > $end} continue
-       set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
-       set xlen [font measure $font [string range $str 0 [expr {$end}]]]
-       set t [$canv create rect [expr {$x0+$xoff}] $y0 \
-                  [expr {$x0+$xlen+2}] $y1 \
-                  -outline {} -tags [list match$l matches] -fill yellow]
-       $canv lower $t
-       if {[info exists selectedline] && $row == $selectedline} {
-           $canv raise $t secsel
-       }
-    }
-}
-
-proc unmarkmatches {} {
-    global markingmatches
-
-    allcanvs delete matches
-    set markingmatches 0
-    stopfinding
-}
-
-proc selcanvline {w x y} {
-    global canv canvy0 ctext linespc
-    global rowtextx
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax == {}} return
-    set yfrac [lindex [$canv yview] 0]
-    set y [expr {$y + $yfrac * $ymax}]
-    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
-    if {$l < 0} {
-       set l 0
-    }
-    if {$w eq $canv} {
-       if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
-    }
-    unmarkmatches
-    selectline $l 1
-}
-
-proc commit_descriptor {p} {
-    global commitinfo
-    if {![info exists commitinfo($p)]} {
-       getcommit $p
-    }
-    set l "..."
-    if {[llength $commitinfo($p)] > 1} {
-       set l [lindex $commitinfo($p) 0]
-    }
-    return "$p ($l)\n"
-}
-
-# 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 pendinglinks
-
-    set start [$ctext index "end - 1c"]
-    $ctext insert end $text $tags
-    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
-    foreach l $links {
-       set s [lindex $l 0]
-       set e [lindex $l 1]
-       set linkid [string range $text $s $e]
-       incr e
-       $ctext tag delete link$linknum
-       $ctext tag add link$linknum "$start + $s c" "$start + $e c"
-       setlink $linkid link$linknum
-       incr linknum
-    }
-}
-
-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} {
-    global canv linespc
-
-    $canv delete hover
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    set wnow [$canv yview]
-    set wtop [expr {[lindex $wnow 0] * $ymax}]
-    set newtop [expr {$wtop + $dir * $linespc}]
-    if {$newtop < 0} {
-       set newtop 0
-    } elseif {$newtop > $ymax} {
-       set newtop $ymax
-    }
-    allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
-}
-
-# add a list of tag or branch names at position pos
-# returns the number of names inserted
-proc appendrefs {pos ids var} {
-    global ctext commitrow linknum curview $var maxrefs
-
-    if {[catch {$ctext index $pos}]} {
-       return 0
-    }
-    $ctext conf -state normal
-    $ctext delete $pos "$pos lineend"
-    set tags {}
-    foreach id $ids {
-       foreach tag [set $var\($id\)] {
-           lappend tags [list $tag $id]
-       }
-    }
-    if {[llength $tags] > $maxrefs} {
-       $ctext insert $pos "many ([llength $tags])"
-    } else {
-       set tags [lsort -index 0 -decreasing $tags]
-       set sep {}
-       foreach ti $tags {
-           set id [lindex $ti 1]
-           set lk link$linknum
-           incr linknum
-           $ctext tag delete $lk
-           $ctext insert $pos $sep
-           $ctext insert $pos [lindex $ti 0] $lk
-           setlink $id $lk
-           set sep ", "
-       }
-    }
-    $ctext conf -state disabled
-    return [llength $tags]
-}
-
-# called when we have finished computing the nearby tags
-proc dispneartags {delay} {
-    global selectedline currentid showneartags tagphase
-
-    if {![info exists selectedline] || !$showneartags} return
-    after cancel dispnexttag
-    if {$delay} {
-       after 200 dispnexttag
-       set tagphase -1
-    } else {
-       after idle dispnexttag
-       set tagphase 0
-    }
-}
-
-proc dispnexttag {} {
-    global selectedline currentid showneartags tagphase ctext
-
-    if {![info exists selectedline] || !$showneartags} return
-    switch -- $tagphase {
-       0 {
-           set dtags [desctags $currentid]
-           if {$dtags ne {}} {
-               appendrefs precedes $dtags idtags
-           }
-       }
-       1 {
-           set atags [anctags $currentid]
-           if {$atags ne {}} {
-               appendrefs follows $atags idtags
-           }
-       }
-       2 {
-           set dheads [descheads $currentid]
-           if {$dheads ne {}} {
-               if {[appendrefs branch $dheads idheads] > 1
-                   && [$ctext get "branch -3c"] eq "h"} {
-                   # turn "Branch" into "Branches"
-                   $ctext conf -state normal
-                   $ctext insert "branch -2c" "es"
-                   $ctext conf -state disabled
-               }
-           }
-       }
-    }
-    if {[incr tagphase] <= 2} {
-       after idle 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 ctext commitinfo selectedline
-    global displayorder
-    global canvy0 linespc parentlist children curview
-    global currentid sha1entry
-    global commentend idtags linknum
-    global mergemax numcommits pending_select
-    global cmitmode showneartags allcommits
-
-    catch {unset pending_select}
-    $canv delete hover
-    normalline
-    unsel_reflist
-    stopfinding
-    if {$l < 0 || $l >= $numcommits} return
-    set y [expr {$canvy0 + $l * $linespc}]
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    set ytop [expr {$y - $linespc - 1}]
-    set ybot [expr {$y + $linespc + 1}]
-    set wnow [$canv yview]
-    set wtop [expr {[lindex $wnow 0] * $ymax}]
-    set wbot [expr {[lindex $wnow 1] * $ymax}]
-    set wh [expr {$wbot - $wtop}]
-    set newtop $wtop
-    if {$ytop < $wtop} {
-       if {$ybot < $wtop} {
-           set newtop [expr {$y - $wh / 2.0}]
-       } else {
-           set newtop $ytop
-           if {$newtop > $wtop - $linespc} {
-               set newtop [expr {$wtop - $linespc}]
-           }
-       }
-    } elseif {$ybot > $wbot} {
-       if {$ytop > $wbot} {
-           set newtop [expr {$y - $wh / 2.0}]
-       } else {
-           set newtop [expr {$ybot - $wh}]
-           if {$newtop < $wtop + $linespc} {
-               set newtop [expr {$wtop + $linespc}]
-           }
-       }
-    }
-    if {$newtop != $wtop} {
-       if {$newtop < 0} {
-           set newtop 0
-       }
-       allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
-       drawvisible
-    }
-
-    make_secsel $l
-
-    if {$isnew} {
-       addtohistory [list selectline $l 0]
-    }
-
-    set selectedline $l
-
-    set id [lindex $displayorder $l]
-    set currentid $id
-    $sha1entry delete 0 end
-    $sha1entry insert 0 $id
-    $sha1entry selection from 0
-    $sha1entry selection to end
-    rhighlight_sel $id
-
-    $ctext conf -state normal
-    clear_ctext
-    set linknum 0
-    set info $commitinfo($id)
-    set date [formatdate [lindex $info 2]]
-    $ctext insert end "Author: [lindex $info 1]  $date\n"
-    set date [formatdate [lindex $info 4]]
-    $ctext insert end "Committer: [lindex $info 3]  $date\n"
-    if {[info exists idtags($id)]} {
-       $ctext insert end "Tags:"
-       foreach tag $idtags($id) {
-           $ctext insert end " $tag"
-       }
-       $ctext insert end "\n"
-    }
-
-    set headers {}
-    set olds [lindex $parentlist $l]
-    if {[llength $olds] > 1} {
-       set np 0
-       foreach p $olds {
-           if {$np >= $mergemax} {
-               set tag mmax
-           } else {
-               set tag m$np
-           }
-           $ctext insert end "Parent: " $tag
-           appendwithlinks [commit_descriptor $p] {}
-           incr np
-       }
-    } else {
-       foreach p $olds {
-           append headers "Parent: [commit_descriptor $p]"
-       }
-    }
-
-    foreach c $children($curview,$id) {
-       append headers "Child:  [commit_descriptor $c]"
-    }
-
-    # make anything that looks like a SHA1 ID be a clickable link
-    appendwithlinks $headers {}
-    if {$showneartags} {
-       if {![info exists allcommits]} {
-           getallcommits
-       }
-       $ctext insert end "Branch: "
-       $ctext mark set branch "end -1c"
-       $ctext mark gravity branch left
-       $ctext insert end "\nFollows: "
-       $ctext mark set follows "end -1c"
-       $ctext mark gravity follows left
-       $ctext insert end "\nPrecedes: "
-       $ctext mark set precedes "end -1c"
-       $ctext mark gravity precedes left
-       $ctext insert end "\n"
-       dispneartags 1
-    }
-    $ctext insert end "\n"
-    set comment [lindex $info 5]
-    if {[string first "\r" $comment] >= 0} {
-       set comment [string map {"\r" "\n    "} $comment]
-    }
-    appendwithlinks $comment {comment}
-
-    $ctext tag remove found 1.0 end
-    $ctext conf -state disabled
-    set commentend [$ctext index "end - 1c"]
-
-    init_flist "Comments"
-    if {$cmitmode eq "tree"} {
-       gettree $id
-    } elseif {[llength $olds] <= 1} {
-       startdiff $id
-    } else {
-       mergediff $id $l
-    }
-}
-
-proc selfirstline {} {
-    unmarkmatches
-    selectline 0 1
-}
-
-proc sellastline {} {
-    global numcommits
-    unmarkmatches
-    set l [expr {$numcommits - 1}]
-    selectline $l 1
-}
-
-proc selnextline {dir} {
-    global selectedline
-    focus .
-    if {![info exists selectedline]} return
-    set l [expr {$selectedline + $dir}]
-    unmarkmatches
-    selectline $l 1
-}
-
-proc selnextpage {dir} {
-    global canv linespc selectedline numcommits
-
-    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
-    if {$lpp < 1} {
-       set lpp 1
-    }
-    allcanvs yview scroll [expr {$dir * $lpp}] units
-    drawvisible
-    if {![info exists selectedline]} return
-    set l [expr {$selectedline + $dir * $lpp}]
-    if {$l < 0} {
-       set l 0
-    } elseif {$l >= $numcommits} {
-        set l [expr $numcommits - 1]
-    }
-    unmarkmatches
-    selectline $l 1
-}
-
-proc unselectline {} {
-    global selectedline currentid
-
-    catch {unset selectedline}
-    catch {unset currentid}
-    allcanvs delete secsel
-    rhighlight_none
-}
-
-proc reselectline {} {
-    global selectedline
-
-    if {[info exists selectedline]} {
-       selectline $selectedline 0
-    }
-}
-
-proc addtohistory {cmd} {
-    global history historyindex curview
-
-    set elt [list $curview $cmd]
-    if {$historyindex > 0
-       && [lindex $history [expr {$historyindex - 1}]] == $elt} {
-       return
-    }
-
-    if {$historyindex < [llength $history]} {
-       set history [lreplace $history $historyindex end $elt]
-    } else {
-       lappend history $elt
-    }
-    incr historyindex
-    if {$historyindex > 1} {
-       .tf.bar.leftbut conf -state normal
-    } else {
-       .tf.bar.leftbut conf -state disabled
-    }
-    .tf.bar.rightbut conf -state disabled
-}
-
-proc godo {elt} {
-    global curview
-
-    set view [lindex $elt 0]
-    set cmd [lindex $elt 1]
-    if {$curview != $view} {
-       showview $view
-    }
-    eval $cmd
-}
-
-proc goback {} {
-    global history historyindex
-    focus .
-
-    if {$historyindex > 1} {
-       incr historyindex -1
-       godo [lindex $history [expr {$historyindex - 1}]]
-       .tf.bar.rightbut conf -state normal
-    }
-    if {$historyindex <= 1} {
-       .tf.bar.leftbut conf -state disabled
-    }
-}
-
-proc goforw {} {
-    global history historyindex
-    focus .
-
-    if {$historyindex < [llength $history]} {
-       set cmd [lindex $history $historyindex]
-       incr historyindex
-       godo $cmd
-       .tf.bar.leftbut conf -state normal
-    }
-    if {$historyindex >= [llength $history]} {
-       .tf.bar.rightbut conf -state disabled
-    }
-}
-
-proc gettree {id} {
-    global treefilelist treeidlist diffids diffmergeid treepending
-    global nullid nullid2
-
-    set diffids $id
-    catch {unset diffmergeid}
-    if {![info exists treefilelist($id)]} {
-       if {![info exists treepending]} {
-           if {$id eq $nullid} {
-               set cmd [list | git ls-files]
-           } elseif {$id eq $nullid2} {
-               set cmd [list | git ls-files --stage -t]
-           } else {
-               set cmd [list | git ls-tree -r $id]
-           }
-           if {[catch {set gtf [open $cmd r]}]} {
-               return
-           }
-           set treepending $id
-           set treefilelist($id) {}
-           set treeidlist($id) {}
-           fconfigure $gtf -blocking 0
-           filerun $gtf [list gettreeline $gtf $id]
-       }
-    } else {
-       setfilelist $id
-    }
-}
-
-proc gettreeline {gtf id} {
-    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
-
-    set nl 0
-    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
-       if {$diffids eq $nullid} {
-           set fname $line
-       } else {
-           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
-           set i [string first "\t" $line]
-           if {$i < 0} continue
-           set sha1 [lindex $line 2]
-           set fname [string range $line [expr {$i+1}] end]
-           if {[string index $fname 0] eq "\""} {
-               set fname [lindex $fname 0]
-           }
-           lappend treeidlist($id) $sha1
-       }
-       lappend treefilelist($id) $fname
-    }
-    if {![eof $gtf]} {
-       return [expr {$nl >= 1000? 2: 1}]
-    }
-    close $gtf
-    unset treepending
-    if {$cmitmode ne "tree"} {
-       if {![info exists diffmergeid]} {
-           gettreediffs $diffids
-       }
-    } elseif {$id ne $diffids} {
-       gettree $diffids
-    } else {
-       setfilelist $id
-    }
-    return 0
-}
-
-proc showfile {f} {
-    global treefilelist treeidlist diffids nullid nullid2
-    global ctext commentend
-
-    set i [lsearch -exact $treefilelist($diffids) $f]
-    if {$i < 0} {
-       puts "oops, $f not in list for id $diffids"
-       return
-    }
-    if {$diffids eq $nullid} {
-       if {[catch {set bf [open $f r]} err]} {
-           puts "oops, can't read $f: $err"
-           return
-       }
-    } else {
-       set blob [lindex $treeidlist($diffids) $i]
-       if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
-           puts "oops, error reading blob $blob: $err"
-           return
-       }
-    }
-    fconfigure $bf -blocking 0
-    filerun $bf [list getblobline $bf $diffids]
-    $ctext config -state normal
-    clear_ctext $commentend
-    $ctext insert end "\n"
-    $ctext insert end "$f\n" filesep
-    $ctext config -state disabled
-    $ctext yview $commentend
-    settabs 0
-}
-
-proc getblobline {bf id} {
-    global diffids cmitmode ctext
-
-    if {$id ne $diffids || $cmitmode ne "tree"} {
-       catch {close $bf}
-       return 0
-    }
-    $ctext config -state normal
-    set nl 0
-    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
-       $ctext insert end "$line\n"
-    }
-    if {[eof $bf]} {
-       # delete last newline
-       $ctext delete "end - 2c" "end - 1c"
-       close $bf
-       return 0
-    }
-    $ctext config -state disabled
-    return [expr {$nl >= 1000? 2: 1}]
-}
-
-proc mergediff {id l} {
-    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 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
-    }
-    fconfigure $mdf -blocking 0
-    set mdifffd($id) $mdf
-    set np [llength [lindex $parentlist $l]]
-    settabs $np
-    filerun $mdf [list getmergediffline $mdf $id $np]
-}
-
-proc getmergediffline {mdf id np} {
-    global diffmergeid ctext cflist mergemax
-    global difffilestart mdifffd
-
-    $ctext conf -state normal
-    set nr 0
-    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
-       if {![info exists diffmergeid] || $id != $diffmergeid
-           || $mdf != $mdifffd($id)} {
-           close $mdf
-           return 0
-       }
-       if {[regexp {^diff --cc (.*)} $line match fname]} {
-           # start of a new file
-           $ctext insert end "\n"
-           set here [$ctext index "end - 1c"]
-           lappend difffilestart $here
-           add_flist [list $fname]
-           set l [expr {(78 - [string length $fname]) / 2}]
-           set pad [string range "----------------------------------------" 1 $l]
-           $ctext insert end "$pad $fname $pad\n" filesep
-       } elseif {[regexp {^@@} $line]} {
-           $ctext insert end "$line\n" hunksep
-       } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
-           # do nothing
-       } else {
-           # parse the prefix - one ' ', '-' or '+' for each parent
-           set spaces {}
-           set minuses {}
-           set pluses {}
-           set isbad 0
-           for {set j 0} {$j < $np} {incr j} {
-               set c [string range $line $j $j]
-               if {$c == " "} {
-                   lappend spaces $j
-               } elseif {$c == "-"} {
-                   lappend minuses $j
-               } elseif {$c == "+"} {
-                   lappend pluses $j
-               } else {
-                   set isbad 1
-                   break
-               }
-           }
-           set tags {}
-           set num {}
-           if {!$isbad && $minuses ne {} && $pluses eq {}} {
-               # line doesn't appear in result, parents in $minuses have the line
-               set num [lindex $minuses 0]
-           } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
-               # line appears in result, parents in $pluses don't have the line
-               lappend tags mresult
-               set num [lindex $spaces 0]
-           }
-           if {$num ne {}} {
-               if {$num >= $mergemax} {
-                   set num "max"
-               }
-               lappend tags m$num
-           }
-           $ctext insert end "$line\n" $tags
-       }
-    }
-    $ctext conf -state disabled
-    if {[eof $mdf]} {
-       close $mdf
-       return 0
-    }
-    return [expr {$nr >= 1000? 2: 1}]
-}
-
-proc startdiff {ids} {
-    global treediffs diffids treepending diffmergeid nullid nullid2
-
-    settabs 1
-    set diffids $ids
-    catch {unset diffmergeid}
-    if {![info exists treediffs($ids)] ||
-       [lsearch -exact $ids $nullid] >= 0 ||
-       [lsearch -exact $ids $nullid2] >= 0} {
-       if {![info exists treepending]} {
-           gettreediffs $ids
-       }
-    } else {
-       addtocflist $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
-
-    add_flist $treediffs($ids)
-    getblobdiffs $ids
-}
-
-proc diffcmd {ids flags} {
-    global nullid nullid2
-
-    set i [lsearch -exact $ids $nullid]
-    set j [lsearch -exact $ids $nullid2]
-    if {$i >= 0} {
-       if {[llength $ids] > 1 && $j < 0} {
-           # comparing working directory with some specific revision
-           set cmd [concat | git diff-index $flags]
-           if {$i == 0} {
-               lappend cmd -R [lindex $ids 1]
-           } else {
-               lappend cmd [lindex $ids 0]
-           }
-       } else {
-           # comparing working directory with index
-           set cmd [concat | git diff-files $flags]
-           if {$j == 1} {
-               lappend cmd -R
-           }
-       }
-    } elseif {$j >= 0} {
-       set cmd [concat | git diff-index --cached $flags]
-       if {[llength $ids] > 1} {
-           # comparing index with specific revision
-           if {$i == 0} {
-               lappend cmd -R [lindex $ids 1]
-           } else {
-               lappend cmd [lindex $ids 0]
-           }
-       } else {
-           # comparing index with HEAD
-           lappend cmd HEAD
-       }
-    } else {
-       set cmd [concat | git diff-tree -r $flags $ids]
-    }
-    return $cmd
-}
-
-proc gettreediffs {ids} {
-    global treediff treepending
-
-    set treepending $ids
-    set treediff {}
-    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
-    fconfigure $gdtf -blocking 0
-    filerun $gdtf [list gettreediffline $gdtf $ids]
-}
-
-proc gettreediffline {gdtf ids} {
-    global treediff treediffs treepending diffids diffmergeid
-    global cmitmode viewfiles curview limitdiffs
-
-    set nr 0
-    while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
-       set i [string first "\t" $line]
-       if {$i >= 0} {
-           set file [string range $line [expr {$i+1}] end]
-           if {[string index $file 0] eq "\""} {
-               set file [lindex $file 0]
-           }
-           lappend treediff $file
-       }
-    }
-    if {![eof $gdtf]} {
-       return [expr {$nr >= 1000? 2: 1}]
-    }
-    close $gdtf
-    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
-    } elseif {$ids != $diffids} {
-       if {![info exists diffmergeid]} {
-           gettreediffs $diffids
-       }
-    } else {
-       addtocflist $ids
-    }
-    return 0
-}
-
-# empty string or positive integer
-proc diffcontextvalidate {v} {
-    return [regexp {^(|[1-9][0-9]*)$} $v]
-}
-
-proc diffcontextchange {n1 n2 op} {
-    global diffcontextstring diffcontext
-
-    if {[string is integer -strict $diffcontextstring]} {
-       if {$diffcontextstring > 0} {
-           set diffcontext $diffcontextstring
-           reselectline
-       }
-    }
-}
-
-proc getblobdiffs {ids} {
-    global blobdifffd diffids env
-    global diffinhdr treediffs
-    global diffcontext
-    global limitdiffs viewfiles curview
-
-    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
-    }
-    set diffinhdr 0
-    fconfigure $bdf -blocking 0
-    set blobdifffd($ids) $bdf
-    filerun $bdf [list getblobdiffline $bdf $diffids]
-}
-
-proc setinlist {var i val} {
-    global $var
-
-    while {[llength [set $var]] < $i} {
-       lappend $var {}
-    }
-    if {[llength [set $var]] == $i} {
-       lappend $var $val
-    } else {
-       lset $var $i $val
-    }
-}
-
-proc makediffhdr {fname ids} {
-    global ctext curdiffstart treediffs
-
-    set i [lsearch -exact $treediffs($ids) $fname]
-    if {$i >= 0} {
-       setinlist difffilestart $i $curdiffstart
-    }
-    set l [expr {(78 - [string length $fname]) / 2}]
-    set pad [string range "----------------------------------------" 1 $l]
-    $ctext insert $curdiffstart "$pad $fname $pad" filesep
-}
-
-proc getblobdiffline {bdf ids} {
-    global diffids blobdifffd ctext curdiffstart
-    global diffnexthead diffnextnote difffilestart
-    global diffinhdr treediffs
-
-    set nr 0
-    $ctext conf -state normal
-    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
-       if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
-           close $bdf
-           return 0
-       }
-       if {![string compare -length 11 "diff --git " $line]} {
-           # trim off "diff --git "
-           set line [string range $line 11 end]
-           set diffinhdr 1
-           # start of a new file
-           $ctext insert end "\n"
-           set curdiffstart [$ctext index "end - 1c"]
-           $ctext insert end "\n" filesep
-           # If the name hasn't changed the length will be odd,
-           # the middle char will be a space, and the two bits either
-           # side will be a/name and b/name, or "a/name" and "b/name".
-           # If the name has changed we'll get "rename from" and
-           # "rename to" or "copy from" and "copy to" lines following this,
-           # and we'll use them to get the filenames.
-           # This complexity is necessary because spaces in the filename(s)
-           # don't get escaped.
-           set l [string length $line]
-           set i [expr {$l / 2}]
-           if {!(($l & 1) && [string index $line $i] eq " " &&
-                 [string range $line 2 [expr {$i - 1}]] eq \
-                     [string range $line [expr {$i + 3}] end])} {
-               continue
-           }
-           # unescape if quoted and chop off the a/ from the front
-           if {[string index $line 0] eq "\""} {
-               set fname [string range [lindex $line 0] 2 end]
-           } else {
-               set fname [string range $line 2 [expr {$i - 1}]]
-           }
-           makediffhdr $fname $ids
-
-       } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
-                      $line match f1l f1c f2l f2c rest]} {
-           $ctext insert end "$line\n" hunksep
-           set diffinhdr 0
-
-       } elseif {$diffinhdr} {
-           if {![string compare -length 12 "rename from " $line]} {
-               set fname [string range $line [expr 6 + [string first " from " $line] ] end]
-               if {[string index $fname 0] eq "\""} {
-                   set fname [lindex $fname 0]
-               }
-               set i [lsearch -exact $treediffs($ids) $fname]
-               if {$i >= 0} {
-                   setinlist difffilestart $i $curdiffstart
-               }
-           } elseif {![string compare -length 10 $line "rename to "] ||
-                     ![string compare -length 8 $line "copy to "]} {
-               set fname [string range $line [expr 4 + [string first " to " $line] ] end]
-               if {[string index $fname 0] eq "\""} {
-                   set fname [lindex $fname 0]
-               }
-               makediffhdr $fname $ids
-           } elseif {[string compare -length 3 $line "---"] == 0} {
-               # do nothing
-               continue
-           } elseif {[string compare -length 3 $line "+++"] == 0} {
-               set diffinhdr 0
-               continue
-           }
-           $ctext insert end "$line\n" filesep
-
-       } else {
-           set x [string range $line 0 0]
-           if {$x == "-" || $x == "+"} {
-               set tag [expr {$x == "+"}]
-               $ctext insert end "$line\n" d$tag
-           } elseif {$x == " "} {
-               $ctext insert end "$line\n"
-           } else {
-               # "\ No newline at end of file",
-               # or something else we don't recognize
-               $ctext insert end "$line\n" hunksep
-           }
-       }
-    }
-    $ctext conf -state disabled
-    if {[eof $bdf]} {
-       close $bdf
-       return 0
-    }
-    return [expr {$nr >= 1000? 2: 1}]
-}
-
-proc changediffdisp {} {
-    global ctext diffelide
-
-    $ctext tag conf d0 -elide [lindex $diffelide 0]
-    $ctext tag conf d1 -elide [lindex $diffelide 1]
-}
-
-proc prevfile {} {
-    global difffilestart ctext
-    set prev [lindex $difffilestart 0]
-    set here [$ctext index @0,0]
-    foreach loc $difffilestart {
-       if {[$ctext compare $loc >= $here]} {
-           $ctext yview $prev
-           return
-       }
-       set prev $loc
-    }
-    $ctext yview $prev
-}
-
-proc nextfile {} {
-    global difffilestart ctext
-    set here [$ctext index @0,0]
-    foreach loc $difffilestart {
-       if {[$ctext compare $loc > $here]} {
-           $ctext yview $loc
-           return
-       }
-    }
-}
-
-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]} {
-       set smarktop $l
-    }
-    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.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} {
-    global ctext searchstring searchdirn
-
-    $ctext tag remove found 1.0 end
-    if {[catch {$ctext index anchor}]} {
-       # no anchor set, use start of selection, or of visible area
-       set sel [$ctext tag ranges sel]
-       if {$sel ne {}} {
-           $ctext mark set anchor [lindex $sel 0]
-       } elseif {$searchdirn eq "-forwards"} {
-           $ctext mark set anchor @0,0
-       } else {
-           $ctext mark set anchor @0,[winfo height $ctext]
-       }
-    }
-    if {$searchstring ne {}} {
-       set here [$ctext search $searchdirn -- $searchstring anchor]
-       if {$here ne {}} {
-           $ctext see $here
-       }
-       searchmarkvisible 1
-    }
-}
-
-proc dosearch {} {
-    global sstring ctext searchstring searchdirn
-
-    focus $sstring
-    $sstring icursor end
-    set searchdirn -forwards
-    if {$searchstring ne {}} {
-       set sel [$ctext tag ranges sel]
-       if {$sel ne {}} {
-           set start "[lindex $sel 0] + 1c"
-       } elseif {[catch {set start [$ctext index anchor]}]} {
-           set start "@0,0"
-       }
-       set match [$ctext search -count mlen -- $searchstring $start]
-       $ctext tag remove sel 1.0 end
-       if {$match eq {}} {
-           bell
-           return
-       }
-       $ctext see $match
-       set mend "$match + $mlen c"
-       $ctext tag add sel $match $mend
-       $ctext mark unset anchor
-    }
-}
-
-proc dosearchback {} {
-    global sstring ctext searchstring searchdirn
-
-    focus $sstring
-    $sstring icursor end
-    set searchdirn -backwards
-    if {$searchstring ne {}} {
-       set sel [$ctext tag ranges sel]
-       if {$sel ne {}} {
-           set start [lindex $sel 0]
-       } elseif {[catch {set start [$ctext index anchor]}]} {
-           set start @0,[winfo height $ctext]
-       }
-       set match [$ctext search -backwards -count ml -- $searchstring $start]
-       $ctext tag remove sel 1.0 end
-       if {$match eq {}} {
-           bell
-           return
-       }
-       $ctext see $match
-       set mend "$match + $ml c"
-       $ctext tag add sel $match $mend
-       $ctext mark unset anchor
-    }
-}
-
-proc searchmark {first last} {
-    global ctext searchstring
-
-    set mend $first.0
-    while {1} {
-       set match [$ctext search -count mlen -- $searchstring $mend $last.end]
-       if {$match eq {}} break
-       set mend "$match + $mlen c"
-       $ctext tag add found $match $mend
-    }
-}
-
-proc searchmarkvisible {doall} {
-    global ctext smarktop smarkbot
-
-    set topline [lindex [split [$ctext index @0,0] .] 0]
-    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
-    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
-       # no overlap with previous
-       searchmark $topline $botline
-       set smarktop $topline
-       set smarkbot $botline
-    } else {
-       if {$topline < $smarktop} {
-           searchmark $topline [expr {$smarktop-1}]
-           set smarktop $topline
-       }
-       if {$botline > $smarkbot} {
-           searchmark [expr {$smarkbot+1}] $botline
-           set smarkbot $botline
-       }
-    }
-}
-
-proc scrolltext {f0 f1} {
-    global searchstring
-
-    .bleft.sb set $f0 $f1
-    if {$searchstring ne {}} {
-       searchmarkvisible 0
-    }
-}
-
-proc setcoords {} {
-    global linespc charspc canvx0 canvy0
-    global xspc1 xspc2 lthickness
-
-    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}]
-    set xspc1(0) $linespc
-    set xspc2 $linespc
-}
-
-proc redisplay {} {
-    global canv
-    global selectedline
-
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax eq {} || $ymax == 0} return
-    set span [$canv yview]
-    clear_display
-    setcanvscroll
-    allcanvs yview moveto [lindex $span 0]
-    drawvisible
-    if {[info exists selectedline]} {
-       selectline $selectedline 0
-       allcanvs yview moveto [lindex $span 0]
-    }
-}
-
-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 stopped entries fontattr
-
-    unmarkmatches
-    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
-    settabs
-    redisplay
-}
-
-proc clearsha1 {} {
-    global sha1entry sha1string
-    if {[string length $sha1string] == 40} {
-       $sha1entry delete 0 end
-    }
-}
-
-proc sha1change {n1 n2 op} {
-    global sha1string currentid sha1but
-    if {$sha1string == {}
-       || ([info exists currentid] && $sha1string == $currentid)} {
-       set state disabled
-    } else {
-       set state normal
-    }
-    if {[$sha1but cget -state] == $state} return
-    if {$state == "normal"} {
-       $sha1but conf -state normal -relief raised -text "Goto: "
-    } else {
-       $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
-    }
-}
-
-proc gotocommit {} {
-    global sha1string currentid commitrow tagids headids
-    global displayorder numcommits curview
-
-    if {$sha1string == {}
-       || ([info exists currentid] && $sha1string == $currentid)} return
-    if {[info exists tagids($sha1string)]} {
-       set id $tagids($sha1string)
-    } elseif {[info exists headids($sha1string)]} {
-       set id $headids($sha1string)
-    } else {
-       set id [string tolower $sha1string]
-       if {[regexp {^[0-9a-f]{4,39}$} $id]} {
-           set matches {}
-           foreach i $displayorder {
-               if {[string match $id* $i]} {
-                   lappend matches $i
-               }
-           }
-           if {$matches ne {}} {
-               if {[llength $matches] > 1} {
-                   error_popup "Short SHA1 id $id is ambiguous"
-                   return
-               }
-               set id [lindex $matches 0]
-           }
-       }
-    }
-    if {[info exists commitrow($curview,$id)]} {
-       selectline $commitrow($curview,$id) 1
-       return
-    }
-    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
-       set type "SHA1 id"
-    } else {
-       set type "Tag/Head"
-    }
-    error_popup "$type $sha1string is not known"
-}
-
-proc lineenter {x y id} {
-    global hoverx hovery hoverid hovertimer
-    global commitinfo canv
-
-    if {![info exists commitinfo($id)] && ![getcommit $id]} return
-    set hoverx $x
-    set hovery $y
-    set hoverid $id
-    if {[info exists hovertimer]} {
-       after cancel $hovertimer
-    }
-    set hovertimer [after 500 linehover]
-    $canv delete hover
-}
-
-proc linemotion {x y id} {
-    global hoverx hovery hoverid hovertimer
-
-    if {[info exists hoverid] && $id == $hoverid} {
-       set hoverx $x
-       set hovery $y
-       if {[info exists hovertimer]} {
-           after cancel $hovertimer
-       }
-       set hovertimer [after 500 linehover]
-    }
-}
-
-proc lineleave {id} {
-    global hoverid hovertimer canv
-
-    if {[info exists hoverid] && $id == $hoverid} {
-       $canv delete hover
-       if {[info exists hovertimer]} {
-           after cancel $hovertimer
-           unset hovertimer
-       }
-       unset hoverid
-    }
-}
-
-proc linehover {} {
-    global hoverx hovery hoverid hovertimer
-    global canv linespc lthickness
-    global commitinfo
-
-    set text [lindex $commitinfo($hoverid) 0]
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax == {}} return
-    set yfrac [lindex [$canv yview] 0]
-    set x [expr {$hoverx + 2 * $linespc}]
-    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 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]
-    $canv raise $t
-}
-
-proc clickisonarrow {id y} {
-    global lthickness
-
-    set ranges [rowranges $id]
-    set thresh [expr {2 * $lthickness + 6}]
-    set n [expr {[llength $ranges] - 1}]
-    for {set i 1} {$i < $n} {incr i} {
-       set row [lindex $ranges $i]
-       if {abs([yc $row] - $y) < $thresh} {
-           return $i
-       }
-    }
-    return {}
-}
-
-proc arrowjump {id n y} {
-    global canv
-
-    # 1 <-> 2, 3 <-> 4, etc...
-    set n [expr {(($n - 1) ^ 1) + 1}]
-    set row [lindex [rowranges $id] $n]
-    set yt [yc $row]
-    set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax eq {} || $ymax <= 0} return
-    set view [$canv yview]
-    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
-    set yfrac [expr {$yt / $ymax - $yspan / 2}]
-    if {$yfrac < 0} {
-       set yfrac 0
-    }
-    allcanvs yview moveto $yfrac
-}
-
-proc lineclick {x y id isnew} {
-    global ctext commitinfo children canv thickerline curview commitrow
-
-    if {![info exists commitinfo($id)] && ![getcommit $id]} return
-    unmarkmatches
-    unselectline
-    normalline
-    $canv delete hover
-    # draw this line thicker than normal
-    set thickerline $id
-    drawlines $id
-    if {$isnew} {
-       set ymax [lindex [$canv cget -scrollregion] 3]
-       if {$ymax eq {}} return
-       set yfrac [lindex [$canv yview] 0]
-       set y [expr {$y + $yfrac * $ymax}]
-    }
-    set dirn [clickisonarrow $id $y]
-    if {$dirn ne {}} {
-       arrowjump $id $dirn $y
-       return
-    }
-
-    if {$isnew} {
-       addtohistory [list lineclick $x $y $id 0]
-    }
-    # fill the details pane with info about this line
-    $ctext conf -state normal
-    clear_ctext
-    settabs 0
-    $ctext insert end "Parent:\t"
-    $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"
-    set date [formatdate [lindex $info 2]]
-    $ctext insert end "\tDate:\t$date\n"
-    set kids $children($curview,$id)
-    if {$kids ne {}} {
-       $ctext insert end "\nChildren:"
-       set i 0
-       foreach child $kids {
-           incr i
-           if {![info exists commitinfo($child)] && ![getcommit $child]} continue
-           set info $commitinfo($child)
-           $ctext insert end "\n\t"
-           $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]]
-           $ctext insert end "\n\tDate:\t$date\n"
-       }
-    }
-    $ctext conf -state disabled
-    init_flist {}
-}
-
-proc normalline {} {
-    global thickerline
-    if {[info exists thickerline]} {
-       set id $thickerline
-       unset thickerline
-       drawlines $id
-    }
-}
-
-proc selbyid {id} {
-    global commitrow curview
-    if {[info exists commitrow($curview,$id)]} {
-       selectline $commitrow($curview,$id) 1
-    }
-}
-
-proc mstime {} {
-    global startmstime
-    if {![info exists startmstime]} {
-       set startmstime [clock clicks -milliseconds]
-    }
-    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
-}
-
-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} {
-       set state disabled
-    } else {
-       set state normal
-    }
-    if {$id ne $nullid && $id ne $nullid2} {
-       set menu $rowctxmenu
-       $menu entryconfigure 7 -label "Reset $mainhead branch to here"
-    } else {
-       set menu $fakerowmenu
-    }
-    $menu entryconfigure "Diff this*" -state $state
-    $menu entryconfigure "Diff selected*" -state $state
-    $menu entryconfigure "Make patch" -state $state
-    tk_popup $menu $x $y
-}
-
-proc diffvssel {dirn} {
-    global rowmenuid selectedline displayorder
-
-    if {![info exists selectedline]} return
-    if {$dirn} {
-       set oldid [lindex $displayorder $selectedline]
-       set newid $rowmenuid
-    } else {
-       set oldid $rowmenuid
-       set newid [lindex $displayorder $selectedline]
-    }
-    addtohistory [list doseldiff $oldid $newid]
-    doseldiff $oldid $newid
-}
-
-proc doseldiff {oldid newid} {
-    global ctext
-    global commitinfo
-
-    $ctext conf -state normal
-    clear_ctext
-    init_flist "Top"
-    $ctext insert end "From "
-    $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 insert end $newid link1
-    setlink $newid link1
-    $ctext insert end "\n     "
-    $ctext insert end [lindex $commitinfo($newid) 0]
-    $ctext insert end "\n"
-    $ctext conf -state disabled
-    $ctext tag remove found 1.0 end
-    startdiff [list $oldid $newid]
-}
-
-proc mkpatch {} {
-    global rowmenuid currentid commitinfo patchtop patchnum
-
-    if {![info exists currentid]} return
-    set oldid $currentid
-    set oldhead [lindex $commitinfo($oldid) 0]
-    set newid $rowmenuid
-    set newhead [lindex $commitinfo($newid) 0]
-    set top .patch
-    set patchtop $top
-    catch {destroy $top}
-    toplevel $top
-    label $top.title -text "Generate patch"
-    grid $top.title - -pady 10
-    label $top.from -text "From:"
-    entry $top.fromsha1 -width 40 -relief flat
-    $top.fromsha1 insert 0 $oldid
-    $top.fromsha1 conf -state readonly
-    grid $top.from $top.fromsha1 -sticky w
-    entry $top.fromhead -width 60 -relief flat
-    $top.fromhead insert 0 $oldhead
-    $top.fromhead conf -state readonly
-    grid x $top.fromhead -sticky w
-    label $top.to -text "To:"
-    entry $top.tosha1 -width 40 -relief flat
-    $top.tosha1 insert 0 $newid
-    $top.tosha1 conf -state readonly
-    grid $top.to $top.tosha1 -sticky w
-    entry $top.tohead -width 60 -relief flat
-    $top.tohead insert 0 $newhead
-    $top.tohead conf -state readonly
-    grid x $top.tohead -sticky w
-    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
-    grid $top.rev x -pady 10
-    label $top.flab -text "Output file:"
-    entry $top.fname -width 60
-    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
-    incr patchnum
-    grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text "Generate" -command mkpatchgo
-    button $top.buts.can -text "Cancel" -command mkpatchcan
-    grid $top.buts.gen $top.buts.can
-    grid columnconfigure $top.buts 0 -weight 1 -uniform a
-    grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - -pady 10 -sticky ew
-    focus $top.fname
-}
-
-proc mkpatchrev {} {
-    global patchtop
-
-    set oldid [$patchtop.fromsha1 get]
-    set oldhead [$patchtop.fromhead get]
-    set newid [$patchtop.tosha1 get]
-    set newhead [$patchtop.tohead get]
-    foreach e [list fromsha1 fromhead tosha1 tohead] \
-           v [list $newid $newhead $oldid $oldhead] {
-       $patchtop.$e conf -state normal
-       $patchtop.$e delete 0 end
-       $patchtop.$e insert 0 $v
-       $patchtop.$e conf -state readonly
-    }
-}
-
-proc mkpatchgo {} {
-    global patchtop nullid nullid2
-
-    set oldid [$patchtop.fromsha1 get]
-    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"
-    }
-    catch {destroy $patchtop}
-    unset patchtop
-}
-
-proc mkpatchcan {} {
-    global patchtop
-
-    catch {destroy $patchtop}
-    unset patchtop
-}
-
-proc mktag {} {
-    global rowmenuid mktagtop commitinfo
-
-    set top .maketag
-    set mktagtop $top
-    catch {destroy $top}
-    toplevel $top
-    label $top.title -text "Create tag"
-    grid $top.title - -pady 10
-    label $top.id -text "ID:"
-    entry $top.sha1 -width 40 -relief flat
-    $top.sha1 insert 0 $rowmenuid
-    $top.sha1 conf -state readonly
-    grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
-    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
-    $top.head conf -state readonly
-    grid x $top.head -sticky w
-    label $top.tlab -text "Tag name:"
-    entry $top.tag -width 60
-    grid $top.tlab $top.tag -sticky w
-    frame $top.buts
-    button $top.buts.gen -text "Create" -command mktaggo
-    button $top.buts.can -text "Cancel" -command mktagcan
-    grid $top.buts.gen $top.buts.can
-    grid columnconfigure $top.buts 0 -weight 1 -uniform a
-    grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - -pady 10 -sticky ew
-    focus $top.tag
-}
-
-proc domktag {} {
-    global mktagtop env tagids idtags
-
-    set id [$mktagtop.sha1 get]
-    set tag [$mktagtop.tag get]
-    if {$tag == {}} {
-       error_popup "No tag name specified"
-       return
-    }
-    if {[info exists tagids($tag)]} {
-       error_popup "Tag \"$tag\" already exists"
-       return
-    }
-    if {[catch {
-       set dir [gitdir]
-       set fname [file join $dir "refs/tags" $tag]
-       set f [open $fname w]
-       puts $f $id
-       close $f
-    } err]} {
-       error_popup "Error creating tag: $err"
-       return
-    }
-
-    set tagids($tag) $id
-    lappend idtags($id) $tag
-    redrawtags $id
-    addedtag $id
-    dispneartags 0
-    run refill_reflist
-}
-
-proc redrawtags {id} {
-    global canv linehtag commitrow idpos selectedline curview
-    global canvxmax iddrawn
-
-    if {![info exists commitrow($curview,$id)]} return
-    if {![info exists iddrawn($id)]} return
-    drawcommits $commitrow($curview,$id)
-    $canv delete tag.$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]}]
-    if {$xr > $canvxmax} {
-       set canvxmax $xr
-       setcanvscroll
-    }
-    if {[info exists selectedline]
-       && $selectedline == $commitrow($curview,$id)} {
-       selectline $selectedline 0
-    }
-}
-
-proc mktagcan {} {
-    global mktagtop
-
-    catch {destroy $mktagtop}
-    unset mktagtop
-}
-
-proc mktaggo {} {
-    domktag
-    mktagcan
-}
-
-proc writecommit {} {
-    global rowmenuid wrcomtop commitinfo wrcomcmd
-
-    set top .writecommit
-    set wrcomtop $top
-    catch {destroy $top}
-    toplevel $top
-    label $top.title -text "Write commit to file"
-    grid $top.title - -pady 10
-    label $top.id -text "ID:"
-    entry $top.sha1 -width 40 -relief flat
-    $top.sha1 insert 0 $rowmenuid
-    $top.sha1 conf -state readonly
-    grid $top.id $top.sha1 -sticky w
-    entry $top.head -width 60 -relief flat
-    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
-    $top.head conf -state readonly
-    grid x $top.head -sticky w
-    label $top.clab -text "Command:"
-    entry $top.cmd -width 60 -textvariable wrcomcmd
-    grid $top.clab $top.cmd -sticky w -pady 10
-    label $top.flab -text "Output file:"
-    entry $top.fname -width 60
-    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
-    grid $top.flab $top.fname -sticky w
-    frame $top.buts
-    button $top.buts.gen -text "Write" -command wrcomgo
-    button $top.buts.can -text "Cancel" -command wrcomcan
-    grid $top.buts.gen $top.buts.can
-    grid columnconfigure $top.buts 0 -weight 1 -uniform a
-    grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - -pady 10 -sticky ew
-    focus $top.fname
-}
-
-proc wrcomgo {} {
-    global wrcomtop
-
-    set id [$wrcomtop.sha1 get]
-    set cmd "echo $id | [$wrcomtop.cmd get]"
-    set fname [$wrcomtop.fname get]
-    if {[catch {exec sh -c $cmd >$fname &} err]} {
-       error_popup "Error writing commit: $err"
-    }
-    catch {destroy $wrcomtop}
-    unset wrcomtop
-}
-
-proc wrcomcan {} {
-    global wrcomtop
-
-    catch {destroy $wrcomtop}
-    unset wrcomtop
-}
-
-proc mkbranch {} {
-    global rowmenuid mkbrtop
-
-    set top .makebranch
-    catch {destroy $top}
-    toplevel $top
-    label $top.title -text "Create new branch"
-    grid $top.title - -pady 10
-    label $top.id -text "ID:"
-    entry $top.sha1 -width 40 -relief flat
-    $top.sha1 insert 0 $rowmenuid
-    $top.sha1 conf -state readonly
-    grid $top.id $top.sha1 -sticky w
-    label $top.nlab -text "Name:"
-    entry $top.name -width 40
-    grid $top.nlab $top.name -sticky w
-    frame $top.buts
-    button $top.buts.go -text "Create" -command [list mkbrgo $top]
-    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
-    grid $top.buts.go $top.buts.can
-    grid columnconfigure $top.buts 0 -weight 1 -uniform a
-    grid columnconfigure $top.buts 1 -weight 1 -uniform a
-    grid $top.buts - -pady 10 -sticky ew
-    focus $top.name
-}
-
-proc mkbrgo {top} {
-    global headids idheads
-
-    set name [$top.name get]
-    set id [$top.sha1 get]
-    if {$name eq {}} {
-       error_popup "Please specify a name for the new branch"
-       return
-    }
-    catch {destroy $top}
-    nowbusy newbranch
-    update
-    if {[catch {
-       exec git branch $name $id
-    } err]} {
-       notbusy newbranch
-       error_popup $err
-    } else {
-       set headids($name) $id
-       lappend idheads($id) $name
-       addedhead $id $name
-       notbusy newbranch
-       redrawtags $id
-       dispneartags 0
-       run refill_reflist
-    }
-}
-
-proc cherrypick {} {
-    global rowmenuid curview commitrow
-    global mainhead
-
-    set oldhead [exec git rev-parse HEAD]
-    set dheads [descheads $rowmenuid]
-    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
-       set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
-                       included in branch $mainhead -- really re-apply it?"]
-       if {!$ok} return
-    }
-    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...
-    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
-       notbusy cherrypick
-       error_popup $err
-       return
-    }
-    set newhead [exec git rev-parse HEAD]
-    if {$newhead eq $oldhead} {
-       notbusy cherrypick
-       error_popup "No changes committed"
-       return
-    }
-    addnewchild $newhead $oldhead
-    if {[info exists commitrow($curview,$oldhead)]} {
-       insertrow $commitrow($curview,$oldhead) $newhead
-       if {$mainhead ne {}} {
-           movehead $newhead $mainhead
-           movedhead $newhead $mainhead
-       }
-       redrawtags $oldhead
-       redrawtags $newhead
-    }
-    notbusy cherrypick
-}
-
-proc resethead {} {
-    global mainheadid mainhead rowmenuid confirm_ok resettype
-
-    set confirm_ok 0
-    set w ".confirmreset"
-    toplevel $w
-    wm transient $w .
-    wm title $w "Confirm reset"
-    message $w.m -text \
-       "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
-       -justify center -aspect 1000
-    pack $w.m -side top -fill x -padx 20 -pady 20
-    frame $w.f -relief sunken -border 2
-    message $w.f.rt -text "Reset type:" -aspect 1000
-    grid $w.f.rt -sticky w
-    set resettype mixed
-    radiobutton $w.f.soft -value soft -variable resettype -justify left \
-       -text "Soft: Leave working tree and index untouched"
-    grid $w.f.soft -sticky w
-    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
-       -text "Mixed: Leave working tree untouched, reset index"
-    grid $w.f.mixed -sticky w
-    radiobutton $w.f.hard -value hard -variable resettype -justify left \
-       -text "Hard: Reset working tree and index\n(discard ALL local changes)"
-    grid $w.f.hard -sticky w
-    pack $w.f -side top -fill x
-    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
-    pack $w.ok -side left -fill x -padx 20 -pady 20
-    button $w.cancel -text Cancel -command "destroy $w"
-    pack $w.cancel -side right -fill x -padx 20 -pady 20
-    bind $w <Visibility> "grab $w; focus $w"
-    tkwait window $w
-    if {!$confirm_ok} return
-    if {[catch {set fd [open \
-           [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
-       error_popup $err
-    } else {
-       dohidelocalchanges
-       filerun $fd [list readresetstat $fd]
-       nowbusy reset "Resetting"
-    }
-}
-
-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 rprogcoord [expr {1.0 * $m / $n}]
-           adjustprogress
-       }
-       return 1
-    }
-    set rprogcoord 0
-    adjustprogress
-    notbusy reset
-    if {[catch {close $fd} err]} {
-       error_popup $err
-    }
-    set oldhead $mainheadid
-    set newhead [exec git rev-parse HEAD]
-    if {$newhead ne $oldhead} {
-       movehead $newhead $mainhead
-       movedhead $newhead $mainhead
-       set mainheadid $newhead
-       redrawtags $oldhead
-       redrawtags $newhead
-    }
-    if {$showlocalchanges} {
-       doshowlocalchanges
-    }
-    return 0
-}
-
-# context menu for a head
-proc headmenu {x y id head} {
-    global headmenuid headmenuhead headctxmenu mainhead
-
-    stopfinding
-    set headmenuid $id
-    set headmenuhead $head
-    set state normal
-    if {$head eq $mainhead} {
-       set state disabled
-    }
-    $headctxmenu entryconfigure 0 -state $state
-    $headctxmenu entryconfigure 1 -state $state
-    tk_popup $headctxmenu $x $y
-}
-
-proc cobranch {} {
-    global headmenuid headmenuhead mainhead headids
-    global showlocalchanges mainheadid
-
-    # check the tree is clean first??
-    set oldmainhead $mainhead
-    nowbusy checkout "Checking out"
-    update
-    dohidelocalchanges
-    if {[catch {
-       exec git checkout -q $headmenuhead
-    } err]} {
-       notbusy checkout
-       error_popup $err
-    } else {
-       notbusy checkout
-       set mainhead $headmenuhead
-       set mainheadid $headmenuid
-       if {[info exists headids($oldmainhead)]} {
-           redrawtags $headids($oldmainhead)
-       }
-       redrawtags $headmenuid
-    }
-    if {$showlocalchanges} {
-       dodiffindex
-    }
-}
-
-proc rmbranch {} {
-    global headmenuid headmenuhead mainhead
-    global idheads
-
-    set head $headmenuhead
-    set id $headmenuid
-    # this check shouldn't be needed any more...
-    if {$head eq $mainhead} {
-       error_popup "Cannot delete the currently checked-out branch"
-       return
-    }
-    set dheads [descheads $id]
-    if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
-       # the stuff on this branch isn't on any other branch
-       if {![confirm_popup "The commits on branch $head aren't on any other\
-                       branch.\nReally delete branch $head?"]} return
-    }
-    nowbusy rmbranch
-    update
-    if {[catch {exec git branch -D $head} err]} {
-       notbusy rmbranch
-       error_popup $err
-       return
-    }
-    removehead $id $head
-    removedhead $id $head
-    redrawtags $id
-    notbusy rmbranch
-    dispneartags 0
-    run refill_reflist
-}
-
-# Display a list of tags and heads
-proc showrefs {} {
-    global showrefstop bgcolor fgcolor selectbgcolor
-    global bglist fglist reflistfilter reflist maincursor
-
-    set top .showrefs
-    set showrefstop $top
-    if {[winfo exists $top]} {
-       raise $top
-       refill_reflist
-       return
-    }
-    toplevel $top
-    wm title $top "Tags and heads: [file tail [pwd]]"
-    text $top.list -background $bgcolor -foreground $fgcolor \
-       -selectbackground $selectbgcolor -font mainfont \
-       -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
-       -width 30 -height 20 -cursor $maincursor \
-       -spacing1 1 -spacing3 1 -state disabled
-    $top.list tag configure highlight -background $selectbgcolor
-    lappend bglist $top.list
-    lappend fglist $top.list
-    scrollbar $top.ysb -command "$top.list yview" -orient vertical
-    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
-    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
-    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
-    grid $top.close -
-    grid columnconfigure $top 0 -weight 1
-    grid rowconfigure $top 0 -weight 1
-    bind $top.list <1> {break}
-    bind $top.list <B1-Motion> {break}
-    bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
-    set reflist {}
-    refill_reflist
-}
-
-proc sel_reflist {w x y} {
-    global showrefstop reflist headids tagids otherrefids
-
-    if {![winfo exists $showrefstop]} return
-    set l [lindex [split [$w index "@$x,$y"] "."] 0]
-    set ref [lindex $reflist [expr {$l-1}]]
-    set n [lindex $ref 0]
-    switch -- [lindex $ref 1] {
-       "H" {selbyid $headids($n)}
-       "T" {selbyid $tagids($n)}
-       "o" {selbyid $otherrefids($n)}
-    }
-    $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
-}
-
-proc unsel_reflist {} {
-    global showrefstop
-
-    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
-    $showrefstop.list tag remove highlight 0.0 end
-}
-
-proc reflistfilter_change {n1 n2 op} {
-    global reflistfilter
-
-    after cancel refill_reflist
-    after 200 refill_reflist
-}
-
-proc refill_reflist {} {
-    global reflist reflistfilter showrefstop headids tagids otherrefids
-    global commitrow curview commitinterest
-
-    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
-    set refs {}
-    foreach n [array names headids] {
-       if {[string match $reflistfilter $n]} {
-           if {[info exists commitrow($curview,$headids($n))]} {
-               lappend refs [list $n H]
-           } else {
-               set commitinterest($headids($n)) {run refill_reflist}
-           }
-       }
-    }
-    foreach n [array names tagids] {
-       if {[string match $reflistfilter $n]} {
-           if {[info exists commitrow($curview,$tagids($n))]} {
-               lappend refs [list $n T]
-           } else {
-               set commitinterest($tagids($n)) {run refill_reflist}
-           }
-       }
-    }
-    foreach n [array names otherrefids] {
-       if {[string match $reflistfilter $n]} {
-           if {[info exists commitrow($curview,$otherrefids($n))]} {
-               lappend refs [list $n o]
-           } else {
-               set commitinterest($otherrefids($n)) {run refill_reflist}
-           }
-       }
-    }
-    set refs [lsort -index 0 $refs]
-    if {$refs eq $reflist} return
-
-    # Update the contents of $showrefstop.list according to the
-    # differences between $reflist (old) and $refs (new)
-    $showrefstop.list conf -state normal
-    $showrefstop.list insert end "\n"
-    set i 0
-    set j 0
-    while {$i < [llength $reflist] || $j < [llength $refs]} {
-       if {$i < [llength $reflist]} {
-           if {$j < [llength $refs]} {
-               set cmp [string compare [lindex $reflist $i 0] \
-                            [lindex $refs $j 0]]
-               if {$cmp == 0} {
-                   set cmp [string compare [lindex $reflist $i 1] \
-                                [lindex $refs $j 1]]
-               }
-           } else {
-               set cmp -1
-           }
-       } else {
-           set cmp 1
-       }
-       switch -- $cmp {
-           -1 {
-               $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
-               incr i
-           }
-           0 {
-               incr i
-               incr j
-           }
-           1 {
-               set l [expr {$j + 1}]
-               $showrefstop.list image create $l.0 -align baseline \
-                   -image reficon-[lindex $refs $j 1] -padx 2
-               $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
-               incr j
-           }
-       }
-    }
-    set reflist $refs
-    # delete last newline
-    $showrefstop.list delete end-2c end-1c
-    $showrefstop.list conf -state disabled
-}
-
-# Stuff for finding nearby tags
-proc getallcommits {} {
-    global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
-    global idheads idtags idotherrefs allparents tagobjid
-
-    if {![info exists allcommits]} {
-       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
-    }
-
-    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
-    }
-}
-
-# Since most commits have 1 parent and 1 child, we group strings of
-# such commits into "arcs" joining branch/merge points (BMPs), which
-# are commits that either don't have 1 parent or don't have 1 child.
-#
-# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
-# arcout(id) - outgoing arcs for BMP
-# arcids(a) - list of IDs on arc including end but not start
-# arcstart(a) - BMP ID at start of arc
-# arcend(a) - BMP ID at end of arc
-# growing(a) - arc a is still growing
-# arctags(a) - IDs out of arcids (excluding end) that have tags
-# archeads(a) - IDs out of arcids (excluding end) that have heads
-# The start of an arc is at the descendent end, so "incoming" means
-# coming from descendents, and "outgoing" means going towards ancestors.
-
-proc getallclines {fd} {
-    global allparents allchildren idtags idheads nextarc
-    global arcnos arcids arctags arcout arcend arcstart archeads growing
-    global seeds allcommits cachedarcs allcupdate
-    
-    set nid 0
-    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
-       set id [lindex $line 0]
-       if {[info exists allparents($id)]} {
-           # seen it already
-           continue
-       }
-       set cachedarcs 0
-       set olds [lrange $line 1 end]
-       set allparents($id) $olds
-       if {![info exists allchildren($id)]} {
-           set allchildren($id) {}
-           set arcnos($id) {}
-           lappend seeds $id
-       } else {
-           set a $arcnos($id)
-           if {[llength $olds] == 1 && [llength $a] == 1} {
-               lappend arcids($a) $id
-               if {[info exists idtags($id)]} {
-                   lappend arctags($a) $id
-               }
-               if {[info exists idheads($id)]} {
-                   lappend archeads($a) $id
-               }
-               if {[info exists allparents($olds)]} {
-                   # seen parent already
-                   if {![info exists arcout($olds)]} {
-                       splitarc $olds
-                   }
-                   lappend arcids($a) $olds
-                   set arcend($a) $olds
-                   unset growing($a)
-               }
-               lappend allchildren($olds) $id
-               lappend arcnos($olds) $a
-               continue
-           }
-       }
-       foreach a $arcnos($id) {
-           lappend arcids($a) $id
-           set arcend($a) $id
-           unset growing($a)
-       }
-
-       set ao {}
-       foreach p $olds {
-           lappend allchildren($p) $id
-           set a [incr nextarc]
-           set arcstart($a) $id
-           set archeads($a) {}
-           set arctags($a) {}
-           set archeads($a) {}
-           set arcids($a) {}
-           lappend ao $a
-           set growing($a) 1
-           if {[info exists allparents($p)]} {
-               # seen it already, may need to make a new branch
-               if {![info exists arcout($p)]} {
-                   splitarc $p
-               }
-               lappend arcids($a) $p
-               set arcend($a) $p
-               unset growing($a)
-           }
-           lappend arcnos($p) $a
-       }
-       set arcout($id) $ao
-    }
-    if {$nid > 0} {
-       global cached_dheads cached_dtags cached_atags
-       catch {unset cached_dheads}
-       catch {unset cached_dtags}
-       catch {unset cached_atags}
-    }
-    if {![eof $fd]} {
-       return [expr {$nid >= 1000? 2: 1}]
-    }
-    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
-}
-
-proc recalcarc {a} {
-    global arctags archeads arcids idtags idheads
-
-    set at {}
-    set ah {}
-    foreach id [lrange $arcids($a) 0 end-1] {
-       if {[info exists idtags($id)]} {
-           lappend at $id
-       }
-       if {[info exists idheads($id)]} {
-           lappend ah $id
-       }
-    }
-    set arctags($a) $at
-    set archeads($a) $ah
-}
-
-proc splitarc {p} {
-    global arcnos arcids nextarc arctags archeads idtags idheads
-    global arcstart arcend arcout allparents growing
-
-    set a $arcnos($p)
-    if {[llength $a] != 1} {
-       puts "oops splitarc called but [llength $a] arcs already"
-       return
-    }
-    set a [lindex $a 0]
-    set i [lsearch -exact $arcids($a) $p]
-    if {$i < 0} {
-       puts "oops splitarc $p not in arc $a"
-       return
-    }
-    set na [incr nextarc]
-    if {[info exists arcend($a)]} {
-       set arcend($na) $arcend($a)
-    } else {
-       set l [lindex $allparents([lindex $arcids($a) end]) 0]
-       set j [lsearch -exact $arcnos($l) $a]
-       set arcnos($l) [lreplace $arcnos($l) $j $j $na]
-    }
-    set tail [lrange $arcids($a) [expr {$i+1}] end]
-    set arcids($a) [lrange $arcids($a) 0 $i]
-    set arcend($a) $p
-    set arcstart($na) $p
-    set arcout($p) $na
-    set arcids($na) $tail
-    if {[info exists growing($a)]} {
-       set growing($na) 1
-       unset growing($a)
-    }
-
-    foreach id $tail {
-       if {[llength $arcnos($id)] == 1} {
-           set arcnos($id) $na
-       } else {
-           set j [lsearch -exact $arcnos($id) $a]
-           set arcnos($id) [lreplace $arcnos($id) $j $j $na]
-       }
-    }
-
-    # reconstruct tags and heads lists
-    if {$arctags($a) ne {} || $archeads($a) ne {}} {
-       recalcarc $a
-       recalcarc $na
-    } else {
-       set arctags($na) {}
-       set archeads($na) {}
-    }
-}
-
-# Update things for a new commit added that is a child of one
-# existing commit.  Used when cherry-picking.
-proc addnewchild {id p} {
-    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
-    set allparents($id) [list $p]
-    set allchildren($id) {}
-    set arcnos($id) {}
-    lappend seeds $id
-    lappend allchildren($p) $id
-    set a [incr nextarc]
-    set arcstart($a) $id
-    set archeads($a) {}
-    set arctags($a) {}
-    set arcids($a) [list $p]
-    set arcend($a) $p
-    if {![info exists arcout($p)]} {
-       splitarc $p
-    }
-    lappend arcnos($p) $a
-    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} {
-    global arcout arcstart arcend arcnos cached_isanc
-
-    if {$arcnos($a) eq $arcnos($b)} {
-       # Both are on the same arc(s); either both are the same BMP,
-       # or if one is not a BMP, the other is also not a BMP or is
-       # the BMP at end of the arc (and it only has 1 incoming arc).
-       # Or both can be BMPs with no incoming arcs.
-       if {$a eq $b || $arcnos($a) eq {}} {
-           return 0
-       }
-       # assert {[llength $arcnos($a)] == 1}
-       set arc [lindex $arcnos($a) 0]
-       set i [lsearch -exact $arcids($arc) $a]
-       set j [lsearch -exact $arcids($arc) $b]
-       if {$i < 0 || $i > $j} {
-           return 1
-       } else {
-           return -1
-       }
-    }
-
-    if {![info exists arcout($a)]} {
-       set arc [lindex $arcnos($a) 0]
-       if {[info exists arcend($arc)]} {
-           set aend $arcend($arc)
-       } else {
-           set aend {}
-       }
-       set a $arcstart($arc)
-    } else {
-       set aend $a
-    }
-    if {![info exists arcout($b)]} {
-       set arc [lindex $arcnos($b) 0]
-       if {[info exists arcend($arc)]} {
-           set bend $arcend($arc)
-       } else {
-           set bend {}
-       }
-       set b $arcstart($arc)
-    } else {
-       set bend $b
-    }
-    if {$a eq $bend} {
-       return 1
-    }
-    if {$b eq $aend} {
-       return -1
-    }
-    if {[info exists cached_isanc($a,$bend)]} {
-       if {$cached_isanc($a,$bend)} {
-           return 1
-       }
-    }
-    if {[info exists cached_isanc($b,$aend)]} {
-       if {$cached_isanc($b,$aend)} {
-           return -1
-       }
-       if {[info exists cached_isanc($a,$bend)]} {
-           return 0
-       }
-    }
-
-    set todo [list $a $b]
-    set anc($a) a
-    set anc($b) b
-    for {set i 0} {$i < [llength $todo]} {incr i} {
-       set x [lindex $todo $i]
-       if {$anc($x) eq {}} {
-           continue
-       }
-       foreach arc $arcnos($x) {
-           set xd $arcstart($arc)
-           if {$xd eq $bend} {
-               set cached_isanc($a,$bend) 1
-               set cached_isanc($b,$aend) 0
-               return 1
-           } elseif {$xd eq $aend} {
-               set cached_isanc($b,$aend) 1
-               set cached_isanc($a,$bend) 0
-               return -1
-           }
-           if {![info exists anc($xd)]} {
-               set anc($xd) $anc($x)
-               lappend todo $xd
-           } elseif {$anc($xd) ne $anc($x)} {
-               set anc($xd) {}
-           }
-       }
-    }
-    set cached_isanc($a,$bend) 0
-    set cached_isanc($b,$aend) 0
-    return 0
-}
-
-# This identifies whether $desc has an ancestor that is
-# a growing tip of the graph and which is not an ancestor of $anc
-# and returns 0 if so and 1 if not.
-# If we subsequently discover a tag on such a growing tip, and that
-# turns out to be a descendent of $anc (which it could, since we
-# don't necessarily see children before parents), then $desc
-# isn't a good choice to display as a descendent tag of
-# $anc (since it is the descendent of another tag which is
-# a descendent of $anc).  Similarly, $anc isn't a good choice to
-# display as a ancestor tag of $desc.
-#
-proc is_certain {desc anc} {
-    global arcnos arcout arcstart arcend growing problems
-
-    set certain {}
-    if {[llength $arcnos($anc)] == 1} {
-       # tags on the same arc are certain
-       if {$arcnos($desc) eq $arcnos($anc)} {
-           return 1
-       }
-       if {![info exists arcout($anc)]} {
-           # if $anc is partway along an arc, use the start of the arc instead
-           set a [lindex $arcnos($anc) 0]
-           set anc $arcstart($a)
-       }
-    }
-    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
-       set x $desc
-    } else {
-       set a [lindex $arcnos($desc) 0]
-       set x $arcend($a)
-    }
-    if {$x == $anc} {
-       return 1
-    }
-    set anclist [list $x]
-    set dl($x) 1
-    set nnh 1
-    set ngrowanc 0
-    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
-       set x [lindex $anclist $i]
-       if {$dl($x)} {
-           incr nnh -1
-       }
-       set done($x) 1
-       foreach a $arcout($x) {
-           if {[info exists growing($a)]} {
-               if {![info exists growanc($x)] && $dl($x)} {
-                   set growanc($x) 1
-                   incr ngrowanc
-               }
-           } else {
-               set y $arcend($a)
-               if {[info exists dl($y)]} {
-                   if {$dl($y)} {
-                       if {!$dl($x)} {
-                           set dl($y) 0
-                           if {![info exists done($y)]} {
-                               incr nnh -1
-                           }
-                           if {[info exists growanc($x)]} {
-                               incr ngrowanc -1
-                           }
-                           set xl [list $y]
-                           for {set k 0} {$k < [llength $xl]} {incr k} {
-                               set z [lindex $xl $k]
-                               foreach c $arcout($z) {
-                                   if {[info exists arcend($c)]} {
-                                       set v $arcend($c)
-                                       if {[info exists dl($v)] && $dl($v)} {
-                                           set dl($v) 0
-                                           if {![info exists done($v)]} {
-                                               incr nnh -1
-                                           }
-                                           if {[info exists growanc($v)]} {
-                                               incr ngrowanc -1
-                                           }
-                                           lappend xl $v
-                                       }
-                                   }
-                               }
-                           }
-                       }
-                   }
-               } elseif {$y eq $anc || !$dl($x)} {
-                   set dl($y) 0
-                   lappend anclist $y
-               } else {
-                   set dl($y) 1
-                   lappend anclist $y
-                   incr nnh
-               }
-           }
-       }
-    }
-    foreach x [array names growanc] {
-       if {$dl($x)} {
-           return 0
-       }
-       return 0
-    }
-    return 1
-}
-
-proc validate_arctags {a} {
-    global arctags idtags
-
-    set i -1
-    set na $arctags($a)
-    foreach id $arctags($a) {
-       incr i
-       if {![info exists idtags($id)]} {
-           set na [lreplace $na $i $i]
-           incr i -1
-       }
-    }
-    set arctags($a) $na
-}
-
-proc validate_archeads {a} {
-    global archeads idheads
-
-    set i -1
-    set na $archeads($a)
-    foreach id $archeads($a) {
-       incr i
-       if {![info exists idheads($id)]} {
-           set na [lreplace $na $i $i]
-           incr i -1
-       }
-    }
-    set archeads($a) $na
-}
-
-# Return the list of IDs that have tags that are descendents of id,
-# ignoring IDs that are descendents of IDs already reported.
-proc desctags {id} {
-    global arcnos arcstart arcids arctags idtags allparents
-    global growing cached_dtags
-
-    if {![info exists allparents($id)]} {
-       return {}
-    }
-    set t1 [clock clicks -milliseconds]
-    set argid $id
-    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
-       # part-way along an arc; check that arc first
-       set a [lindex $arcnos($id) 0]
-       if {$arctags($a) ne {}} {
-           validate_arctags $a
-           set i [lsearch -exact $arcids($a) $id]
-           set tid {}
-           foreach t $arctags($a) {
-               set j [lsearch -exact $arcids($a) $t]
-               if {$j >= $i} break
-               set tid $t
-           }
-           if {$tid ne {}} {
-               return $tid
-           }
-       }
-       set id $arcstart($a)
-       if {[info exists idtags($id)]} {
-           return $id
-       }
-    }
-    if {[info exists cached_dtags($id)]} {
-       return $cached_dtags($id)
-    }
-
-    set origid $id
-    set todo [list $id]
-    set queued($id) 1
-    set nc 1
-    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
-       set id [lindex $todo $i]
-       set done($id) 1
-       set ta [info exists hastaggedancestor($id)]
-       if {!$ta} {
-           incr nc -1
-       }
-       # ignore tags on starting node
-       if {!$ta && $i > 0} {
-           if {[info exists idtags($id)]} {
-               set tagloc($id) $id
-               set ta 1
-           } elseif {[info exists cached_dtags($id)]} {
-               set tagloc($id) $cached_dtags($id)
-               set ta 1
-           }
-       }
-       foreach a $arcnos($id) {
-           set d $arcstart($a)
-           if {!$ta && $arctags($a) ne {}} {
-               validate_arctags $a
-               if {$arctags($a) ne {}} {
-                   lappend tagloc($id) [lindex $arctags($a) end]
-               }
-           }
-           if {$ta || $arctags($a) ne {}} {
-               set tomark [list $d]
-               for {set j 0} {$j < [llength $tomark]} {incr j} {
-                   set dd [lindex $tomark $j]
-                   if {![info exists hastaggedancestor($dd)]} {
-                       if {[info exists done($dd)]} {
-                           foreach b $arcnos($dd) {
-                               lappend tomark $arcstart($b)
-                           }
-                           if {[info exists tagloc($dd)]} {
-                               unset tagloc($dd)
-                           }
-                       } elseif {[info exists queued($dd)]} {
-                           incr nc -1
-                       }
-                       set hastaggedancestor($dd) 1
-                   }
-               }
-           }
-           if {![info exists queued($d)]} {
-               lappend todo $d
-               set queued($d) 1
-               if {![info exists hastaggedancestor($d)]} {
-                   incr nc
-               }
-           }
-       }
-    }
-    set tags {}
-    foreach id [array names tagloc] {
-       if {![info exists hastaggedancestor($id)]} {
-           foreach t $tagloc($id) {
-               if {[lsearch -exact $tags $t] < 0} {
-                   lappend tags $t
-               }
-           }
-       }
-    }
-    set t2 [clock clicks -milliseconds]
-    set loopix $i
-
-    # remove tags that are descendents of other tags
-    for {set i 0} {$i < [llength $tags]} {incr i} {
-       set a [lindex $tags $i]
-       for {set j 0} {$j < $i} {incr j} {
-           set b [lindex $tags $j]
-           set r [anc_or_desc $a $b]
-           if {$r == 1} {
-               set tags [lreplace $tags $j $j]
-               incr j -1
-               incr i -1
-           } elseif {$r == -1} {
-               set tags [lreplace $tags $i $i]
-               incr i -1
-               break
-           }
-       }
-    }
-
-    if {[array names growing] ne {}} {
-       # graph isn't finished, need to check if any tag could get
-       # eclipsed by another tag coming later.  Simply ignore any
-       # tags that could later get eclipsed.
-       set ctags {}
-       foreach t $tags {
-           if {[is_certain $t $origid]} {
-               lappend ctags $t
-           }
-       }
-       if {$tags eq $ctags} {
-           set cached_dtags($origid) $tags
-       } else {
-           set tags $ctags
-       }
-    } else {
-       set cached_dtags($origid) $tags
-    }
-    set t3 [clock clicks -milliseconds]
-    if {0 && $t3 - $t1 >= 100} {
-       puts "iterating descendents ($loopix/[llength $todo] nodes) took\
-           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
-    }
-    return $tags
-}
-
-proc anctags {id} {
-    global arcnos arcids arcout arcend arctags idtags allparents
-    global growing cached_atags
-
-    if {![info exists allparents($id)]} {
-       return {}
-    }
-    set t1 [clock clicks -milliseconds]
-    set argid $id
-    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
-       # part-way along an arc; check that arc first
-       set a [lindex $arcnos($id) 0]
-       if {$arctags($a) ne {}} {
-           validate_arctags $a
-           set i [lsearch -exact $arcids($a) $id]
-           foreach t $arctags($a) {
-               set j [lsearch -exact $arcids($a) $t]
-               if {$j > $i} {
-                   return $t
-               }
-           }
-       }
-       if {![info exists arcend($a)]} {
-           return {}
-       }
-       set id $arcend($a)
-       if {[info exists idtags($id)]} {
-           return $id
-       }
-    }
-    if {[info exists cached_atags($id)]} {
-       return $cached_atags($id)
-    }
-
-    set origid $id
-    set todo [list $id]
-    set queued($id) 1
-    set taglist {}
-    set nc 1
-    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
-       set id [lindex $todo $i]
-       set done($id) 1
-       set td [info exists hastaggeddescendent($id)]
-       if {!$td} {
-           incr nc -1
-       }
-       # ignore tags on starting node
-       if {!$td && $i > 0} {
-           if {[info exists idtags($id)]} {
-               set tagloc($id) $id
-               set td 1
-           } elseif {[info exists cached_atags($id)]} {
-               set tagloc($id) $cached_atags($id)
-               set td 1
-           }
-       }
-       foreach a $arcout($id) {
-           if {!$td && $arctags($a) ne {}} {
-               validate_arctags $a
-               if {$arctags($a) ne {}} {
-                   lappend tagloc($id) [lindex $arctags($a) 0]
-               }
-           }
-           if {![info exists arcend($a)]} continue
-           set d $arcend($a)
-           if {$td || $arctags($a) ne {}} {
-               set tomark [list $d]
-               for {set j 0} {$j < [llength $tomark]} {incr j} {
-                   set dd [lindex $tomark $j]
-                   if {![info exists hastaggeddescendent($dd)]} {
-                       if {[info exists done($dd)]} {
-                           foreach b $arcout($dd) {
-                               if {[info exists arcend($b)]} {
-                                   lappend tomark $arcend($b)
-                               }
-                           }
-                           if {[info exists tagloc($dd)]} {
-                               unset tagloc($dd)
-                           }
-                       } elseif {[info exists queued($dd)]} {
-                           incr nc -1
-                       }
-                       set hastaggeddescendent($dd) 1
-                   }
-               }
-           }
-           if {![info exists queued($d)]} {
-               lappend todo $d
-               set queued($d) 1
-               if {![info exists hastaggeddescendent($d)]} {
-                   incr nc
-               }
-           }
-       }
-    }
-    set t2 [clock clicks -milliseconds]
-    set loopix $i
-    set tags {}
-    foreach id [array names tagloc] {
-       if {![info exists hastaggeddescendent($id)]} {
-           foreach t $tagloc($id) {
-               if {[lsearch -exact $tags $t] < 0} {
-                   lappend tags $t
-               }
-           }
-       }
-    }
-
-    # remove tags that are ancestors of other tags
-    for {set i 0} {$i < [llength $tags]} {incr i} {
-       set a [lindex $tags $i]
-       for {set j 0} {$j < $i} {incr j} {
-           set b [lindex $tags $j]
-           set r [anc_or_desc $a $b]
-           if {$r == -1} {
-               set tags [lreplace $tags $j $j]
-               incr j -1
-               incr i -1
-           } elseif {$r == 1} {
-               set tags [lreplace $tags $i $i]
-               incr i -1
-               break
-           }
-       }
-    }
-
-    if {[array names growing] ne {}} {
-       # graph isn't finished, need to check if any tag could get
-       # eclipsed by another tag coming later.  Simply ignore any
-       # tags that could later get eclipsed.
-       set ctags {}
-       foreach t $tags {
-           if {[is_certain $origid $t]} {
-               lappend ctags $t
-           }
-       }
-       if {$tags eq $ctags} {
-           set cached_atags($origid) $tags
-       } else {
-           set tags $ctags
-       }
-    } else {
-       set cached_atags($origid) $tags
-    }
-    set t3 [clock clicks -milliseconds]
-    if {0 && $t3 - $t1 >= 100} {
-       puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
-           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
-    }
-    return $tags
-}
-
-# Return the list of IDs that have heads that are descendents of id,
-# including id itself if it has a head.
-proc descheads {id} {
-    global arcnos arcstart arcids archeads idheads cached_dheads
-    global allparents
-
-    if {![info exists allparents($id)]} {
-       return {}
-    }
-    set aret {}
-    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
-       # part-way along an arc; check it first
-       set a [lindex $arcnos($id) 0]
-       if {$archeads($a) ne {}} {
-           validate_archeads $a
-           set i [lsearch -exact $arcids($a) $id]
-           foreach t $archeads($a) {
-               set j [lsearch -exact $arcids($a) $t]
-               if {$j > $i} break
-               lappend aret $t
-           }
-       }
-       set id $arcstart($a)
-    }
-    set origid $id
-    set todo [list $id]
-    set seen($id) 1
-    set ret {}
-    for {set i 0} {$i < [llength $todo]} {incr i} {
-       set id [lindex $todo $i]
-       if {[info exists cached_dheads($id)]} {
-           set ret [concat $ret $cached_dheads($id)]
-       } else {
-           if {[info exists idheads($id)]} {
-               lappend ret $id
-           }
-           foreach a $arcnos($id) {
-               if {$archeads($a) ne {}} {
-                   validate_archeads $a
-                   if {$archeads($a) ne {}} {
-                       set ret [concat $ret $archeads($a)]
-                   }
-               }
-               set d $arcstart($a)
-               if {![info exists seen($d)]} {
-                   lappend todo $d
-                   set seen($d) 1
-               }
-           }
-       }
-    }
-    set ret [lsort -unique $ret]
-    set cached_dheads($origid) $ret
-    return [concat $ret $aret]
-}
-
-proc addedtag {id} {
-    global arcnos arcout cached_dtags cached_atags
-
-    if {![info exists arcnos($id)]} return
-    if {![info exists arcout($id)]} {
-       recalcarc [lindex $arcnos($id) 0]
-    }
-    catch {unset cached_dtags}
-    catch {unset cached_atags}
-}
-
-proc addedhead {hid head} {
-    global arcnos arcout cached_dheads
-
-    if {![info exists arcnos($hid)]} return
-    if {![info exists arcout($hid)]} {
-       recalcarc [lindex $arcnos($hid) 0]
-    }
-    catch {unset cached_dheads}
-}
-
-proc removedhead {hid head} {
-    global cached_dheads
-
-    catch {unset cached_dheads}
-}
-
-proc movedhead {hid head} {
-    global arcnos arcout cached_dheads
-
-    if {![info exists arcnos($hid)]} return
-    if {![info exists arcout($hid)]} {
-       recalcarc [lindex $arcnos($hid) 0]
-    }
-    catch {unset cached_dheads}
-}
-
-proc changedrefs {} {
-    global cached_dheads cached_dtags cached_atags
-    global arctags archeads arcnos arcout idheads idtags
-
-    foreach id [concat [array names idheads] [array names idtags]] {
-       if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
-           set a [lindex $arcnos($id) 0]
-           if {![info exists donearc($a)]} {
-               recalcarc $a
-               set donearc($a) 1
-           }
-       }
-    }
-    catch {unset cached_dtags}
-    catch {unset cached_atags}
-    catch {unset cached_dheads}
-}
-
-proc rereadrefs {} {
-    global idtags idheads idotherrefs mainhead
-
-    set refids [concat [array names idtags] \
-                   [array names idheads] [array names idotherrefs]]
-    foreach id $refids {
-       if {![info exists ref($id)]} {
-           set ref($id) [listrefs $id]
-       }
-    }
-    set oldmainhead $mainhead
-    readrefs
-    changedrefs
-    set refids [lsort -unique [concat $refids [array names idtags] \
-                       [array names idheads] [array names idotherrefs]]]
-    foreach id $refids {
-       set v [listrefs $id]
-       if {![info exists ref($id)] || $ref($id) != $v ||
-           ($id eq $oldmainhead && $id ne $mainhead) ||
-           ($id eq $mainhead && $id ne $oldmainhead)} {
-           redrawtags $id
-       }
-    }
-    run refill_reflist
-}
-
-proc listrefs {id} {
-    global idtags idheads idotherrefs
-
-    set x {}
-    if {[info exists idtags($id)]} {
-       set x $idtags($id)
-    }
-    set y {}
-    if {[info exists idheads($id)]} {
-       set y $idheads($id)
-    }
-    set z {}
-    if {[info exists idotherrefs($id)]} {
-       set z $idotherrefs($id)
-    }
-    return [list $x $y $z]
-}
-
-proc showtag {tag isnew} {
-    global ctext tagcontents tagids linknum tagobjid
-
-    if {$isnew} {
-       addtohistory [list showtag $tag 0]
-    }
-    $ctext conf -state normal
-    clear_ctext
-    settabs 0
-    set linknum 0
-    if {![info exists tagcontents($tag)]} {
-       catch {
-           set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
-       }
-    }
-    if {[info exists tagcontents($tag)]} {
-       set text $tagcontents($tag)
-    } else {
-       set text "Tag: $tag\nId:  $tagids($tag)"
-    }
-    appendwithlinks $text {}
-    $ctext conf -state disabled
-    init_flist {}
-}
-
-proc doquit {} {
-    global stopped
-    set stopped 100
-    savestuff .
-    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
-    global oldprefs prefstop showneartags showlocalchanges
-    global bgcolor fgcolor ctext diffcolors selectbgcolor
-    global uifont tabstop limitdiffs
-
-    set top .gitkprefs
-    set prefstop $top
-    if {[winfo exists $top]} {
-       raise $top
-       return
-    }
-    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
-    grid $top.ldisp - -sticky w -pady 10
-    label $top.spacer -text " "
-    label $top.maxwidthl -text "Maximum graph width (lines)" \
-       -font optionfont
-    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
-    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
-    label $top.maxpctl -text "Maximum graph width (% of pane)" \
-       -font optionfont
-    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
-    grid x $top.maxpctl $top.maxpct -sticky w
-    frame $top.showlocal
-    label $top.showlocal.l -text "Show local changes" -font optionfont
-    checkbutton $top.showlocal.b -variable showlocalchanges
-    pack $top.showlocal.b $top.showlocal.l -side left
-    grid x $top.showlocal -sticky w
-
-    label $top.ddisp -text "Diff display options"
-    $top.ddisp configure -font uifont
-    grid $top.ddisp - -sticky w -pady 10
-    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
-    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
-    grid $top.cdisp - -sticky w -pady 10
-    label $top.bg -padx 40 -relief sunk -background $bgcolor
-    button $top.bgbut -text "Background" -font optionfont \
-       -command [list choosecolor bgcolor 0 $top.bg background setbg]
-    grid x $top.bgbut $top.bg -sticky w
-    label $top.fg -padx 40 -relief sunk -background $fgcolor
-    button $top.fgbut -text "Foreground" -font optionfont \
-       -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
-    grid x $top.fgbut $top.fg -sticky w
-    label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
-    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
-       -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
-                     [list $ctext tag conf d0 -foreground]]
-    grid x $top.diffoldbut $top.diffold -sticky w
-    label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
-    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
-       -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
-                     [list $ctext tag conf d1 -foreground]]
-    grid x $top.diffnewbut $top.diffnew -sticky w
-    label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
-    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
-       -command [list choosecolor diffcolors 2 $top.hunksep \
-                     "diff hunk header" \
-                     [list $ctext tag conf hunksep -foreground]]
-    grid x $top.hunksepbut $top.hunksep -sticky w
-    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
-    button $top.selbgbut -text "Select bg" -font optionfont \
-       -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
-    button $top.buts.can -text "Cancel" -command prefscan -default normal
-    $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
-    grid $top.buts - - -pady 10 -sticky ew
-    bind $top <Visibility> "focus $top.buts.ok"
-}
-
-proc choosecolor {v vi w x cmd} {
-    global $v
-
-    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
-              -title "Gitk: choose color for $x"]
-    if {$c eq {}} return
-    $w conf -background $c
-    lset $v $vi $c
-    eval $cmd $c
-}
-
-proc setselbg {c} {
-    global bglist cflist
-    foreach w $bglist {
-       $w configure -selectbackground $c
-    }
-    $cflist tag configure highlight \
-       -background [$cflist cget -selectbackground]
-    allcanvs itemconf secsel -fill $c
-}
-
-proc setbg {c} {
-    global bglist
-
-    foreach w $bglist {
-       $w conf -background $c
-    }
-}
-
-proc setfg {c} {
-    global fglist canv
-
-    foreach w $fglist {
-       $w conf -foreground $c
-    }
-    allcanvs itemconf text -fill $c
-    $canv itemconf circle -outline $c
-}
-
-proc prefscan {} {
-    global oldprefs prefstop
-
-    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 fontpref mainfont textfont uifont
-    global limitdiffs treediffs
-
-    catch {destroy $prefstop}
-    unset prefstop
-    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
-       } else {
-           dohidelocalchanges
-       }
-    }
-    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) ||
-         $limitdiffs != $oldprefs(limitdiffs)} {
-       reselectline
-    }
-}
-
-proc formatdate {d} {
-    global datetimeformat
-    if {$d ne {}} {
-       set d [clock format $d -format $datetimeformat]
-    }
-    return $d
-}
-
-# This list of encoding names and aliases is distilled from
-# http://www.iana.org/assignments/character-sets.
-# Not all of them are supported by Tcl.
-set encoding_aliases {
-    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
-      ISO646-US US-ASCII us IBM367 cp367 csASCII }
-    { ISO-10646-UTF-1 csISO10646UTF1 }
-    { ISO_646.basic:1983 ref csISO646basic1983 }
-    { INVARIANT csINVARIANT }
-    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
-    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
-    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
-    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
-    { NATS-DANO iso-ir-9-1 csNATSDANO }
-    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
-    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
-    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
-    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
-    { ISO-2022-KR csISO2022KR }
-    { EUC-KR csEUCKR }
-    { ISO-2022-JP csISO2022JP }
-    { ISO-2022-JP-2 csISO2022JP2 }
-    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
-      csISO13JISC6220jp }
-    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
-    { IT iso-ir-15 ISO646-IT csISO15Italian }
-    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
-    { ES iso-ir-17 ISO646-ES csISO17Spanish }
-    { greek7-old iso-ir-18 csISO18Greek7Old }
-    { latin-greek iso-ir-19 csISO19LatinGreek }
-    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
-    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
-    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
-    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
-    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
-    { BS_viewdata iso-ir-47 csISO47BSViewdata }
-    { INIS iso-ir-49 csISO49INIS }
-    { INIS-8 iso-ir-50 csISO50INIS8 }
-    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
-    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
-    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
-    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
-    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
-    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
-      csISO60Norwegian1 }
-    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
-    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
-    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
-    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
-    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
-    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
-    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
-    { greek7 iso-ir-88 csISO88Greek7 }
-    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
-    { iso-ir-90 csISO90 }
-    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
-    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
-      csISO92JISC62991984b }
-    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
-    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
-    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
-      csISO95JIS62291984handadd }
-    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
-    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
-    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
-    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
-      CP819 csISOLatin1 }
-    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
-    { T.61-7bit iso-ir-102 csISO102T617bit }
-    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
-    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
-    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
-    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
-    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
-    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
-    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
-    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
-      arabic csISOLatinArabic }
-    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
-    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
-    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
-      greek greek8 csISOLatinGreek }
-    { T.101-G2 iso-ir-128 csISO128T101G2 }
-    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
-      csISOLatinHebrew }
-    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
-    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
-    { CSN_369103 iso-ir-139 csISO139CSN369103 }
-    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
-    { ISO_6937-2-add iso-ir-142 csISOTextComm }
-    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
-    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
-      csISOLatinCyrillic }
-    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
-    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
-    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
-    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
-    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
-    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
-    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
-    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
-    { ISO_10367-box iso-ir-155 csISO10367Box }
-    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
-    { latin-lap lap iso-ir-158 csISO158Lap }
-    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
-    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
-    { us-dk csUSDK }
-    { dk-us csDKUS }
-    { JIS_X0201 X0201 csHalfWidthKatakana }
-    { KSC5636 ISO646-KR csKSC5636 }
-    { ISO-10646-UCS-2 csUnicode }
-    { ISO-10646-UCS-4 csUCS4 }
-    { DEC-MCS dec csDECMCS }
-    { hp-roman8 roman8 r8 csHPRoman8 }
-    { macintosh mac csMacintosh }
-    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
-      csIBM037 }
-    { IBM038 EBCDIC-INT cp038 csIBM038 }
-    { IBM273 CP273 csIBM273 }
-    { IBM274 EBCDIC-BE CP274 csIBM274 }
-    { IBM275 EBCDIC-BR cp275 csIBM275 }
-    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
-    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
-    { IBM280 CP280 ebcdic-cp-it csIBM280 }
-    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
-    { IBM284 CP284 ebcdic-cp-es csIBM284 }
-    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
-    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
-    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
-    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
-    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
-    { IBM424 cp424 ebcdic-cp-he csIBM424 }
-    { IBM437 cp437 437 csPC8CodePage437 }
-    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
-    { IBM775 cp775 csPC775Baltic }
-    { IBM850 cp850 850 csPC850Multilingual }
-    { IBM851 cp851 851 csIBM851 }
-    { IBM852 cp852 852 csPCp852 }
-    { IBM855 cp855 855 csIBM855 }
-    { IBM857 cp857 857 csIBM857 }
-    { IBM860 cp860 860 csIBM860 }
-    { IBM861 cp861 861 cp-is csIBM861 }
-    { IBM862 cp862 862 csPC862LatinHebrew }
-    { IBM863 cp863 863 csIBM863 }
-    { IBM864 cp864 csIBM864 }
-    { IBM865 cp865 865 csIBM865 }
-    { IBM866 cp866 866 csIBM866 }
-    { IBM868 CP868 cp-ar csIBM868 }
-    { IBM869 cp869 869 cp-gr csIBM869 }
-    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
-    { IBM871 CP871 ebcdic-cp-is csIBM871 }
-    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
-    { IBM891 cp891 csIBM891 }
-    { IBM903 cp903 csIBM903 }
-    { IBM904 cp904 904 csIBBM904 }
-    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
-    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
-    { IBM1026 CP1026 csIBM1026 }
-    { EBCDIC-AT-DE csIBMEBCDICATDE }
-    { EBCDIC-AT-DE-A csEBCDICATDEA }
-    { EBCDIC-CA-FR csEBCDICCAFR }
-    { EBCDIC-DK-NO csEBCDICDKNO }
-    { EBCDIC-DK-NO-A csEBCDICDKNOA }
-    { EBCDIC-FI-SE csEBCDICFISE }
-    { EBCDIC-FI-SE-A csEBCDICFISEA }
-    { EBCDIC-FR csEBCDICFR }
-    { EBCDIC-IT csEBCDICIT }
-    { EBCDIC-PT csEBCDICPT }
-    { EBCDIC-ES csEBCDICES }
-    { EBCDIC-ES-A csEBCDICESA }
-    { EBCDIC-ES-S csEBCDICESS }
-    { EBCDIC-UK csEBCDICUK }
-    { EBCDIC-US csEBCDICUS }
-    { UNKNOWN-8BIT csUnknown8BiT }
-    { MNEMONIC csMnemonic }
-    { MNEM csMnem }
-    { VISCII csVISCII }
-    { VIQR csVIQR }
-    { KOI8-R csKOI8R }
-    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
-    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
-    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
-    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
-    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
-    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
-    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
-    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
-    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
-    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
-    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
-    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
-    { IBM1047 IBM-1047 }
-    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
-    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
-    { UNICODE-1-1 csUnicode11 }
-    { CESU-8 csCESU-8 }
-    { BOCU-1 csBOCU-1 }
-    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
-    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
-      l8 }
-    { ISO-8859-15 ISO_8859-15 Latin-9 }
-    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
-    { GBK CP936 MS936 windows-936 }
-    { JIS_Encoding csJISEncoding }
-    { Shift_JIS MS_Kanji csShiftJIS }
-    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
-      EUC-JP }
-    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
-    { ISO-10646-UCS-Basic csUnicodeASCII }
-    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
-    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
-    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
-    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
-    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
-    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
-    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
-    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
-    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
-    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
-    { Adobe-Standard-Encoding csAdobeStandardEncoding }
-    { Ventura-US csVenturaUS }
-    { Ventura-International csVenturaInternational }
-    { PC8-Danish-Norwegian csPC8DanishNorwegian }
-    { PC8-Turkish csPC8Turkish }
-    { IBM-Symbols csIBMSymbols }
-    { IBM-Thai csIBMThai }
-    { HP-Legal csHPLegal }
-    { HP-Pi-font csHPPiFont }
-    { HP-Math8 csHPMath8 }
-    { Adobe-Symbol-Encoding csHPPSMath }
-    { HP-DeskTop csHPDesktop }
-    { Ventura-Math csVenturaMath }
-    { Microsoft-Publishing csMicrosoftPublishing }
-    { Windows-31J csWindows31J }
-    { GB2312 csGB2312 }
-    { Big5 csBig5 }
-}
-
-proc tcl_encoding {enc} {
-    global encoding_aliases
-    set names [encoding names]
-    set lcnames [string tolower $names]
-    set enc [string tolower $enc]
-    set i [lsearch -exact $lcnames $enc]
-    if {$i < 0} {
-       # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
-       if {[regsub {^iso[-_]} $enc iso encx]} {
-           set i [lsearch -exact $lcnames $encx]
-       }
-    }
-    if {$i < 0} {
-       foreach l $encoding_aliases {
-           set ll [string tolower $l]
-           if {[lsearch -exact $ll $enc] < 0} continue
-           # look through the aliases for one that tcl knows about
-           foreach e $ll {
-               set i [lsearch -exact $lcnames $e]
-               if {$i < 0} {
-                   if {[regsub {^iso[-_]} $e iso ex]} {
-                       set i [lsearch -exact $lcnames $ex]
-                   }
-               }
-               if {$i >= 0} break
-           }
-           break
-       }
-    }
-    if {$i >= 0} {
-       return [lindex $names $i]
-    }
-    return {}
-}
-
-# First check that Tcl/Tk is recent enough
-if {[catch {package require Tk 8.4} err]} {
-    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
-                    Gitk requires at least Tcl/Tk 8.4."
-    exit 1
-}
-
-# defaults...
-set datemode 0
-set wrcomcmd "git diff-tree --stdin -p --pretty"
-
-set gitencoding {}
-catch {
-    set gitencoding [exec git config --get i18n.commitencoding]
-}
-if {$gitencoding == ""} {
-    set gitencoding "utf-8"
-}
-set tclencoding [tcl_encoding $gitencoding]
-if {$tclencoding == {}} {
-    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
-}
-
-set mainfont {Helvetica 9}
-set textfont {Courier 9}
-set uifont {Helvetica 9 bold}
-set tabstop 8
-set findmergefiles 0
-set maxgraphpct 50
-set maxwidth 16
-set revlistorder 0
-set fastdate 0
-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}
-set bgcolor white
-set fgcolor black
-set diffcolors {red "#00a000" blue}
-set diffcontext 3
-set selectbgcolor gray85
-
-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."
-    exit 1
-}
-if {![file isdirectory $gitdir]} {
-    show_error {} . "Cannot find the git directory \"$gitdir\"."
-    exit 1
-}
-
-set mergeonly 0
-set revtreeargs {}
-set cmdline_files {}
-set i 0
-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
-       }
-       default {
-           lappend revtreeargs $arg
-       }
-    }
-    incr i
-}
-
-if {$i >= [llength $argv] && $revtreeargs ne {}} {
-    # no -- on command line, but some arguments (other than -d)
-    if {[catch {
-       set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
-       set cmdline_files [split $f "\n"]
-       set n [llength $cmdline_files]
-       set revtreeargs [lrange $revtreeargs 0 end-$n]
-       # Unfortunately git rev-parse doesn't produce an error when
-       # something is both a revision and a filename.  To be consistent
-       # with git log and git rev-list, check revtreeargs for filenames.
-       foreach arg $revtreeargs {
-           if {[file exists $arg]} {
-               show_error {} . "Ambiguous argument '$arg': both revision\
-                                and filename"
-               exit 1
-           }
-       }
-    } err]} {
-       # unfortunately we get both stdout and stderr in $err,
-       # so look for "fatal:".
-       set i [string first "fatal:" $err]
-       if {$i > 0} {
-           set err [string range $err [expr {$i + 6}] end]
-       }
-       show_error {} . "Bad arguments to gitk:\n$err"
-       exit 1
-    }
-}
-
-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 {}
-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 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) {}
-
-set cmdlineok 0
-set stopped 0
-set stuffsaved 0
-set patchnum 0
-set localirow -1
-set localfrow -1
-set lserial 0
-setcoords
-makewindow
-# wait for the window to become visible
-tkwait visibility .
-wm title . "[file tail $argv0]: [file tail [pwd]]"
-readrefs
-
-if {$cmdline_files ne {} || $revtreeargs ne {}} {
-    # create a view for the files/dirs specified on the command line
-    set curview 1
-    set selectedview 1
-    set nextviewnum 2
-    set viewname(1) "Command line"
-    set viewfiles(1) $cmdline_files
-    set viewargs(1) $revtreeargs
-    set viewperm(1) 0
-    addviewmenu 1
-    .bar.view entryconf Edit* -state normal
-    .bar.view entryconf Delete* -state normal
-}
-
-if {[info exists permviews]} {
-    foreach v $permviews {
-       set n $nextviewnum
-       incr nextviewnum
-       set viewname($n) [lindex $v 0]
-       set viewfiles($n) [lindex $v 1]
-       set viewargs($n) [lindex $v 2]
-       set viewperm($n) 1
-       addviewmenu $n
-    }
-}
-getcommits
diff --git a/gitk-git/Makefile b/gitk-git/Makefile
new file mode 100644 (file)
index 0000000..9bc1e24
--- /dev/null
@@ -0,0 +1,29 @@
+# The default target of this Makefile is...
+all::
+
+prefix ?= $(HOME)
+bindir ?= $(prefix)/bin
+TCLTK_PATH ?= wish
+INSTALL ?= install
+RM ?= rm -f
+
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
+
+ifndef V
+       QUIET          = @
+       QUIET_GEN      = $(QUIET)echo '   ' GEN $@ &&
+endif
+
+all:: gitk-wish
+install:: all
+       $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+clean::
+       $(RM) gitk-wish
+
+gitk-wish: gitk
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
+       chmod +x $@+ && \
+       mv -f $@+ $@
diff --git a/gitk-git/gitk b/gitk-git/gitk
new file mode 100644 (file)
index 0000000..1da0b0a
--- /dev/null
@@ -0,0 +1,8661 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+# Copyright (C) 2005-2006 Paul Mackerras.  All rights reserved.
+# This program is free software; it may be used, copied, modified
+# and distributed under the terms of the GNU General Public Licence,
+# either version 2, or (at your option) any later version.
+
+proc gitdir {} {
+    global env
+    if {[info exists env(GIT_DIR)]} {
+       return $env(GIT_DIR)
+    } else {
+       return [exec git rev-parse --git-dir]
+    }
+}
+
+# A simple scheduler for compute-intensive stuff.
+# The aim is to make sure that event handlers for GUI actions can
+# run at least every 50-100 ms.  Unfortunately fileevent handlers are
+# run before X event handlers, so reading from a fast source can
+# make the GUI completely unresponsive.
+proc run args {
+    global isonrunq runq
+
+    set script $args
+    if {[info exists isonrunq($script)]} return
+    if {$runq eq {}} {
+       after idle dorunq
+    }
+    lappend runq [list {} $script]
+    set isonrunq($script) 1
+}
+
+proc filerun {fd script} {
+    fileevent $fd readable [list filereadable $fd $script]
+}
+
+proc filereadable {fd script} {
+    global runq
+
+    fileevent $fd readable {}
+    if {$runq eq {}} {
+       after idle dorunq
+    }
+    lappend runq [list $fd $script]
+}
+
+proc dorunq {} {
+    global isonrunq runq
+
+    set tstart [clock clicks -milliseconds]
+    set t0 $tstart
+    while {$runq ne {}} {
+       set fd [lindex $runq 0 0]
+       set script [lindex $runq 0 1]
+       set repeat [eval $script]
+       set t1 [clock clicks -milliseconds]
+       set t [expr {$t1 - $t0}]
+       set runq [lrange $runq 1 end]
+       if {$repeat ne {} && $repeat} {
+           if {$fd eq {} || $repeat == 2} {
+               # script returns 1 if it wants to be readded
+               # file readers return 2 if they could do more straight away
+               lappend runq [list $fd $script]
+           } else {
+               fileevent $fd readable [list filereadable $fd $script]
+           }
+       } elseif {$fd eq {}} {
+           unset isonrunq($script)
+       }
+       set t0 $t1
+       if {$t1 - $tstart >= 80} break
+    }
+    if {$runq ne {}} {
+       after idle dorunq
+    }
+}
+
+# Start off a git rev-list process and arrange to read its output
+proc start_rev_list {view} {
+    global startmsecs
+    global commfd leftover tclencoding datemode
+    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"
+    }
+    if {[catch {
+       set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \
+                        --boundary $viewargs($view) "--" $viewfiles($view)] r]
+    } err]} {
+       error_popup "Error executing git rev-list: $err"
+       exit 1
+    }
+    set commfd($view) $fd
+    set leftover($view) {}
+    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 "Reading"
+    if {$view == $curview} {
+       set progressdirn 1
+       set progresscoords {0 0}
+       set proglastnc 0
+    }
+}
+
+proc stop_rev_list {} {
+    global commfd curview
+
+    if {![info exists commfd($curview)]} return
+    set fd $commfd($curview)
+    catch {
+       set pid [pid $fd]
+       exec kill $pid
+    }
+    catch {close $fd}
+    unset commfd($curview)
+}
+
+proc getcommits {} {
+    global phase canv curview
+
+    set phase getcommits
+    initlayout
+    start_rev_list $curview
+    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 commitinterest
+    global leftover commfd
+    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...
+    if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
+       set stuff "\0"
+    }
+    if {$stuff == {}} {
+       if {![eof $fd]} {
+           return 1
+       }
+       # 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]} {
+           set fv {}
+           if {$view != $curview} {
+               set fv " for the \"$viewname($view)\" view"
+           }
+           if {[string range $err 0 4] == "usage"} {
+               set err "Gitk: error reading commits$fv:\
+                       bad arguments to git rev-list."
+               if {$viewname($view) eq "Command line"} {
+                   append err \
+                       "  (Note: arguments to gitk are passed to git rev-list\
+                        to allow selection of commits to be displayed.)"
+               }
+           } else {
+               set err "Error reading commits$fv: $err"
+           }
+           error_popup $err
+       }
+       if {$view == $curview} {
+           run chewcommits $view
+       }
+       return 0
+    }
+    set start 0
+    set gotsome 0
+    while 1 {
+       set i [string first "\0" $stuff $start]
+       if {$i < 0} {
+           append leftover($view) [string range $stuff $start end]
+           break
+       }
+       if {$start == 0} {
+           set cmit $leftover($view)
+           append cmit [string range $stuff 0 [expr {$i - 1}]]
+           set leftover($view) {}
+       } else {
+           set cmit [string range $stuff $start [expr {$i - 1}]]
+       }
+       set start [expr {$i + 1}]
+       set j [string first "\n" $cmit]
+       set ok 0
+       set listed 1
+       if {$j >= 0 && [string match "commit *" $cmit]} {
+           set ids [string range $cmit 7 [expr {$j - 1}]]
+           if {[string match {[-<>]*} $ids]} {
+               switch -- [string index $ids 0] {
+                   "-" {set listed 0}
+                   "<" {set listed 2}
+                   ">" {set listed 3}
+               }
+               set ids [string range $ids 1 end]
+           }
+           set ok 1
+           foreach id $ids {
+               if {[string length $id] != 40} {
+                   set ok 0
+                   break
+               }
+           }
+       }
+       if {!$ok} {
+           set shortcmit $cmit
+           if {[string length $shortcmit] > 80} {
+               set shortcmit "[string range $shortcmit 0 80]..."
+           }
+           error_popup "Can't parse git log output: {$shortcmit}"
+           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]
+           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
+               }
+           }
+       } else {
+           set olds {}
+       }
+       if {![info exists children($view,$id)]} {
+           set children($view,$id) {}
+       }
+       set commitdata($id) [string range $cmit [expr {$j + 1}] end]
+       set commitrow($view,$id) $commitidx($view)
+       incr commitidx($view)
+       if {$view == $curview} {
+           lappend parentlist $olds
+           lappend displayorder $id
+           lappend commitlisted $listed
+       } else {
+           lappend vparentlist($view) $olds
+           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 viewcomplete
+    global selectedline pending_select
+
+    if {$view == $curview} {
+       layoutmore
+       if {$viewcomplete($view)} {
+           global displayorder commitidx phase
+           global numcommits startmsecs
+
+           if {[info exists pending_select]} {
+               set row [first_real_row]
+               selectline $row 1
+           }
+           if {$commitidx($curview) > 0} {
+               #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
+               #puts "overall $ms ms for $numcommits commits"
+           } else {
+               show_status "No commits selected"
+           }
+           notbusy layout
+           set phase {}
+       }
+    }
+    if {[info exists hlview] && $view == $hlview} {
+       vhighlightmore
+    }
+    return 0
+}
+
+proc readcommit {id} {
+    if {[catch {set contents [exec git cat-file commit $id]}]} return
+    parsecommit $id $contents 0
+}
+
+proc updatecommits {} {
+    global viewdata curview phase displayorder ordertok idpending
+    global children commitrow selectedline thickerline showneartags
+
+    if {$phase ne {}} {
+       stop_rev_list
+       set phase {}
+    }
+    set n $curview
+    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}
+    catch {unset thickerline}
+    catch {unset viewdata($n)}
+    readrefs
+    changedrefs
+    if {$showneartags} {
+       getallcommits
+    }
+    showview $n
+}
+
+proc parsecommit {id contents listed} {
+    global commitinfo cdate
+
+    set inhdr 1
+    set comment {}
+    set headline {}
+    set auname {}
+    set audate {}
+    set comname {}
+    set comdate {}
+    set hdrend [string first "\n\n" $contents]
+    if {$hdrend < 0} {
+       # should never happen...
+       set hdrend [string length $contents]
+    }
+    set header [string range $contents 0 [expr {$hdrend - 1}]]
+    set comment [string range $contents [expr {$hdrend + 2}] end]
+    foreach line [split $header "\n"] {
+       set tag [lindex $line 0]
+       if {$tag == "author"} {
+           set audate [lindex $line end-1]
+           set auname [lrange $line 1 end-2]
+       } elseif {$tag == "committer"} {
+           set comdate [lindex $line end-1]
+           set comname [lrange $line 1 end-2]
+       }
+    }
+    set headline {}
+    # take the first non-blank line of the comment as the headline
+    set headline [string trimleft $comment]
+    set i [string first "\n" $headline]
+    if {$i >= 0} {
+       set headline [string range $headline 0 $i]
+    }
+    set headline [string trimright $headline]
+    set i [string first "\r" $headline]
+    if {$i >= 0} {
+       set headline [string trimright [string range $headline 0 $i]]
+    }
+    if {!$listed} {
+       # git rev-list indents the comment by 4 spaces;
+       # if we got this via git cat-file, add the indentation
+       set newcomment {}
+       foreach line [split $comment "\n"] {
+           append newcomment "    "
+           append newcomment $line
+           append newcomment "\n"
+       }
+       set comment $newcomment
+    }
+    if {$comdate != {}} {
+       set cdate($id) $comdate
+    }
+    set commitinfo($id) [list $headline $auname $audate \
+                            $comname $comdate $comment]
+}
+
+proc getcommit {id} {
+    global commitdata commitinfo
+
+    if {[info exists commitdata($id)]} {
+       parsecommit $id $commitdata($id) 1
+    } else {
+       readcommit $id
+       if {![info exists commitinfo($id)]} {
+           set commitinfo($id) {"No commit information available"}
+       }
+    }
+    return 1
+}
+
+proc readrefs {} {
+    global tagids idtags headids idheads tagobjid
+    global otherrefids idotherrefs mainhead mainheadid
+
+    foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
+       catch {unset $v}
+    }
+    set refd [open [list | git show-ref -d] r]
+    while {[gets $refd line] >= 0} {
+       if {[string index $line 40] ne " "} continue
+       set id [string range $line 0 39]
+       set ref [string range $line 41 end]
+       if {![string match "refs/*" $ref]} continue
+       set name [string range $ref 5 end]
+       if {[string match "remotes/*" $name]} {
+           if {![string match "*/HEAD" $name]} {
+               set headids($name) $id
+               lappend idheads($id) $name
+           }
+       } elseif {[string match "heads/*" $name]} {
+           set name [string range $name 6 end]
+           set headids($name) $id
+           lappend idheads($id) $name
+       } elseif {[string match "tags/*" $name]} {
+           # this lets refs/tags/foo^{} overwrite refs/tags/foo,
+           # which is what we want since the former is the commit ID
+           set name [string range $name 5 end]
+           if {[string match "*^{}" $name]} {
+               set name [string range $name 0 end-3]
+           } else {
+               set tagobjid($name) $id
+           }
+           set tagids($name) $id
+           lappend idtags($id) $name
+       } else {
+           set otherrefids($name) $id
+           lappend idotherrefs($id) $name
+       }
+    }
+    catch {close $refd}
+    set mainhead {}
+    set mainheadid {}
+    catch {
+       set thehead [exec git symbolic-ref HEAD]
+       if {[string match "refs/heads/*" $thehead]} {
+           set mainhead [string range $thehead 11 end]
+           if {[info exists headids($mainhead)]} {
+               set mainheadid $headids($mainhead)
+           }
+       }
+    }
+}
+
+# skip over fake commits
+proc first_real_row {} {
+    global nullid nullid2 displayorder numcommits
+
+    for {set row 0} {$row < $numcommits} {incr row} {
+       set id [lindex $displayorder $row]
+       if {$id ne $nullid && $id ne $nullid2} {
+           break
+       }
+    }
+    return $row
+}
+
+# update things for a head moved to a child of its previous location
+proc movehead {id name} {
+    global headids idheads
+
+    removehead $headids($name) $name
+    set headids($name) $id
+    lappend idheads($id) $name
+}
+
+# update things when a head has been removed
+proc removehead {id name} {
+    global headids idheads
+
+    if {$idheads($id) eq $name} {
+       unset idheads($id)
+    } else {
+       set i [lsearch -exact $idheads($id) $name]
+       if {$i >= 0} {
+           set idheads($id) [lreplace $idheads($id) $i $i]
+       }
+    }
+    unset headids($name)
+}
+
+proc show_error {w top msg} {
+    message $w.m -text $msg -justify center -aspect 400
+    pack $w.m -side top -fill x -padx 20 -pady 20
+    button $w.ok -text OK -command "destroy $top"
+    pack $w.ok -side bottom -fill x
+    bind $top <Visibility> "grab $top; focus $top"
+    bind $top <Key-Return> "destroy $top"
+    tkwait window $top
+}
+
+proc error_popup msg {
+    set w .error
+    toplevel $w
+    wm transient $w .
+    show_error $w $w $msg
+}
+
+proc confirm_popup msg {
+    global confirm_ok
+    set confirm_ok 0
+    set w .confirm
+    toplevel $w
+    wm transient $w .
+    message $w.m -text $msg -justify center -aspect 400
+    pack $w.m -side top -fill x -padx 20 -pady 20
+    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+    pack $w.ok -side left -fill x
+    button $w.cancel -text Cancel -command "destroy $w"
+    pack $w.cancel -side right -fill x
+    bind $w <Visibility> "grab $w; focus $w"
+    tkwait window $w
+    return $confirm_ok
+}
+
+proc makewindow {} {
+    global canv canv2 canv3 linespc charspc ctext cflist
+    global tabstop
+    global findtype findtypemenu findloc findstring fstring geometry
+    global entries sha1entry sha1string sha1but
+    global diffcontextstring diffcontext
+    global maincursor textcursor curtextcursor
+    global rowctxmenu fakerowmenu mergemax wrapcomment
+    global highlight_files gdttype
+    global searchstring sstring
+    global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
+    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
+    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
+    menu .bar.edit
+    .bar add cascade -label "Edit" -menu .bar.edit
+    .bar.edit add command -label "Preferences" -command doprefs
+    .bar.edit configure -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 \
+       -state disabled
+    .bar.view add command -label "Delete view" -command delview -state disabled
+    .bar.view add separator
+    .bar.view add radiobutton -label "All files" -command {showview 0} \
+       -variable selectedview -value 0
+
+    menu .bar.help
+    .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
+    . configure -menu .bar
+
+    # the gui has upper and lower half, parts of a paned window.
+    panedwindow .ctop -orient vertical
+
+    # possibly use assumed geometry
+    if {![info exists geometry(pwsash0)]} {
+        set geometry(topheight) [expr {15 * $linespc}]
+        set geometry(topwidth) [expr {80 * $charspc}]
+        set geometry(botheight) [expr {15 * $linespc}]
+        set geometry(botwidth) [expr {50 * $charspc}]
+        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
+        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
+    }
+
+    # the upper half will have a paned window, a scroll bar to the right, and some stuff below
+    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
+    frame .tf.histframe
+    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
+
+    # create three canvases
+    set cscroll .tf.histframe.csb
+    set canv .tf.histframe.pwclist.canv
+    canvas $canv \
+       -selectbackground $selectbgcolor \
+       -background $bgcolor -bd 0 \
+       -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
+    .tf.histframe.pwclist add $canv
+    set canv2 .tf.histframe.pwclist.canv2
+    canvas $canv2 \
+       -selectbackground $selectbgcolor \
+       -background $bgcolor -bd 0 -yscrollincr $linespc
+    .tf.histframe.pwclist add $canv2
+    set canv3 .tf.histframe.pwclist.canv3
+    canvas $canv3 \
+       -selectbackground $selectbgcolor \
+       -background $bgcolor -bd 0 -yscrollincr $linespc
+    .tf.histframe.pwclist add $canv3
+    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
+    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+
+    # a scroll bar to rule them
+    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
+    pack $cscroll -side right -fill y
+    bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
+    lappend bglist $canv $canv2 $canv3
+    pack .tf.histframe.pwclist -fill both -expand 1 -side left
+
+    # we have two button bars at bottom of top frame. Bar 1
+    frame .tf.bar
+    frame .tf.lbar -height 15
+
+    set sha1entry .tf.bar.sha1
+    set entries $sha1entry
+    set sha1but .tf.bar.sha1label
+    button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
+       -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
+    trace add variable sha1string write sha1change
+    pack $sha1entry -side left -pady 2
+
+    image create bitmap bm-left -data {
+       #define left_width 16
+       #define left_height 16
+       static unsigned char left_bits[] = {
+       0x00, 0x00, 0xc0, 0x01, 0xe0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1c, 0x00,
+       0x0e, 0x00, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x0e, 0x00, 0x1c, 0x00,
+       0x38, 0x00, 0x70, 0x00, 0xe0, 0x00, 0xc0, 0x01};
+    }
+    image create bitmap bm-right -data {
+       #define right_width 16
+       #define right_height 16
+       static unsigned char right_bits[] = {
+       0x00, 0x00, 0xc0, 0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x0e, 0x00, 0x1c,
+       0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
+       0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
+    }
+    button .tf.bar.leftbut -image bm-left -command goback \
+       -state disabled -width 26
+    pack .tf.bar.leftbut -side left -fill y
+    button .tf.bar.rightbut -image bm-right -command goforw \
+       -state disabled -width 26
+    pack .tf.bar.rightbut -side left -fill y
+
+    # 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.lbar.findstring
+    lappend entries $fstring
+    entry $fstring -width 30 -font textfont -textvariable findstring
+    trace add variable findstring write find_change
+    set findtype Exact
+    set findtypemenu [tk_optionMenu .tf.lbar.findtype \
+                     findtype Exact IgnCase Regexp]
+    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.lbar.findloc findloc "All fields" Headline \
+       Comments Author Committer
+    trace add variable findloc write find_change
+    .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
+    pack .tf.bar -in .tf -side bottom -fill x
+    pack .tf.histframe -fill both -side top -expand 1
+    .ctop add .tf
+    .ctop paneconfigure .tf -height $geometry(topheight)
+    .ctop paneconfigure .tf -width $geometry(topwidth)
+
+    # now build up the bottom
+    panedwindow .pwbottom -orient horizontal
+
+    # lower left, a text box over search bar, scroll bar to the right
+    # if we know window height, then that will set the lower text height, otherwise
+    # we set lower text height which will drive window height
+    if {[info exists geometry(main)]} {
+        frame .bleft -width $geometry(botwidth)
+    } else {
+        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
+    }
+    frame .bleft.top
+    frame .bleft.mid
+
+    button .bleft.top.search -text "Search" -command dosearch \
+       -font uifont
+    pack .bleft.top.search -side left -padx 5
+    set sstring .bleft.top.sstring
+    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" -font uifont \
+       -command changediffdisp -variable diffelide -value {0 0}
+    radiobutton .bleft.mid.old -text "Old version" -font uifont \
+       -command changediffdisp -variable diffelide -value {0 1}
+    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
+    pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
+    spinbox .bleft.mid.diffcontext -width 5 -font textfont \
+       -from 1 -increment 1 -to 10000000 \
+       -validate all -validatecommand "diffcontextvalidate %P" \
+       -textvariable diffcontextstring
+    .bleft.mid.diffcontext set $diffcontext
+    trace add variable diffcontextstring write diffcontextchange
+    lappend entries .bleft.mid.diffcontext
+    pack .bleft.mid.labeldiffcontext .bleft.mid.diffcontext -side left
+    set ctext .bleft.ctext
+    text $ctext -background $bgcolor -foreground $fgcolor \
+       -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
+    pack .bleft.sb -side right -fill y
+    pack $ctext -side left -fill both -expand 1
+    lappend bglist $ctext
+    lappend fglist $ctext
+
+    $ctext tag conf comment -wrap $wrapcomment
+    $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]
+    $ctext tag conf m0 -fore red
+    $ctext tag conf m1 -fore blue
+    $ctext tag conf m2 -fore green
+    $ctext tag conf m3 -fore purple
+    $ctext tag conf m4 -fore brown
+    $ctext tag conf m5 -fore "#009090"
+    $ctext tag conf m6 -fore magenta
+    $ctext tag conf m7 -fore "#808000"
+    $ctext tag conf m8 -fore "#009000"
+    $ctext tag conf m9 -fore "#ff0080"
+    $ctext tag conf m10 -fore cyan
+    $ctext tag conf m11 -fore "#b07070"
+    $ctext tag conf m12 -fore "#70b0f0"
+    $ctext tag conf m13 -fore "#70f0b0"
+    $ctext tag conf m14 -fore "#f0b070"
+    $ctext tag conf m15 -fore "#ff70b0"
+    $ctext tag conf mmax -fore darkgrey
+    set mergemax 16
+    $ctext tag conf mresult -font textfontbold
+    $ctext tag conf msep -font textfontbold
+    $ctext tag conf found -back yellow
+
+    .pwbottom add .bleft
+    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+
+    # lower right
+    frame .bright
+    frame .bright.mode
+    radiobutton .bright.mode.patch -text "Patch" \
+       -command reselectline -variable cmitmode -value "patch"
+    .bright.mode.patch configure -font uifont
+    radiobutton .bright.mode.tree -text "Tree" \
+       -command reselectline -variable cmitmode -value "tree"
+    .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"]
+    text $cflist \
+       -selectbackground $selectbgcolor \
+       -background $bgcolor -foreground $fgcolor \
+       -font mainfont \
+       -tabs [list $indent [expr {2 * $indent}]] \
+       -yscrollcommand ".bright.sb set" \
+       -cursor [. cget -cursor] \
+       -spacing1 1 -spacing3 1
+    lappend bglist $cflist
+    lappend fglist $cflist
+    scrollbar .bright.sb -command "$cflist yview"
+    pack .bright.sb -side right -fill y
+    pack $cflist -side left -fill both -expand 1
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
+    $cflist tag configure bold -font mainfontbold
+
+    .pwbottom add .bright
+    .ctop add .pwbottom
+
+    # restore window position if known
+    if {[info exists geometry(main)]} {
+        wm geometry . "$geometry(main)"
+    }
+
+    if {[tk windowingsystem] eq {aqua}} {
+        set M1B M1
+    } else {
+        set M1B Control
+    }
+
+    bind .pwbottom <Configure> {resizecdetpanes %W %w}
+    pack .ctop -fill both -expand 1
+    bindall <1> {selcanvline %W %x %y}
+    #bindall <B1-Motion> {selcanvline %W %x %y}
+    if {[tk windowingsystem] == "win32"} {
+       bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
+       bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
+    } else {
+       bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
+       bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+        if {[tk windowingsystem] eq "aqua"} {
+            bindall <MouseWheel> {
+                set delta [expr {- (%D)}]
+                allcanvs yview scroll $delta units
+            }
+        }
+    }
+    bindall <2> "canvscan mark %W %x %y"
+    bindall <B2-Motion> "canvscan dragto %W %x %y"
+    bindkey <Home> selfirstline
+    bindkey <End> sellastline
+    bind . <Key-Up> "selnextline -1"
+    bind . <Key-Down> "selnextline 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"
+    bind . <Key-Next> "selnextpage 1"
+    bind . <$M1B-Home> "allcanvs yview moveto 0.0"
+    bind . <$M1B-End> "allcanvs yview moveto 1.0"
+    bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
+    bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
+    bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
+    bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
+    bindkey <Key-Delete> "$ctext yview scroll -1 pages"
+    bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
+    bindkey <Key-space> "$ctext yview scroll 1 pages"
+    bindkey p "selnextline -1"
+    bindkey n "selnextline 1"
+    bindkey z "goback"
+    bindkey x "goforw"
+    bindkey i "selnextline -1"
+    bindkey k "selnextline 1"
+    bindkey j "goback"
+    bindkey l "goforw"
+    bindkey b "$ctext yview scroll -1 pages"
+    bindkey d "$ctext yview scroll 18 units"
+    bindkey u "$ctext yview scroll -18 units"
+    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 1 1}
+    bind . <$M1B-g> {dofind 1 0}
+    bind . <$M1B-r> dosearchback
+    bind . <$M1B-s> dosearch
+    bind . <$M1B-equal> {incrfont 1}
+    bind . <$M1B-KP_Add> {incrfont 1}
+    bind . <$M1B-minus> {incrfont -1}
+    bind . <$M1B-KP_Subtract> {incrfont -1}
+    wm protocol . WM_DELETE_WINDOW doquit
+    bind . <Button-1> "click %W"
+    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}
+    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
+    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
+    bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
+
+    set maincursor [. cget -cursor]
+    set textcursor [$ctext cget -cursor]
+    set curtextcursor $textcursor
+
+    set rowctxmenu .rowctxmenu
+    menu $rowctxmenu -tearoff 0
+    $rowctxmenu add command -label "Diff this -> selected" \
+       -command {diffvssel 0}
+    $rowctxmenu add command -label "Diff selected -> this" \
+       -command {diffvssel 1}
+    $rowctxmenu add command -label "Make patch" -command mkpatch
+    $rowctxmenu add command -label "Create tag" -command mktag
+    $rowctxmenu add command -label "Write commit to file" -command writecommit
+    $rowctxmenu add command -label "Create new branch" -command mkbranch
+    $rowctxmenu add command -label "Cherry-pick this commit" \
+       -command cherrypick
+    $rowctxmenu add command -label "Reset HEAD branch to here" \
+       -command resethead
+
+    set fakerowmenu .fakerowmenu
+    menu $fakerowmenu -tearoff 0
+    $fakerowmenu add command -label "Diff this -> selected" \
+       -command {diffvssel 0}
+    $fakerowmenu add command -label "Diff selected -> this" \
+       -command {diffvssel 1}
+    $fakerowmenu add command -label "Make patch" -command mkpatch
+#    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
+#    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
+#    $fakerowmenu add command -label "Revert local changes" -command revertlocal
+
+    set headctxmenu .headctxmenu
+    menu $headctxmenu -tearoff 0
+    $headctxmenu add command -label "Check out this branch" \
+       -command cobranch
+    $headctxmenu add command -label "Remove this branch" \
+       -command rmbranch
+
+    global flist_menu
+    set flist_menu .flistctxmenu
+    menu $flist_menu -tearoff 0
+    $flist_menu add command -label "Highlight this too" \
+       -command {flist_hl 0}
+    $flist_menu add command -label "Highlight this only" \
+       -command {flist_hl 1}
+}
+
+# Windows sends all mouse wheel events to the current focused window, not
+# the one where the mouse hovers, so bind those events here and redirect
+# to the correct window
+proc windows_mousewheel_redirector {W X Y D} {
+    global canv canv2 canv3
+    set w [winfo containing -displayof $W $X $Y]
+    if {$w ne ""} {
+       set u [expr {$D < 0 ? 5 : -5}]
+       if {$w == $canv || $w == $canv2 || $w == $canv3} {
+           allcanvs yview scroll $u units
+       } else {
+           catch {
+               $w yview scroll $u units
+           }
+       }
+    }
+}
+
+# mouse-2 makes all windows scan vertically, but only the one
+# the cursor is in scans horizontally
+proc canvscan {op w x y} {
+    global canv canv2 canv3
+    foreach c [list $canv $canv2 $canv3] {
+       if {$c == $w} {
+           $c scan $op $x $y
+       } else {
+           $c scan $op 0 $y
+       }
+    }
+}
+
+proc scrollcanv {cscroll f0 f1} {
+    $cscroll set $f0 $f1
+    drawfrac $f0 $f1
+    flushhighlights
+}
+
+# when we make a key binding for the toplevel, make sure
+# it doesn't get triggered when that key is pressed in the
+# find string entry widget.
+proc bindkey {ev script} {
+    global entries
+    bind . $ev $script
+    set escript [bind Entry $ev]
+    if {$escript == {}} {
+       set escript [bind Entry <Key>]
+    }
+    foreach e $entries {
+       bind $e $ev "$escript; break"
+    }
+}
+
+# set the focus back to the toplevel for any click outside
+# the entry widgets
+proc click {w} {
+    global ctext entries
+    foreach e [concat $entries $ctext] {
+       if {$w == $e} return
+    }
+    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 mainfont textfont uifont tabstop
+    global stuffsaved findmergefiles maxgraphpct
+    global maxwidth showneartags showlocalchanges
+    global viewname viewfiles viewargs viewperm nextviewnum
+    global cmitmode wrapcomment datetimeformat limitdiffs
+    global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
+
+    if {$stuffsaved} return
+    if {![winfo viewable .]} return
+    catch {
+       set f [open "~/.gitk-new" w]
+       puts $f [list set mainfont $mainfont]
+       puts $f [list set textfont $textfont]
+       puts $f [list set uifont $uifont]
+       puts $f [list set tabstop $tabstop]
+       puts $f [list set findmergefiles $findmergefiles]
+       puts $f [list set maxgraphpct $maxgraphpct]
+       puts $f [list set maxwidth $maxwidth]
+       puts $f [list set cmitmode $cmitmode]
+       puts $f [list set wrapcomment $wrapcomment]
+       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]
+       puts $f [list set diffcolors $diffcolors]
+       puts $f [list set diffcontext $diffcontext]
+       puts $f [list set selectbgcolor $selectbgcolor]
+
+       puts $f "set geometry(main) [wm geometry .]"
+       puts $f "set geometry(topwidth) [winfo width .tf]"
+       puts $f "set geometry(topheight) [winfo height .tf]"
+        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
+        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       puts $f "set geometry(botwidth) [winfo width .bleft]"
+       puts $f "set geometry(botheight) [winfo height .bleft]"
+
+       puts -nonewline $f "set permviews {"
+       for {set v 0} {$v < $nextviewnum} {incr v} {
+           if {$viewperm($v)} {
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
+           }
+       }
+       puts $f "}"
+       close $f
+       file rename -force "~/.gitk-new" "~/.gitk"
+    }
+    set stuffsaved 1
+}
+
+proc resizeclistpanes {win w} {
+    global oldwidth
+    if {[info exists oldwidth($win)]} {
+       set s0 [$win sash coord 0]
+       set s1 [$win sash coord 1]
+       if {$w < 60} {
+           set sash0 [expr {int($w/2 - 2)}]
+           set sash1 [expr {int($w*5/6 - 2)}]
+       } else {
+           set factor [expr {1.0 * $w / $oldwidth($win)}]
+           set sash0 [expr {int($factor * [lindex $s0 0])}]
+           set sash1 [expr {int($factor * [lindex $s1 0])}]
+           if {$sash0 < 30} {
+               set sash0 30
+           }
+           if {$sash1 < $sash0 + 20} {
+               set sash1 [expr {$sash0 + 20}]
+           }
+           if {$sash1 > $w - 10} {
+               set sash1 [expr {$w - 10}]
+               if {$sash0 > $sash1 - 20} {
+                   set sash0 [expr {$sash1 - 20}]
+               }
+           }
+       }
+       $win sash place 0 $sash0 [lindex $s0 1]
+       $win sash place 1 $sash1 [lindex $s1 1]
+    }
+    set oldwidth($win) $w
+}
+
+proc resizecdetpanes {win w} {
+    global oldwidth
+    if {[info exists oldwidth($win)]} {
+       set s0 [$win sash coord 0]
+       if {$w < 60} {
+           set sash0 [expr {int($w*3/4 - 2)}]
+       } else {
+           set factor [expr {1.0 * $w / $oldwidth($win)}]
+           set sash0 [expr {int($factor * [lindex $s0 0])}]
+           if {$sash0 < 45} {
+               set sash0 45
+           }
+           if {$sash0 > $w - 15} {
+               set sash0 [expr {$w - 15}]
+           }
+       }
+       $win sash place 0 $sash0 [lindex $s0 1]
+    }
+    set oldwidth($win) $w
+}
+
+proc allcanvs args {
+    global canv canv2 canv3
+    eval $canv $args
+    eval $canv2 $args
+    eval $canv3 $args
+}
+
+proc bindall {event action} {
+    global canv canv2 canv3
+    bind $canv $event $action
+    bind $canv2 $event $action
+    bind $canv3 $event $action
+}
+
+proc about {} {
+    global uifont
+    set w .about
+    if {[winfo exists $w]} {
+       raise $w
+       return
+    }
+    toplevel $w
+    wm title $w "About gitk"
+    message $w.m -text {
+Gitk - a commit viewer for git
+
+Copyright Â© 2005-2006 Paul Mackerras
+
+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
+    button $w.ok -text Close -command "destroy $w" -default active
+    pack $w.ok -side bottom
+    $w.ok configure -font uifont
+    bind $w <Visibility> "focus $w.ok"
+    bind $w <Key-Escape> "destroy $w"
+    bind $w <Key-Return> "destroy $w"
+}
+
+proc keys {} {
+    global uifont
+    set w .keys
+    if {[winfo exists $w]} {
+       raise $w
+       return
+    }
+    if {[tk windowingsystem] eq {aqua}} {
+       set M1T Cmd
+    } else {
+       set M1T Ctrl
+    }
+    toplevel $w
+    wm title $w "Gitk key bindings"
+    message $w.m -text "
+Gitk key bindings:
+
+<$M1T-Q>               Quit
+<Home>         Move to first commit
+<End>          Move to last commit
+<Up>, p, i     Move up one commit
+<Down>, n, k   Move down one commit
+<Left>, z, j   Go back in history list
+<Right>, x, l  Go forward in history list
+<PageUp>       Move up one page in commit list
+<PageDown>     Move down one page in commit list
+<$M1T-Home>    Scroll to top of commit list
+<$M1T-End>     Scroll to bottom of commit list
+<$M1T-Up>      Scroll commit list up one line
+<$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>     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
+u              Scroll diff view up 18 lines
+d              Scroll diff view down 18 lines
+<$M1T-F>               Find
+<$M1T-G>               Move to next find hit
+<Return>       Move to next find hit
+/              Move to next find hit, or redo find
+?              Move to previous find hit
+f              Scroll diff view to next file
+<$M1T-S>               Search for next hit in diff view
+<$M1T-R>               Search for previous hit in diff view
+<$M1T-KP+>     Increase font size
+<$M1T-plus>    Increase font size
+<$M1T-KP->     Decrease font size
+<$M1T-minus>   Decrease font size
+<F5>           Update
+" \
+           -justify left -bg white -border 2 -relief groove
+    pack $w.m -side top -fill both -padx 2 -pady 2
+    $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
+    bind $w <Visibility> "focus $w.ok"
+    bind $w <Key-Escape> "destroy $w"
+    bind $w <Key-Return> "destroy $w"
+}
+
+# Procedures for manipulating the file list window at the
+# bottom right of the overall window.
+
+proc treeview {w l openlevs} {
+    global treecontents treediropen treeheight treeparent treeindex
+
+    set ix 0
+    set treeindex() 0
+    set lev 0
+    set prefix {}
+    set prefixend -1
+    set prefendstack {}
+    set htstack {}
+    set ht 0
+    set treecontents() {}
+    $w conf -state normal
+    foreach f $l {
+       while {[string range $f 0 $prefixend] ne $prefix} {
+           if {$lev <= $openlevs} {
+               $w mark set e:$treeindex($prefix) "end -1c"
+               $w mark gravity e:$treeindex($prefix) left
+           }
+           set treeheight($prefix) $ht
+           incr ht [lindex $htstack end]
+           set htstack [lreplace $htstack end end]
+           set prefixend [lindex $prefendstack end]
+           set prefendstack [lreplace $prefendstack end end]
+           set prefix [string range $prefix 0 $prefixend]
+           incr lev -1
+       }
+       set tail [string range $f [expr {$prefixend+1}] end]
+       while {[set slash [string first "/" $tail]] >= 0} {
+           lappend htstack $ht
+           set ht 0
+           lappend prefendstack $prefixend
+           incr prefixend [expr {$slash + 1}]
+           set d [string range $tail 0 $slash]
+           lappend treecontents($prefix) $d
+           set oldprefix $prefix
+           append prefix $d
+           set treecontents($prefix) {}
+           set treeindex($prefix) [incr ix]
+           set treeparent($prefix) $oldprefix
+           set tail [string range $tail [expr {$slash+1}] end]
+           if {$lev <= $openlevs} {
+               set ht 1
+               set treediropen($prefix) [expr {$lev < $openlevs}]
+               set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
+               $w mark set d:$ix "end -1c"
+               $w mark gravity d:$ix left
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w image create end -align center -image $bm -padx 1 \
+                   -name a:$ix
+               $w insert end $d [highlight_tag $prefix]
+               $w mark set s:$ix "end -1c"
+               $w mark gravity s:$ix left
+           }
+           incr lev
+       }
+       if {$tail ne {}} {
+           if {$lev <= $openlevs} {
+               incr ht
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w insert end $tail [highlight_tag $f]
+           }
+           lappend treecontents($prefix) $tail
+       }
+    }
+    while {$htstack ne {}} {
+       set treeheight($prefix) $ht
+       incr ht [lindex $htstack end]
+       set htstack [lreplace $htstack end end]
+       set prefixend [lindex $prefendstack end]
+       set prefendstack [lreplace $prefendstack end end]
+       set prefix [string range $prefix 0 $prefixend]
+    }
+    $w conf -state disabled
+}
+
+proc linetoelt {l} {
+    global treeheight treecontents
+
+    set y 2
+    set prefix {}
+    while {1} {
+       foreach e $treecontents($prefix) {
+           if {$y == $l} {
+               return "$prefix$e"
+           }
+           set n 1
+           if {[string index $e end] eq "/"} {
+               set n $treeheight($prefix$e)
+               if {$y + $n > $l} {
+                   append prefix $e
+                   incr y
+                   break
+               }
+           }
+           incr y $n
+       }
+    }
+}
+
+proc highlight_tree {y prefix} {
+    global treeheight treecontents cflist
+
+    foreach e $treecontents($prefix) {
+       set path $prefix$e
+       if {[highlight_tag $path] ne {}} {
+           $cflist tag add bold $y.0 "$y.0 lineend"
+       }
+       incr y
+       if {[string index $e end] eq "/" && $treeheight($path) > 1} {
+           set y [highlight_tree $y $path]
+       }
+    }
+    return $y
+}
+
+proc treeclosedir {w dir} {
+    global treediropen treeheight treeparent treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w delete s:$ix e:$ix
+    set treediropen($dir) 0
+    $w image configure a:$ix -image tri-rt
+    $w conf -state disabled
+    set n [expr {1 - $treeheight($dir)}]
+    while {$dir ne {}} {
+       incr treeheight($dir) $n
+       set dir $treeparent($dir)
+    }
+}
+
+proc treeopendir {w dir} {
+    global treediropen treeheight treeparent treecontents treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w image configure a:$ix -image tri-dn
+    $w mark set e:$ix s:$ix
+    $w mark gravity e:$ix right
+    set lev 0
+    set str "\n"
+    set n [llength $treecontents($dir)]
+    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
+       incr lev
+       append str "\t"
+       incr treeheight($x) $n
+    }
+    foreach e $treecontents($dir) {
+       set de $dir$e
+       if {[string index $e end] eq "/"} {
+           set iy $treeindex($de)
+           $w mark set d:$iy e:$ix
+           $w mark gravity d:$iy left
+           $w insert e:$ix $str
+           set treediropen($de) 0
+           $w image create e:$ix -align center -image tri-rt -padx 1 \
+               -name a:$iy
+           $w insert e:$ix $e [highlight_tag $de]
+           $w mark set s:$iy e:$ix
+           $w mark gravity s:$iy left
+           set treeheight($de) 1
+       } else {
+           $w insert e:$ix $str
+           $w insert e:$ix $e [highlight_tag $de]
+       }
+    }
+    $w mark gravity e:$ix left
+    $w conf -state disabled
+    set treediropen($dir) 1
+    set top [lindex [split [$w index @0,0] .] 0]
+    set ht [$w cget -height]
+    set l [lindex [split [$w index s:$ix] .] 0]
+    if {$l < $top} {
+       $w yview $l.0
+    } elseif {$l + $n + 1 > $top + $ht} {
+       set top [expr {$l + $n + 2 - $ht}]
+       if {$l < $top} {
+           set top $l
+       }
+       $w yview $top.0
+    }
+}
+
+proc treeclick {w x y} {
+    global treediropen cmitmode ctext cflist cflist_top
+
+    if {$cmitmode ne "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+       return
+    }
+    set e [linetoelt $l]
+    if {[string index $e end] ne "/"} {
+       showfile $e
+    } elseif {$treediropen($e)} {
+       treeclosedir $w $e
+    } else {
+       treeopendir $w $e
+    }
+}
+
+proc setfilelist {id} {
+    global treefilelist cflist
+
+    treeview $cflist $treefilelist($id) 0
+}
+
+image create bitmap tri-rt -background black -foreground blue -data {
+    #define tri-rt_width 13
+    #define tri-rt_height 13
+    static unsigned char tri-rt_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
+       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-rt-mask_width 13
+    #define tri-rt-mask_height 13
+    static unsigned char tri-rt-mask_bits[] = {
+       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
+       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
+       0x08, 0x00};
+}
+image create bitmap tri-dn -background black -foreground blue -data {
+    #define tri-dn_width 13
+    #define tri-dn_height 13
+    static unsigned char tri-dn_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
+       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-dn-mask_width 13
+    #define tri-dn-mask_height 13
+    static unsigned char tri-dn-mask_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
+       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+}
+
+image create bitmap reficon-T -background black -foreground yellow -data {
+    #define tagicon_width 13
+    #define tagicon_height 9
+    static unsigned char tagicon_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0xf0, 0x07, 0xf8, 0x07,
+       0xfc, 0x07, 0xf8, 0x07, 0xf0, 0x07, 0x00, 0x00, 0x00, 0x00};
+} -maskdata {
+    #define tagicon-mask_width 13
+    #define tagicon-mask_height 9
+    static unsigned char tagicon-mask_bits[] = {
+       0x00, 0x00, 0xf0, 0x0f, 0xf8, 0x0f, 0xfc, 0x0f,
+       0xfe, 0x0f, 0xfc, 0x0f, 0xf8, 0x0f, 0xf0, 0x0f, 0x00, 0x00};
+}
+set rectdata {
+    #define headicon_width 13
+    #define headicon_height 9
+    static unsigned char headicon_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0xf8, 0x07, 0xf8, 0x07,
+       0xf8, 0x07, 0xf8, 0x07, 0xf8, 0x07, 0x00, 0x00, 0x00, 0x00};
+}
+set rectmask {
+    #define headicon-mask_width 13
+    #define headicon-mask_height 9
+    static unsigned char headicon-mask_bits[] = {
+       0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f,
+       0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00};
+}
+image create bitmap reficon-H -background black -foreground green \
+    -data $rectdata -maskdata $rectmask
+image create bitmap reficon-o -background black -foreground "#ddddff" \
+    -data $rectdata -maskdata $rectmask
+
+proc init_flist {first} {
+    global cflist cflist_top selectedline difffilestart
+
+    $cflist conf -state normal
+    $cflist delete 0.0 end
+    if {$first ne {}} {
+       $cflist insert end $first
+       set cflist_top 1
+       $cflist tag add highlight 1.0 "1.0 lineend"
+    } else {
+       catch {unset cflist_top}
+    }
+    $cflist conf -state disabled
+    set difffilestart {}
+}
+
+proc highlight_tag {f} {
+    global highlight_paths
+
+    foreach p $highlight_paths {
+       if {[string match $p $f]} {
+           return "bold"
+       }
+    }
+    return {}
+}
+
+proc highlight_filelist {} {
+    global cmitmode cflist
+
+    $cflist conf -state normal
+    if {$cmitmode ne "tree"} {
+       set end [lindex [split [$cflist index end] .] 0]
+       for {set l 2} {$l < $end} {incr l} {
+           set line [$cflist get $l.0 "$l.0 lineend"]
+           if {[highlight_tag $line] ne {}} {
+               $cflist tag add bold $l.0 "$l.0 lineend"
+           }
+       }
+    } else {
+       highlight_tree 2 {}
+    }
+    $cflist conf -state disabled
+}
+
+proc unhighlight_filelist {} {
+    global cflist
+
+    $cflist conf -state normal
+    $cflist tag remove bold 1.0 end
+    $cflist conf -state disabled
+}
+
+proc add_flist {fl} {
+    global cflist
+
+    $cflist conf -state normal
+    foreach f $fl {
+       $cflist insert end "\n"
+       $cflist insert end $f [highlight_tag $f]
+    }
+    $cflist conf -state disabled
+}
+
+proc sel_flist {w x y} {
+    global ctext difffilestart cflist cflist_top cmitmode
+
+    if {$cmitmode eq "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+    } else {
+       catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
+    }
+}
+
+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"} {
+       set e [linetoelt $l]
+       if {[string index $e end] eq "/"} return
+    } else {
+       set e [lindex $treediffs($diffids) [expr {$l-2}]]
+    }
+    set flist_menu_file $e
+    tk_popup $flist_menu $X $Y
+}
+
+proc flist_hl {only} {
+    global flist_menu_file findstring gdttype
+
+    set x [shellquote $flist_menu_file]
+    if {$only || $findstring eq {} || $gdttype ne "touching paths:"} {
+       set findstring $x
+    } else {
+       append findstring " " $x
+    }
+    set gdttype "touching paths:"
+}
+
+# Functions for adding and removing shell-type quoting
+
+proc shellquote {str} {
+    if {![string match "*\['\"\\ \t]*" $str]} {
+       return $str
+    }
+    if {![string match "*\['\"\\]*" $str]} {
+       return "\"$str\""
+    }
+    if {![string match "*'*" $str]} {
+       return "'$str'"
+    }
+    return "\"[string map {\" \\\" \\ \\\\} $str]\""
+}
+
+proc shellarglist {l} {
+    set str {}
+    foreach a $l {
+       if {$str ne {}} {
+           append str " "
+       }
+       append str [shellquote $a]
+    }
+    return $str
+}
+
+proc shelldequote {str} {
+    set ret {}
+    set used -1
+    while {1} {
+       incr used
+       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
+           append ret [string range $str $used end]
+           set used [string length $str]
+           break
+       }
+       set first [lindex $first 0]
+       set ch [string index $str $first]
+       if {$first > $used} {
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+       }
+       if {$ch eq " " || $ch eq "\t"} break
+       incr used
+       if {$ch eq "'"} {
+           set first [string first "'" $str $used]
+           if {$first < 0} {
+               error "unmatched single-quote"
+           }
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+           continue
+       }
+       if {$ch eq "\\"} {
+           if {$used >= [string length $str]} {
+               error "trailing backslash"
+           }
+           append ret [string index $str $used]
+           continue
+       }
+       # here ch == "\""
+       while {1} {
+           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
+               error "unmatched double-quote"
+           }
+           set first [lindex $first 0]
+           set ch [string index $str $first]
+           if {$first > $used} {
+               append ret [string range $str $used [expr {$first - 1}]]
+               set used $first
+           }
+           if {$ch eq "\""} break
+           incr used
+           append ret [string index $str $used]
+           incr used
+       }
+    }
+    return [list $used $ret]
+}
+
+proc shellsplit {str} {
+    set l {}
+    while {1} {
+       set str [string trimleft $str]
+       if {$str eq {}} break
+       set dq [shelldequote $str]
+       set n [lindex $dq 0]
+       set word [lindex $dq 1]
+       set str [string range $str $n end]
+       lappend l $word
+    }
+    return $l
+}
+
+# Code to implement multiple views
+
+proc newview {ishighlight} {
+    global nextviewnum newviewname newviewperm uifont newishighlight
+    global newviewargs revtreeargs
+
+    set newishighlight $ishighlight
+    set top .gitkview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewperm($nextviewnum) 0
+    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
+    vieweditor $top $nextviewnum "Gitk view definition"
+}
+
+proc editview {} {
+    global curview
+    global viewname viewperm newviewname newviewperm
+    global viewargs newviewargs
+
+    set top .gitkvedit-$curview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($curview) $viewname($curview)
+    set newviewperm($curview) $viewperm($curview)
+    set newviewargs($curview) [shellarglist $viewargs($curview)]
+    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
+}
+
+proc vieweditor {top n title} {
+    global newviewname newviewperm viewfiles
+    global uifont
+
+    toplevel $top
+    wm title $top $title
+    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
+    grid $top.perm - -pady 5 -sticky w
+    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
+    grid $top.args - -sticky ew -padx 5
+    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
+    if {[info exists viewfiles($n)]} {
+       foreach f $viewfiles($n) {
+           $top.t insert end $f
+           $top.t insert end "\n"
+       }
+       $top.t delete {end - 1c} end
+       $top.t mark set insert 0.0
+    }
+    grid $top.t - -sticky ew -padx 5
+    frame $top.buts
+    button $top.buts.ok -text "OK" -command [list newviewok $top $n] \
+       -font uifont
+    button $top.buts.can -text "Cancel" -command [list destroy $top] \
+       -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
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.t
+}
+
+proc doviewmenu {m first cmd op argv} {
+    set nmenu [$m index end]
+    for {set i $first} {$i <= $nmenu} {incr i} {
+       if {[$m entrycget $i -command] eq $cmd} {
+           eval $m $op $i $argv
+           break
+       }
+    }
+}
+
+proc allviewmenus {n op args} {
+    # global viewhlmenu
+
+    doviewmenu .bar.view 5 [list showview $n] $op $args
+    # doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
+}
+
+proc newviewok {top n} {
+    global nextviewnum newviewperm newviewname newishighlight
+    global viewname viewfiles viewperm selectedview curview
+    global viewargs newviewargs viewhlmenu
+
+    if {[catch {
+       set newargs [shellsplit $newviewargs($n)]
+    } err]} {
+       error_popup "Error in commit selection arguments: $err"
+       wm raise $top
+       focus $top
+       return
+    }
+    set files {}
+    foreach f [split [$top.t get 0.0 end] "\n"] {
+       set ft [string trim $f]
+       if {$ft ne {}} {
+           lappend files $ft
+       }
+    }
+    if {![info exists viewfiles($n)]} {
+       # creating a new view
+       incr nextviewnum
+       set viewname($n) $newviewname($n)
+       set viewperm($n) $newviewperm($n)
+       set viewfiles($n) $files
+       set viewargs($n) $newargs
+       addviewmenu $n
+       if {!$newishighlight} {
+           run showview $n
+       } else {
+           run addvhighlight $n
+       }
+    } else {
+       # editing an existing view
+       set viewperm($n) $newviewperm($n)
+       if {$newviewname($n) ne $viewname($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)]
+       }
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
+           set viewfiles($n) $files
+           set viewargs($n) $newargs
+           if {$curview == $n} {
+               run updatecommits
+           }
+       }
+    }
+    catch {destroy $top}
+}
+
+proc delview {} {
+    global curview viewdata viewperm hlview selectedhlview
+
+    if {$curview == 0} return
+    if {[info exists hlview] && $hlview == $curview} {
+       set selectedhlview None
+       unset hlview
+    }
+    allviewmenus $curview delete
+    set viewdata($curview) {}
+    set viewperm($curview) 0
+    showview 0
+}
+
+proc addviewmenu {n} {
+    global viewname viewhlmenu
+
+    .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
+}
+
+proc flatten {var} {
+    global $var
+
+    set ret {}
+    foreach i [array names $var] {
+       lappend ret $i [set $var\($i\)]
+    }
+    return $ret
+}
+
+proc unflatten {var l} {
+    global $var
+
+    catch {unset $var}
+    foreach {i v} $l {
+       set $var\($i\) $v
+    }
+}
+
+proc showview {n} {
+    global curview viewdata viewfiles
+    global displayorder parentlist rowidlist rowisopt rowfinal
+    global colormap rowtextx commitrow nextcolor canvxmax
+    global numcommits commitlisted
+    global selectedline currentid canv canvy0
+    global treediffs
+    global pending_select phase
+    global commitidx
+    global commfd
+    global selectedview selectfirst
+    global vparentlist vdisporder vcmitlisted
+    global hlview selectedhlview commitinterest
+
+    if {$n == $curview} return
+    set selid {}
+    if {[info exists selectedline]} {
+       set selid $currentid
+       set y [yc $selectedline]
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set span [$canv yview]
+       set ytop [expr {[lindex $span 0] * $ymax}]
+       set ybot [expr {[lindex $span 1] * $ymax}]
+       if {$ytop < $y && $y < $ybot} {
+           set yscreen [expr {$y - $ytop}]
+       } else {
+           set yscreen [expr {($ybot - $ytop) / 2}]
+       }
+    } elseif {[info exists pending_select]} {
+       set selid $pending_select
+       unset pending_select
+    }
+    unselectline
+    normalline
+    if {$curview >= 0} {
+       set vparentlist($curview) $parentlist
+       set vdisporder($curview) $displayorder
+       set vcmitlisted($curview) $commitlisted
+       if {$phase ne {} ||
+           ![info exists viewdata($curview)] ||
+           [lindex $viewdata($curview) 0] ne {}} {
+           set viewdata($curview) \
+               [list $phase $rowidlist $rowisopt $rowfinal]
+       }
+    }
+    catch {unset treediffs}
+    clear_display
+    if {[info exists hlview] && $hlview == $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
+       }
+       getcommits
+       return
+    }
+
+    set v $viewdata($n)
+    set phase [lindex $v 0]
+    set displayorder $vdisporder($n)
+    set parentlist $vparentlist($n)
+    set commitlisted $vcmitlisted($n)
+    set rowidlist [lindex $v 1]
+    set rowisopt [lindex $v 2]
+    set rowfinal [lindex $v 3]
+    set numcommits $commitidx($n)
+
+    catch {unset colormap}
+    catch {unset rowtextx}
+    set nextcolor 0
+    set canvxmax [$canv cget -width]
+    set curview $n
+    set row 0
+    setcanvscroll
+    set yf 0
+    set row {}
+    set selectfirst 0
+    if {$selid ne {} && [info exists commitrow($n,$selid)]} {
+       set row $commitrow($n,$selid)
+       # try to get the selected row in the same position on the screen
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set ytop [expr {[yc $row] - $yscreen}]
+       if {$ytop < 0} {
+           set ytop 0
+       }
+       set yf [expr {$ytop * 1.0 / $ymax}]
+    }
+    allcanvs yview moveto $yf
+    drawvisible
+    if {$row ne {}} {
+       selectline $row 0
+    } elseif {$selid ne {}} {
+       set pending_select $selid
+    } else {
+       set row [first_real_row]
+       if {$row < $numcommits} {
+           selectline $row 0
+       } else {
+           set selectfirst 1
+       }
+    }
+    if {$phase ne {}} {
+       if {$phase eq "getcommits"} {
+           show_status "Reading commits..."
+       }
+       run chewcommits $n
+    } elseif {$numcommits == 0} {
+       show_status "No commits selected"
+    }
+}
+
+# Stuff relating to the highlighting facility
+
+proc ishighlighted {row} {
+    global vhighlights fhighlights nhighlights rhighlights
+
+    if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
+       return $nhighlights($row)
+    }
+    if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
+       return $vhighlights($row)
+    }
+    if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
+       return $fhighlights($row)
+    }
+    if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
+       return $rhighlights($row)
+    }
+    return 0
+}
+
+proc bolden {row font} {
+    global canv linehtag selectedline boldrows
+
+    lappend boldrows $row
+    $canv itemconf $linehtag($row) -font $font
+    if {[info exists selectedline] && $row == $selectedline} {
+       $canv delete secsel
+       set t [eval $canv create rect [$canv bbox $linehtag($row)] \
+                  -outline {{}} -tags secsel \
+                  -fill [$canv cget -selectbackground]]
+       $canv lower $t
+    }
+}
+
+proc bolden_name {row font} {
+    global canv2 linentag selectedline boldnamerows
+
+    lappend boldnamerows $row
+    $canv2 itemconf $linentag($row) -font $font
+    if {[info exists selectedline] && $row == $selectedline} {
+       $canv2 delete secsel
+       set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
+                  -outline {{}} -tags secsel \
+                  -fill [$canv2 cget -selectbackground]]
+       $canv2 lower $t
+    }
+}
+
+proc unbolden {} {
+    global boldrows
+
+    set stillbold {}
+    foreach row $boldrows {
+       if {![ishighlighted $row]} {
+           bolden $row mainfont
+       } else {
+           lappend stillbold $row
+       }
+    }
+    set boldrows $stillbold
+}
+
+proc addvhighlight {n} {
+    global hlview curview viewdata vhl_done vhighlights commitidx
+
+    if {[info exists hlview]} {
+       delvhighlight
+    }
+    set hlview $n
+    if {$n != $curview && ![info exists viewdata($n)]} {
+       set viewdata($n) [list getcommits {{}} 0 0 0]
+       set vparentlist($n) {}
+       set vdisporder($n) {}
+       set vcmitlisted($n) {}
+       start_rev_list $n
+    }
+    set vhl_done $commitidx($hlview)
+    if {$vhl_done > 0} {
+       drawvisible
+    }
+}
+
+proc delvhighlight {} {
+    global hlview vhighlights
+
+    if {![info exists hlview]} return
+    unset hlview
+    catch {unset vhighlights}
+    unbolden
+}
+
+proc vhighlightmore {} {
+    global hlview vhl_done commitidx vhighlights
+    global displayorder vdisporder curview
+
+    set max $commitidx($hlview)
+    if {$hlview == $curview} {
+       set disp $displayorder
+    } else {
+       set disp $vdisporder($hlview)
+    }
+    set vr [visiblerows]
+    set r0 [lindex $vr 0]
+    set r1 [lindex $vr 1]
+    for {set i $vhl_done} {$i < $max} {incr i} {
+       set id [lindex $disp $i]
+       if {[info exists commitrow($curview,$id)]} {
+           set row $commitrow($curview,$id)
+           if {$r0 <= $row && $row <= $r1} {
+               if {![highlighted $row]} {
+                   bolden $row mainfontbold
+               }
+               set vhighlights($row) 1
+           }
+       }
+    }
+    set vhl_done $max
+}
+
+proc askvhighlight {row id} {
+    global hlview vhighlights commitrow iddrawn
+
+    if {[info exists commitrow($hlview,$id)]} {
+       if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
+           bolden $row mainfontbold
+       }
+       set vhighlights($row) 1
+    } else {
+       set vhighlights($row) 0
+    }
+}
+
+proc hfiles_change {} {
+    global highlight_files filehighlight fhighlights fh_serial
+    global highlight_paths gdttype
+
+    if {[info exists filehighlight]} {
+       # delete previous highlights
+       catch {close $filehighlight}
+       unset filehighlight
+       catch {unset fhighlights}
+       unbolden
+       unhighlight_filelist
+    }
+    set highlight_paths {}
+    after cancel do_file_hl $fh_serial
+    incr fh_serial
+    if {$highlight_files ne {}} {
+       after 300 do_file_hl $fh_serial
+    }
+}
+
+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 {
+       set ee [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} $e]
+       if {[string index $ee end] eq "/"} {
+           lappend ret "$ee*"
+       } else {
+           lappend ret $ee
+           lappend ret "$ee/*"
+       }
+    }
+    return $ret
+}
+
+proc do_file_hl {serial} {
+    global highlight_files filehighlight highlight_paths gdttype fhl_list
+
+    if {$gdttype eq "touching paths:"} {
+       if {[catch {set paths [shellsplit $highlight_files]}]} return
+       set highlight_paths [makepatterns $paths]
+       highlight_filelist
+       set gdtargs [concat -- $paths]
+    } 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+]
+    fconfigure $filehighlight -blocking 0
+    filerun $filehighlight readfhighlight
+    set fhl_list {}
+    drawvisible
+    flushhighlights
+}
+
+proc flushhighlights {} {
+    global filehighlight fhl_list
+
+    if {[info exists filehighlight]} {
+       lappend fhl_list {}
+       puts $filehighlight ""
+       flush $filehighlight
+    }
+}
+
+proc askfilehighlight {row id} {
+    global filehighlight fhighlights fhl_list
+
+    lappend fhl_list $id
+    set fhighlights($row) -1
+    puts $filehighlight $id
+}
+
+proc readfhighlight {} {
+    global filehighlight fhighlights commitrow curview iddrawn
+    global fhl_list find_dirn
+
+    if {![info exists filehighlight]} {
+       return 0
+    }
+    set nr 0
+    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
+       set line [string trim $line]
+       set i [lsearch -exact $fhl_list $line]
+       if {$i < 0} continue
+       for {set j 0} {$j < $i} {incr j} {
+           set id [lindex $fhl_list $j]
+           if {[info exists commitrow($curview,$id)]} {
+               set fhighlights($commitrow($curview,$id)) 0
+           }
+       }
+       set fhl_list [lrange $fhl_list [expr {$i+1}] end]
+       if {$line eq {}} continue
+       if {![info exists commitrow($curview,$line)]} continue
+       set row $commitrow($curview,$line)
+       if {[info exists iddrawn($line)] && ![ishighlighted $row]} {
+           bolden $row mainfontbold
+       }
+       set fhighlights($row) 1
+    }
+    if {[eof $filehighlight]} {
+       # strange...
+       puts "oops, git diff-tree died"
+       catch {close $filehighlight}
+       unset filehighlight
+       return 0
+    }
+    if {[info exists find_dirn]} {
+       run findmore
+    }
+    return 1
+}
+
+proc doesmatch {f} {
+    global findtype findpattern
+
+    if {$findtype eq "Regexp"} {
+       return [regexp $findpattern $f]
+    } elseif {$findtype eq "IgnCase"} {
+       return [string match -nocase $findpattern $f]
+    } else {
+       return [string match $findpattern $f]
+    }
+}
+
+proc askfindhighlight {row id} {
+    global nhighlights commitinfo iddrawn
+    global findloc
+    global markingmatches
+
+    if {![info exists commitinfo($id)]} {
+       getcommit $id
+    }
+    set info $commitinfo($id)
+    set isbold 0
+    set fldtypes {Headline Author Date Committer CDate Comments}
+    foreach f $info ty $fldtypes {
+       if {($findloc eq "All fields" || $findloc eq $ty) &&
+           [doesmatch $f]} {
+           if {$ty eq "Author"} {
+               set isbold 2
+               break
+           }
+           set isbold 1
+       }
+    }
+    if {$isbold && [info exists iddrawn($id)]} {
+       if {![ishighlighted $row]} {
+           bolden $row mainfontbold
+           if {$isbold > 1} {
+               bolden_name $row mainfontbold
+           }
+       }
+       if {$markingmatches} {
+           markrowmatches $row $id
+       }
+    }
+    set nhighlights($row) $isbold
+}
+
+proc markrowmatches {row id} {
+    global canv canv2 linehtag linentag commitinfo findloc
+
+    set headline [lindex $commitinfo($id) 0]
+    set author [lindex $commitinfo($id) 1]
+    $canv delete match$row
+    $canv2 delete match$row
+    if {$findloc eq "All fields" || $findloc eq "Headline"} {
+       set m [findmatches $headline]
+       if {$m ne {}} {
+           markmatches $canv $row $headline $linehtag($row) $m \
+               [$canv itemcget $linehtag($row) -font] $row
+       }
+    }
+    if {$findloc eq "All fields" || $findloc eq "Author"} {
+       set m [findmatches $author]
+       if {$m ne {}} {
+           markmatches $canv2 $row $author $linentag($row) $m \
+               [$canv2 itemcget $linentag($row) -font] $row
+       }
+    }
+}
+
+proc vrel_change {name ix op} {
+    global highlight_related
+
+    rhighlight_none
+    if {$highlight_related ne "None"} {
+       run drawvisible
+    }
+}
+
+# prepare for testing whether commits are descendents or ancestors of a
+proc rhighlight_sel {a} {
+    global descendent desc_todo ancestor anc_todo
+    global highlight_related rhighlights
+
+    catch {unset descendent}
+    set desc_todo [list $a]
+    catch {unset ancestor}
+    set anc_todo [list $a]
+    if {$highlight_related ne "None"} {
+       rhighlight_none
+       run drawvisible
+    }
+}
+
+proc rhighlight_none {} {
+    global rhighlights
+
+    catch {unset rhighlights}
+    unbolden
+}
+
+proc is_descendent {a} {
+    global curview children commitrow descendent desc_todo
+
+    set v $curview
+    set la $commitrow($v,$a)
+    set todo $desc_todo
+    set leftover {}
+    set done 0
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set do [lindex $todo $i]
+       if {$commitrow($v,$do) < $la} {
+           lappend leftover $do
+           continue
+       }
+       foreach nk $children($v,$do) {
+           if {![info exists descendent($nk)]} {
+               set descendent($nk) 1
+               lappend todo $nk
+               if {$nk eq $a} {
+                   set done 1
+               }
+           }
+       }
+       if {$done} {
+           set desc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
+           return
+       }
+    }
+    set descendent($a) 0
+    set desc_todo $leftover
+}
+
+proc is_ancestor {a} {
+    global curview parentlist commitrow ancestor anc_todo
+
+    set v $curview
+    set la $commitrow($v,$a)
+    set todo $anc_todo
+    set leftover {}
+    set done 0
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set do [lindex $todo $i]
+       if {![info exists commitrow($v,$do)] || $commitrow($v,$do) > $la} {
+           lappend leftover $do
+           continue
+       }
+       foreach np [lindex $parentlist $commitrow($v,$do)] {
+           if {![info exists ancestor($np)]} {
+               set ancestor($np) 1
+               lappend todo $np
+               if {$np eq $a} {
+                   set done 1
+               }
+           }
+       }
+       if {$done} {
+           set anc_todo [concat $leftover [lrange $todo [expr {$i+1}] end]]
+           return
+       }
+    }
+    set ancestor($a) 0
+    set anc_todo $leftover
+}
+
+proc askrelhighlight {row id} {
+    global descendent highlight_related iddrawn rhighlights
+    global selectedline ancestor
+
+    if {![info exists selectedline]} return
+    set isbold 0
+    if {$highlight_related eq "Descendent" ||
+       $highlight_related eq "Not descendent"} {
+       if {![info exists descendent($id)]} {
+           is_descendent $id
+       }
+       if {$descendent($id) == ($highlight_related eq "Descendent")} {
+           set isbold 1
+       }
+    } elseif {$highlight_related eq "Ancestor" ||
+             $highlight_related eq "Not ancestor"} {
+       if {![info exists ancestor($id)]} {
+           is_ancestor $id
+       }
+       if {$ancestor($id) == ($highlight_related eq "Ancestor")} {
+           set isbold 1
+       }
+    }
+    if {[info exists iddrawn($id)]} {
+       if {$isbold && ![ishighlighted $row]} {
+           bolden $row mainfontbold
+       }
+    }
+    set rhighlights($row) $isbold
+}
+
+# Graph layout functions
+
+proc shortids {ids} {
+    set res {}
+    foreach id $ids {
+       if {[llength $id] > 1} {
+           lappend res [shortids $id]
+       } elseif {[regexp {^[0-9a-f]{40}$} $id]} {
+           lappend res [string range $id 0 7]
+       } else {
+           lappend res $id
+       }
+    }
+    return $res
+}
+
+proc ntimes {n o} {
+    set ret {}
+    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 $ret
+}
+
+# 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 t $ordertok($curview,$id)
+    if {$i >= [llength $idlist] ||
+       $t < $ordertok($curview,[lindex $idlist $i])} {
+       if {$i > [llength $idlist]} {
+           set i [llength $idlist]
+       }
+       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])} {}
+       }
+    }
+    return $i
+}
+
+proc initlayout {} {
+    global rowidlist rowisopt rowfinal displayorder commitlisted
+    global numcommits canvxmax canv
+    global nextcolor
+    global parentlist
+    global colormap rowtextx
+    global selectfirst
+
+    set numcommits 0
+    set displayorder {}
+    set commitlisted {}
+    set parentlist {}
+    set nextcolor 0
+    set rowidlist {}
+    set rowisopt {}
+    set rowfinal {}
+    set canvxmax [$canv cget -width]
+    catch {unset colormap}
+    catch {unset rowtextx}
+    set selectfirst 1
+}
+
+proc setcanvscroll {} {
+    global canv canv2 canv3 numcommits linespc canvxmax canvy0
+
+    set ymax [expr {$canvy0 + ($numcommits - 0.5) * $linespc + 2}]
+    $canv conf -scrollregion [list 0 0 $canvxmax $ymax]
+    $canv2 conf -scrollregion [list 0 0 0 $ymax]
+    $canv3 conf -scrollregion [list 0 0 0 $ymax]
+}
+
+proc visiblerows {} {
+    global canv numcommits linespc
+
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax == 0} return
+    set f [$canv yview]
+    set y0 [expr {int([lindex $f 0] * $ymax)}]
+    set r0 [expr {int(($y0 - 3) / $linespc) - 1}]
+    if {$r0 < 0} {
+       set r0 0
+    }
+    set y1 [expr {int([lindex $f 1] * $ymax)}]
+    set r1 [expr {int(($y1 - 3) / $linespc) + 1}]
+    if {$r1 >= $numcommits} {
+       set r1 [expr {$numcommits - 1}]
+    }
+    return [list $r0 $r1]
+}
+
+proc layoutmore {} {
+    global commitidx viewcomplete numcommits
+    global uparrowlen downarrowlen mingaplen curview
+
+    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 mainheadid displayorder selectfirst
+    global lastscrollset commitinterest
+
+    if {$numcommits == 0} {
+       global phase
+       set phase "incrdraw"
+       allcanvs delete all
+    }
+    set r0 $numcommits
+    set prev $numcommits
+    set numcommits $canshow
+    set t [clock clicks -milliseconds]
+    if {$prev < 100 || $last || $t - $lastscrollset > 500} {
+       set lastscrollset $t
+       setcanvscroll
+    }
+    set rows [visiblerows]
+    set r1 [lindex $rows 1]
+    if {$r1 >= $canshow} {
+       set r1 [expr {$canshow - 1}]
+    }
+    if {$r0 <= $r1} {
+       drawcommits $r0 $r1
+    }
+    if {[info exists pending_select] &&
+       [info exists commitrow($curview,$pending_select)] &&
+       $commitrow($curview,$pending_select) < $numcommits} {
+       selectline $commitrow($curview,$pending_select) 1
+    }
+    if {$selectfirst} {
+       if {[info exists selectedline] || [info exists pending_select]} {
+           set selectfirst 0
+       } else {
+           set l [first_real_row]
+           selectline $l 1
+           set selectfirst 0
+       }
+    }
+}
+
+proc doshowlocalchanges {} {
+    global curview mainheadid phase commitrow
+
+    if {[info exists commitrow($curview,$mainheadid)] &&
+       ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+       dodiffindex
+    } elseif {$phase ne {}} {
+       lappend commitinterest($mainheadid) {}
+    }
+}
+
+proc dohidelocalchanges {} {
+    global localfrow localirow lserial
+
+    if {$localfrow >= 0} {
+       removerow $localfrow
+       set localfrow -1
+       if {$localirow > 0} {
+           incr localirow -1
+       }
+    }
+    if {$localirow >= 0} {
+       removerow $localirow
+       set localirow -1
+    }
+    incr lserial
+}
+
+# spawn off a process to do git diff-index --cached HEAD
+proc dodiffindex {} {
+    global localirow localfrow lserial showlocalchanges
+
+    if {!$showlocalchanges} return
+    incr lserial
+    set localfrow -1
+    set localirow -1
+    set fd [open "|git diff-index --cached HEAD" r]
+    fconfigure $fd -blocking 0
+    filerun $fd [list readdiffindex $fd $lserial]
+}
+
+proc readdiffindex {fd serial} {
+    global localirow commitrow mainheadid nullid2 curview
+    global commitinfo commitdata lserial
+
+    set isdiff 1
+    if {[gets $fd line] < 0} {
+       if {![eof $fd]} {
+           return 1
+       }
+       set isdiff 0
+    }
+    # we only need to see one line and we don't really care what it says...
+    close $fd
+
+    # now see if there are any local changes not checked in to the index
+    if {$serial == $lserial} {
+       set fd [open "|git diff-files" r]
+       fconfigure $fd -blocking 0
+       filerun $fd [list readdifffiles $fd $serial]
+    }
+
+    if {$isdiff && $serial == $lserial && $localirow == -1} {
+       # add the line for the changes in the index to the graph
+       set localirow $commitrow($curview,$mainheadid)
+       set hl "Local changes checked in to index but not committed"
+       set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
+       set commitdata($nullid2) "\n    $hl\n"
+       insertrow $localirow $nullid2
+    }
+    return 0
+}
+
+proc readdifffiles {fd serial} {
+    global localirow localfrow commitrow mainheadid nullid curview
+    global commitinfo commitdata lserial
+
+    set isdiff 1
+    if {[gets $fd line] < 0} {
+       if {![eof $fd]} {
+           return 1
+       }
+       set isdiff 0
+    }
+    # we only need to see one line and we don't really care what it says...
+    close $fd
+
+    if {$isdiff && $serial == $lserial && $localfrow == -1} {
+       # add the line for the local diff to the graph
+       if {$localirow >= 0} {
+           set localfrow $localirow
+           incr localirow
+       } else {
+           set localfrow $commitrow($curview,$mainheadid)
+       }
+       set hl "Local uncommitted changes, not checked in to index"
+       set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
+       set commitdata($nullid) "\n    $hl\n"
+       insertrow $localfrow $nullid
+    }
+    return 0
+}
+
+proc nextuse {id row} {
+    global commitrow curview children
+
+    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)
+           }
+       }
+    }
+    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)
+           }
+       }
+    }
+    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]
+           }
+       }
+    }
+    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]
+           }
+       }
+    }
+    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 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]
+           }
+       }
+    }
+    set idlist {}
+    foreach idx [lsort -unique $ids] {
+       lappend idlist [lindex $idx 1]
+    }
+    return $idlist
+}
+
+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}]
+}
+
+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}]
+    }
+    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 layoutrows {row endrow} {
+    global rowidlist rowisopt rowfinal displayorder
+    global uparrowlen downarrowlen maxwidth mingaplen
+    global children parentlist
+    global commitidx viewcomplete curview commitrow
+
+    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
+
+    set pad [ntimes $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 rowisopt displayorder curview children
+
+    if {$row < 1} {
+       set row 1
+    }
+    for {} {$row < $endrow} {incr row; set col 0} {
+       if {[lindex $rowisopt $row]} continue
+       set haspad 0
+       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 x0 [lsearch -exact $previdlist $id]
+           if {$x0 < 0} continue
+           set z [expr {$x0 - $col}]
+           set isarrow 0
+           set z0 {}
+           if {$ym >= 0} {
+               set xm [lsearch -exact $pprevidlist $id]
+               if {$xm >= 0} {
+                   set z0 [expr {$xm - $x0}]
+               }
+           }
+           if {$z0 eq {}} {
+               # 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
+           # or at 45 degrees.
+           if {$z < -1 || ($z < 0 && $isarrow)} {
+               # Line currently goes left too much;
+               # insert pads in the previous row, then optimize it
+               set npad [expr {-1 - $z + $isarrow}]
+               insert_pad $y0 $x0 $npad
+               if {$y0 > 0} {
+                   optimize_rows $y0 $x0 $row
+               }
+               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
+               set npad [expr {$z - 1 + $isarrow}]
+               insert_pad $row $col $npad
+               set idlist [lindex $rowidlist $row]
+               incr col $npad
+               set z [expr {$x0 - $col}]
+               set haspad 1
+           }
+           if {$z0 eq {} && !$isarrow && $ym >= 0} {
+               # this line links to its first child on row $row-2
+               set id [lindex $displayorder $ym]
+               set xc [lsearch -exact $pprevidlist $id]
+               if {$xc >= 0} {
+                   set z0 [expr {$xc - $x0}]
+               }
+           }
+           # avoid lines jigging left then immediately right
+           if {$z0 ne {} && $z < 0 && $z0 > 0} {
+               insert_pad $y0 $x0 1
+               incr x0
+               optimize_rows $y0 $x0 $row
+               set previdlist [lindex $rowidlist $y0]
+           }
+       }
+       if {!$haspad} {
+           # Find the first column that doesn't have a line going right
+           for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
+               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 kid [lindex $displayorder $y0]
+                   if {[lindex $children($curview,$id) 0] eq $kid} {
+                       # it is, work out offset to child
+                       set x0 [lsearch -exact $previdlist $kid]
+                   }
+               }
+               if {$x0 <= $col} break
+           }
+           # Insert a pad at that column as long as it has a line and
+           # isn't the last column
+           if {$x0 >= 0 && [incr col] < [llength $idlist]} {
+               set idlist [linsert $idlist $col {}]
+               lset rowidlist $row $idlist
+               changedrow $row
+           }
+       }
+    }
+}
+
+proc xc {row col} {
+    global canvx0 linespc
+    return [expr {$canvx0 + $col * $linespc}]
+}
+
+proc yc {row} {
+    global canvy0 linespc
+    return [expr {$canvy0 + $row * $linespc}]
+}
+
+proc linewidth {id} {
+    global thickerline lthickness
+
+    set wid $lthickness
+    if {[info exists thickerline] && $id eq $thickerline} {
+       set wid [expr {2 * $lthickness}]
+    }
+    return $wid
+}
+
+proc rowranges {id} {
+    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 {
+           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 $ret
+}
+
+proc drawlineseg {id row endrow arrowlow} {
+    global rowidlist displayorder iddrawn linesegs
+    global canv colormap linespc curview maxlinelen parentlist
+
+    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
+    set le [expr {$row + 1}]
+    set arrowhigh 1
+    while {1} {
+       set c [lsearch -exact [lindex $rowidlist $le] $id]
+       if {$c < 0} {
+           incr le -1
+           break
+       }
+       lappend cols $c
+       set x [lindex $displayorder $le]
+       if {$x eq $id} {
+           set arrowhigh 0
+           break
+       }
+       if {[info exists iddrawn($x)] || $le == $endrow} {
+           set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
+           if {$c >= 0} {
+               lappend cols $c
+               set arrowhigh 0
+           }
+           break
+       }
+       incr le
+    }
+    if {$le <= $row} {
+       return $row
+    }
+
+    set lines {}
+    set i 0
+    set joinhigh 0
+    if {[info exists linesegs($id)]} {
+       set lines $linesegs($id)
+       foreach li $lines {
+           set r0 [lindex $li 0]
+           if {$r0 > $row} {
+               if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
+                   set joinhigh 1
+               }
+               break
+           }
+           incr i
+       }
+    }
+    set joinlow 0
+    if {$i > 0} {
+       set li [lindex $lines [expr {$i-1}]]
+       set r1 [lindex $li 1]
+       if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
+           set joinlow 1
+       }
+    }
+
+    set x [lindex $cols [expr {$le - $row}]]
+    set xp [lindex $cols [expr {$le - 1 - $row}]]
+    set dir [expr {$xp - $x}]
+    if {$joinhigh} {
+       set ith [lindex $lines $i 2]
+       set coords [$canv coords $ith]
+       set ah [$canv itemcget $ith -arrow]
+       set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
+       set x2 [lindex $cols [expr {$le + 1 - $row}]]
+       if {$x2 ne {} && $x - $x2 == $dir} {
+           set coords [lrange $coords 0 end-2]
+       }
+    } else {
+       set coords [list [xc $le $x] [yc $le]]
+    }
+    if {$joinlow} {
+       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} {
+       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} {} {
+       set x $xp
+       set xp [lindex $cols [expr {$y - 1 - $row}]]
+       set ndir [expr {$xp - $x}]
+       if {$dir != $ndir || $xp < 0} {
+           lappend coords [xc $y $x] [yc $y]
+       }
+       set dir $ndir
+    }
+    if {!$joinlow} {
+       if {$xp < 0} {
+           # join parent line to first child
+           set ch [lindex $displayorder $row]
+           set xc [lsearch -exact [lindex $rowidlist $row] $ch]
+           if {$xc < 0} {
+               puts "oops: drawlineseg: child $ch not on row $row"
+           } 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]
+               }
+               set x $xc
+           }
+           lappend coords [xc $row $x] [yc $row]
+       } else {
+           set xn [xc $row $xp]
+           set yn [yc $row]
+           lappend coords $xn $yn
+       }
+       if {!$joinhigh} {
+           assigncolor $id
+           set t [$canv create line $coords -width [linewidth $id] \
+                      -fill $colormap($id) -tags lines.$id -arrow $arrow]
+           $canv lower $t
+           bindline $t $id
+           set lines [linsert $lines $i [list $row $le $t]]
+       } else {
+           $canv coords $ith $coords
+           if {$arrow ne $ah} {
+               $canv itemconf $ith -arrow $arrow
+           }
+           lset lines $i 0 $row
+       }
+    } else {
+       set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
+       set ndir [expr {$xo - $xp}]
+       set clow [$canv coords $itl]
+       if {$dir == $ndir} {
+           set clow [lrange $clow 2 end]
+       }
+       set coords [concat $coords $clow]
+       if {!$joinhigh} {
+           lset lines [expr {$i-1}] 1 $le
+       } else {
+           # coalesce two pieces
+           $canv delete $ith
+           set b [lindex $lines [expr {$i-1}] 0]
+           set e [lindex $lines $i 1]
+           set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
+       }
+       $canv coords $itl $coords
+       if {$arrow ne $al} {
+           $canv itemconf $itl -arrow $arrow
+       }
+    }
+
+    set linesegs($id) $lines
+    return $le
+}
+
+proc drawparentlinks {id row} {
+    global rowidlist canv colormap curview parentlist
+    global idpos linespc
+
+    set rowids [lindex $rowidlist $row]
+    set col [lsearch -exact $rowids $id]
+    if {$col < 0} return
+    set olds [lindex $parentlist $row]
+    set row2 [expr {$row + 1}]
+    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
+    foreach p $olds {
+       set i [lsearch -exact $ids $p]
+       if {$i < 0} {
+           puts "oops, parent $p of $id not in list"
+           continue
+       }
+       set x2 [xc $row2 $i]
+       if {$x2 > $rmx} {
+           set rmx $x2
+       }
+       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} {
+           # 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
+       }
+       set t [$canv create line $coords -width [linewidth $p] \
+                  -fill $colormap($p) -tags lines.$p]
+       $canv lower $t
+       bindline $t $p
+    }
+    if {$rmx > [lindex $idpos($id) 1]} {
+       lset idpos($id) 1 $rmx
+       redrawtags $id
+    }
+}
+
+proc drawlines {id} {
+    global canv
+
+    $canv itemconf lines.$id -width [linewidth $id]
+}
+
+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 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]
+    if {$id eq $nullid} {
+       set ofill red
+    } elseif {$id eq $nullid2} {
+       set ofill green
+    } else {
+       set ofill [expr {$listed != 0? "blue": "white"}]
+    }
+    set x [xc $row $col]
+    set y [yc $row]
+    set orad [expr {$linespc / 3}]
+    if {$listed <= 1} {
+       set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
+                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    } elseif {$listed == 2} {
+       # triangle pointing left for left-side commits
+       set t [$canv create polygon \
+                  [expr {$x - $orad}] $y \
+                  [expr {$x + $orad - 1}] [expr {$y - $orad}] \
+                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    } else {
+       # triangle pointing right for right-side commits
+       set t [$canv create polygon \
+                  [expr {$x + $orad - 1}] $y \
+                  [expr {$x - $orad}] [expr {$y - $orad}] \
+                  [expr {$x - $orad}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    }
+    $canv raise $t
+    $canv bind $t <1> {selcanvline {} %x %y}
+    set rmx [llength [lindex $rowidlist $row]]
+    set olds [lindex $parentlist $row]
+    if {$olds ne {}} {
+       set nextids [lindex $rowidlist [expr {$row + 1}]]
+       foreach p $olds {
+           set i [lsearch -exact $nextids $p]
+           if {$i > $rmx} {
+               set rmx $i
+           }
+       }
+    }
+    set xt [xc $row $rmx]
+    set rowtextx($row) $xt
+    set idpos($id) [list $x $xt $y]
+    if {[info exists idtags($id)] || [info exists idheads($id)]
+       || [info exists idotherrefs($id)]} {
+       set xt [drawtags $id $x $xt $y]
+    }
+    set headline [lindex $commitinfo($id) 0]
+    set name [lindex $commitinfo($id) 1]
+    set date [lindex $commitinfo($id) 2]
+    set date [formatdate $date]
+    set font mainfont
+    set nfont mainfont
+    set isbold [ishighlighted $row]
+    if {$isbold > 0} {
+       lappend boldrows $row
+       set font mainfontbold
+       if {$isbold > 1} {
+           lappend boldnamerows $row
+           set nfont mainfontbold
+       }
+    }
+    set linehtag($row) [$canv create text $xt $y -anchor w -fill $fgcolor \
+                           -text $headline -font $font -tags text]
+    $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
+    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]
+    if {[info exists selectedline] && $selectedline == $row} {
+       make_secsel $row
+    }
+    set xr [expr {$xt + [font measure $font $headline]}]
+    if {$xr > $canvxmax} {
+       set canvxmax $xr
+       setcanvscroll
+    }
+}
+
+proc drawcmitrow {row} {
+    global displayorder rowidlist nrows_drawn
+    global iddrawn markingmatches
+    global commitinfo parentlist numcommits
+    global filehighlight fhighlights findpattern nhighlights
+    global hlview vhighlights
+    global highlight_related rhighlights
+
+    if {$row >= $numcommits} return
+
+    set id [lindex $displayorder $row]
+    if {[info exists hlview] && ![info exists vhighlights($row)]} {
+       askvhighlight $row $id
+    }
+    if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
+       askfilehighlight $row $id
+    }
+    if {$findpattern ne {} && ![info exists nhighlights($row)]} {
+       askfindhighlight $row $id
+    }
+    if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
+       askrelhighlight $row $id
+    }
+    if {![info exists iddrawn($id)]} {
+       set col [lsearch -exact [lindex $rowidlist $row] $id]
+       if {$col < 0} {
+           puts "oops, row $row id $id not in list"
+           return
+       }
+       if {![info exists commitinfo($id)]} {
+           getcommit $id
+       }
+       assigncolor $id
+       drawcmittext $id $row $col
+       set iddrawn($id) 1
+       incr nrows_drawn
+    }
+    if {$markingmatches} {
+       markrowmatches $row $id
+    }
+}
+
+proc drawcommits {row {endrow {}}} {
+    global numcommits iddrawn displayorder curview need_redisplay
+    global parentlist rowidlist rowfinal uparrowlen downarrowlen nrows_drawn
+
+    if {$row < 0} {
+       set row 0
+    }
+    if {$endrow eq {}} {
+       set endrow $row
+    }
+    if {$endrow >= $numcommits} {
+       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])]} {
+       set r $row
+    }
+    set er [expr {$endrow + 1}]
+    if {$er >= $numcommits ||
+       ![info exists iddrawn([lindex $displayorder $er])]} {
+       set er $endrow
+    }
+    for {} {$r <= $er} {incr r} {
+       set id [lindex $displayorder $r]
+       set wasdrawn [info exists iddrawn($id)]
+       drawcmitrow $r
+       if {$r == $er} break
+       set nextid [lindex $displayorder [expr {$r + 1}]]
+       if {$wasdrawn && [info exists iddrawn($nextid)]} continue
+       drawparentlinks $id $r
+
+       set rowids [lindex $rowidlist $r]
+       foreach lid $rowids {
+           if {$lid eq {}} continue
+           if {[info exists lineend($lid)] && $lineend($lid) > $r} continue
+           if {$lid eq $id} {
+               # see if this is the first child of any of its parents
+               foreach p [lindex $parentlist $r] {
+                   if {[lsearch -exact $rowids $p] < 0} {
+                       # make this line extend up to the child
+                       set lineend($p) [drawlineseg $p $r $er 0]
+                   }
+               }
+           } else {
+               set lineend($lid) [drawlineseg $lid $r $er 1]
+           }
+       }
+    }
+}
+
+proc drawfrac {f0 f1} {
+    global canv linespc
+
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax == 0} return
+    set y0 [expr {int($f0 * $ymax)}]
+    set row [expr {int(($y0 - 3) / $linespc) - 1}]
+    set y1 [expr {int($f1 * $ymax)}]
+    set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
+    drawcommits $row $endrow
+}
+
+proc drawvisible {} {
+    global canv
+    eval drawfrac [$canv yview]
+}
+
+proc clear_display {} {
+    global iddrawn linesegs need_redisplay nrows_drawn
+    global vhighlights fhighlights nhighlights rhighlights
+
+    allcanvs delete all
+    catch {unset iddrawn}
+    catch {unset linesegs}
+    catch {unset vhighlights}
+    catch {unset fhighlights}
+    catch {unset nhighlights}
+    catch {unset rhighlights}
+    set need_redisplay 0
+    set nrows_drawn 0
+}
+
+proc findcrossings {id} {
+    global rowidlist parentlist numcommits displayorder
+
+    set cross {}
+    set ccross {}
+    foreach {s e} [rowranges $id] {
+       if {$e >= $numcommits} {
+           set e [expr {$numcommits - 1}]
+       }
+       if {$e <= $s} 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]
+           if {$kidx < 0} continue
+           set nextrow [lindex $rowidlist [expr {$row + 1}]]
+           foreach p $olds {
+               set px [lsearch -exact $nextrow $p]
+               if {$px < 0} continue
+               if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
+                   if {[lsearch -exact $ccross $p] >= 0} continue
+                   if {$x == $px + ($kidx < $px? -1: 1)} {
+                       lappend ccross $p
+                   } elseif {[lsearch -exact $cross $p] < 0} {
+                       lappend cross $p
+                   }
+               }
+           }
+       }
+    }
+    return [concat $ccross {{}} $cross]
+}
+
+proc assigncolor {id} {
+    global colormap colors nextcolor
+    global commitrow parentlist children children curview
+
+    if {[info exists colormap($id)]} return
+    set ncolors [llength $colors]
+    if {[info exists children($curview,$id)]} {
+       set kids $children($curview,$id)
+    } else {
+       set kids {}
+    }
+    if {[llength $kids] == 1} {
+       set child [lindex $kids 0]
+       if {[info exists colormap($child)]
+           && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
+           set colormap($id) $colormap($child)
+           return
+       }
+    }
+    set badcolors {}
+    set origbad {}
+    foreach x [findcrossings $id] {
+       if {$x eq {}} {
+           # delimiter between corner crossings and other crossings
+           if {[llength $badcolors] >= $ncolors - 1} break
+           set origbad $badcolors
+       }
+       if {[info exists colormap($x)]
+           && [lsearch -exact $badcolors $colormap($x)] < 0} {
+           lappend badcolors $colormap($x)
+       }
+    }
+    if {[llength $badcolors] >= $ncolors} {
+       set badcolors $origbad
+    }
+    set origbad $badcolors
+    if {[llength $badcolors] < $ncolors - 1} {
+       foreach child $kids {
+           if {[info exists colormap($child)]
+               && [lsearch -exact $badcolors $colormap($child)] < 0} {
+               lappend badcolors $colormap($child)
+           }
+           foreach p [lindex $parentlist $commitrow($curview,$child)] {
+               if {[info exists colormap($p)]
+                   && [lsearch -exact $badcolors $colormap($p)] < 0} {
+                   lappend badcolors $colormap($p)
+               }
+           }
+       }
+       if {[llength $badcolors] >= $ncolors} {
+           set badcolors $origbad
+       }
+    }
+    for {set i 0} {$i <= $ncolors} {incr i} {
+       set c [lindex $colors $nextcolor]
+       if {[incr nextcolor] >= $ncolors} {
+           set nextcolor 0
+       }
+       if {[lsearch -exact $badcolors $c]} break
+    }
+    set colormap($id) $c
+}
+
+proc bindline {t id} {
+    global canv
+
+    $canv bind $t <Enter> "lineenter %x %y $id"
+    $canv bind $t <Motion> "linemotion %x %y $id"
+    $canv bind $t <Leave> "lineleave $id"
+    $canv bind $t <Button-1> "lineclick %x %y $id 1"
+}
+
+proc drawtags {id x xt y1} {
+    global idtags idheads idotherrefs mainhead
+    global linespc lthickness
+    global canv commitrow rowtextx curview fgcolor bgcolor
+
+    set marks {}
+    set ntags 0
+    set nheads 0
+    if {[info exists idtags($id)]} {
+       set marks $idtags($id)
+       set ntags [llength $marks]
+    }
+    if {[info exists idheads($id)]} {
+       set marks [concat $marks $idheads($id)]
+       set nheads [llength $idheads($id)]
+    }
+    if {[info exists idotherrefs($id)]} {
+       set marks [concat $marks $idotherrefs($id)]
+    }
+    if {$marks eq {}} {
+       return $xt
+    }
+
+    set delta [expr {int(0.5 * ($linespc - $lthickness))}]
+    set yt [expr {$y1 - 0.5 * $linespc}]
+    set yb [expr {$yt + $linespc - 1}]
+    set xvals {}
+    set wvals {}
+    set i -1
+    foreach tag $marks {
+       incr i
+       if {$i >= $ntags && $i < $ntags + $nheads && $tag eq $mainhead} {
+           set wid [font measure mainfontbold $tag]
+       } else {
+           set wid [font measure mainfont $tag]
+       }
+       lappend xvals $xt
+       lappend wvals $wid
+       set xt [expr {$xt + $delta + $wid + $lthickness + $linespc}]
+    }
+    set t [$canv create line $x $y1 [lindex $xvals end] $y1 \
+              -width $lthickness -fill black -tags tag.$id]
+    $canv lower $t
+    foreach tag $marks x $xvals wid $wvals {
+       set xl [expr {$x + $delta}]
+       set xr [expr {$x + $delta + $wid + $lthickness}]
+       set font mainfont
+       if {[incr ntags -1] >= 0} {
+           # draw a tag
+           set t [$canv create polygon $x [expr {$yt + $delta}] $xl $yt \
+                      $xr $yt $xr $yb $xl $yb $x [expr {$yb - $delta}] \
+                      -width 1 -outline black -fill yellow -tags tag.$id]
+           $canv bind $t <1> [list showtag $tag 1]
+           set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
+       } else {
+           # draw a head or other ref
+           if {[incr nheads -1] >= 0} {
+               set col green
+               if {$tag eq $mainhead} {
+                   set font mainfontbold
+               }
+           } else {
+               set col "#ddddff"
+           }
+           set xl [expr {$xl - $delta/2}]
+           $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 xi [expr {$x + 1}]
+               set yti [expr {$yt + 1}]
+               set xri [expr {$x + $rwid}]
+               $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
+                       -width 0 -fill "#ffddaa" -tags tag.$id
+           }
+       }
+       set t [$canv create text $xl $y1 -anchor w -text $tag -fill $fgcolor \
+                  -font $font -tags [list tag.$id text]]
+       if {$ntags >= 0} {
+           $canv bind $t <1> [list showtag $tag 1]
+       } elseif {$nheads >= 0} {
+           $canv bind $t <Button-3> [list headmenu %X %Y $id $tag]
+       }
+    }
+    return $xt
+}
+
+proc xcoord {i level ln} {
+    global canvx0 xspc1 xspc2
+
+    set x [expr {$canvx0 + $i * $xspc1($ln)}]
+    if {$i > 0 && $i == $level} {
+       set x [expr {$x + 0.5 * ($xspc2 - $xspc1($ln))}]
+    } elseif {$i > $level} {
+       set x [expr {$x + $xspc2 - $xspc1($ln)}]
+    }
+    return $x
+}
+
+proc show_status {msg} {
+    global canv fgcolor
+
+    clear_display
+    $canv create text 3 3 -anchor nw -text $msg -font mainfont \
+       -tags text -fill $fgcolor
+}
+
+# Insert a new commit as the child of the commit on row $row.
+# The new commit will be displayed on row $row and the commits
+# on that row and below will move down one row.
+proc insertrow {row newcmit} {
+    global displayorder parentlist commitlisted children
+    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"
+       return
+    }
+    set p [lindex $displayorder $row]
+    set displayorder [linsert $displayorder $row $newcmit]
+    set parentlist [linsert $parentlist $row $p]
+    set kids $children($curview,$p)
+    lappend kids $newcmit
+    set children($curview,$p) $kids
+    set children($curview,$newcmit) {}
+    set commitlisted [linsert $commitlisted $row 1]
+    set l [llength $displayorder]
+    for {set r $row} {$r < $l} {incr r} {
+       set id [lindex $displayorder $r]
+       set commitrow($curview,$id) $r
+    }
+    incr commitidx($curview)
+    set ordertok($curview,$newcmit) $ordertok($curview,$p)
+
+    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
+           }
+       }
+       set rowidlist [linsert $rowidlist $row $idlist]
+       set rowisopt [linsert $rowisopt $row 0]
+       set rowfinal [linsert $rowfinal $row [lindex $rowfinal $row]]
+    }
+
+    incr numcommits
+
+    if {[info exists selectedline] && $selectedline >= $row} {
+       incr selectedline
+    }
+    redisplay
+}
+
+# Remove a commit that was inserted with insertrow on row $row.
+proc removerow {row} {
+    global displayorder parentlist commitlisted children
+    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"
+       return
+    }
+    set rp1 [expr {$row + 1}]
+    set id [lindex $displayorder $row]
+    set p [lindex $parentlist $row]
+    set displayorder [lreplace $displayorder $row $row]
+    set parentlist [lreplace $parentlist $row $row]
+    set commitlisted [lreplace $commitlisted $row $row]
+    set kids $children($curview,$p)
+    set i [lsearch -exact $kids $id]
+    if {$i >= 0} {
+       set kids [lreplace $kids $i $i]
+       set children($curview,$p) $kids
+    }
+    set l [llength $displayorder]
+    for {set r $row} {$r < $l} {incr r} {
+       set id [lindex $displayorder $r]
+       set commitrow($curview,$id) $r
+    }
+    incr commitidx($curview) -1
+
+    if {$row < [llength $rowidlist]} {
+       set rowidlist [lreplace $rowidlist $row $row]
+       set rowisopt [lreplace $rowisopt $row $row]
+       set rowfinal [lreplace $rowfinal $row $row]
+    }
+
+    incr numcommits -1
+
+    if {[info exists selectedline] && $selectedline > $row} {
+       incr selectedline -1
+    }
+    redisplay
+}
+
+# Don't change the text pane cursor if it is currently the hand cursor,
+# showing that we are over a sha1 ID link.
+proc settextcursor {c} {
+    global ctext curtextcursor
+
+    if {[$ctext cget -cursor] == $curtextcursor} {
+       $ctext config -cursor $c
+    }
+    set curtextcursor $c
+}
+
+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 busyname statusw
+
+    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
+    }
+}
+
+proc findmatches {f} {
+    global findtype findstring
+    if {$findtype == "Regexp"} {
+       set matches [regexp -indices -all -inline $findstring $f]
+    } else {
+       set fs $findstring
+       if {$findtype == "IgnCase"} {
+           set f [string tolower $f]
+           set fs [string tolower $fs]
+       }
+       set matches {}
+       set i 0
+       set l [string length $fs]
+       while {[set j [string first $fs $f $i]] >= 0} {
+           lappend matches [list $j [expr {$j+$l-1}]]
+           set i [expr {$j + $l}]
+       }
+    }
+    return $matches
+}
+
+proc dofind {{dirn 1} {wrap 1}} {
+    global findstring findstartline findcurline selectedline numcommits
+    global gdttype filehighlight fh_serial find_dirn findallowwrap
+
+    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] [expr {$dirn < 0}]]
+    } else {
+       set findstartline $selectedline
+    }
+    set findcurline $findstartline
+    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 stopfinding {} {
+    global find_dirn findcurline fprogcoord
+
+    if {[info exists find_dirn]} {
+       unset find_dirn
+       unset findcurline
+       notbusy finding
+       set fprogcoord 0
+       adjustprogress
+    }
+}
+
+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
+    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 {
+       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
+       }
+    } 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 {$found || ($domore && !$moretodo)} {
+       unset findcurline
+       unset find_dirn
+       notbusy finding
+       set fprogcoord 0
+       adjustprogress
+       if {$found} {
+           findselectline $l
+       } else {
+           bell
+       }
+       return 0
+    }
+    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 gdttype
+
+    set markingmatches 1
+    set findcurline $l
+    selectline $l 1
+    if {$findloc == "All fields" || $findloc == "Comments"} {
+       # highlight the matches in the comments
+       set f [$ctext get 1.0 $commentend]
+       set matches [findmatches $f]
+       foreach match $matches {
+           set start [lindex $match 0]
+           set end [expr {[lindex $match 1] + 1}]
+           $ctext tag add found "1.0 + $start c" "1.0 + $end c"
+       }
+    }
+    drawvisible
+}
+
+# mark the bits of a headline or author that match a find string
+proc markmatches {canv l str tag matches font row} {
+    global selectedline
+
+    set bbox [$canv bbox $tag]
+    set x0 [lindex $bbox 0]
+    set y0 [lindex $bbox 1]
+    set y1 [lindex $bbox 3]
+    foreach match $matches {
+       set start [lindex $match 0]
+       set end [lindex $match 1]
+       if {$start > $end} continue
+       set xoff [font measure $font [string range $str 0 [expr {$start-1}]]]
+       set xlen [font measure $font [string range $str 0 [expr {$end}]]]
+       set t [$canv create rect [expr {$x0+$xoff}] $y0 \
+                  [expr {$x0+$xlen+2}] $y1 \
+                  -outline {} -tags [list match$l matches] -fill yellow]
+       $canv lower $t
+       if {[info exists selectedline] && $row == $selectedline} {
+           $canv raise $t secsel
+       }
+    }
+}
+
+proc unmarkmatches {} {
+    global markingmatches
+
+    allcanvs delete matches
+    set markingmatches 0
+    stopfinding
+}
+
+proc selcanvline {w x y} {
+    global canv canvy0 ctext linespc
+    global rowtextx
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax == {}} return
+    set yfrac [lindex [$canv yview] 0]
+    set y [expr {$y + $yfrac * $ymax}]
+    set l [expr {int(($y - $canvy0) / $linespc + 0.5)}]
+    if {$l < 0} {
+       set l 0
+    }
+    if {$w eq $canv} {
+       if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
+    }
+    unmarkmatches
+    selectline $l 1
+}
+
+proc commit_descriptor {p} {
+    global commitinfo
+    if {![info exists commitinfo($p)]} {
+       getcommit $p
+    }
+    set l "..."
+    if {[llength $commitinfo($p)] > 1} {
+       set l [lindex $commitinfo($p) 0]
+    }
+    return "$p ($l)\n"
+}
+
+# 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 pendinglinks
+
+    set start [$ctext index "end - 1c"]
+    $ctext insert end $text $tags
+    set links [regexp -indices -all -inline {[0-9a-f]{40}} $text]
+    foreach l $links {
+       set s [lindex $l 0]
+       set e [lindex $l 1]
+       set linkid [string range $text $s $e]
+       incr e
+       $ctext tag delete link$linknum
+       $ctext tag add link$linknum "$start + $s c" "$start + $e c"
+       setlink $linkid link$linknum
+       incr linknum
+    }
+}
+
+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} {
+    global canv linespc
+
+    $canv delete hover
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    set wnow [$canv yview]
+    set wtop [expr {[lindex $wnow 0] * $ymax}]
+    set newtop [expr {$wtop + $dir * $linespc}]
+    if {$newtop < 0} {
+       set newtop 0
+    } elseif {$newtop > $ymax} {
+       set newtop $ymax
+    }
+    allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
+}
+
+# add a list of tag or branch names at position pos
+# returns the number of names inserted
+proc appendrefs {pos ids var} {
+    global ctext commitrow linknum curview $var maxrefs
+
+    if {[catch {$ctext index $pos}]} {
+       return 0
+    }
+    $ctext conf -state normal
+    $ctext delete $pos "$pos lineend"
+    set tags {}
+    foreach id $ids {
+       foreach tag [set $var\($id\)] {
+           lappend tags [list $tag $id]
+       }
+    }
+    if {[llength $tags] > $maxrefs} {
+       $ctext insert $pos "many ([llength $tags])"
+    } else {
+       set tags [lsort -index 0 -decreasing $tags]
+       set sep {}
+       foreach ti $tags {
+           set id [lindex $ti 1]
+           set lk link$linknum
+           incr linknum
+           $ctext tag delete $lk
+           $ctext insert $pos $sep
+           $ctext insert $pos [lindex $ti 0] $lk
+           setlink $id $lk
+           set sep ", "
+       }
+    }
+    $ctext conf -state disabled
+    return [llength $tags]
+}
+
+# called when we have finished computing the nearby tags
+proc dispneartags {delay} {
+    global selectedline currentid showneartags tagphase
+
+    if {![info exists selectedline] || !$showneartags} return
+    after cancel dispnexttag
+    if {$delay} {
+       after 200 dispnexttag
+       set tagphase -1
+    } else {
+       after idle dispnexttag
+       set tagphase 0
+    }
+}
+
+proc dispnexttag {} {
+    global selectedline currentid showneartags tagphase ctext
+
+    if {![info exists selectedline] || !$showneartags} return
+    switch -- $tagphase {
+       0 {
+           set dtags [desctags $currentid]
+           if {$dtags ne {}} {
+               appendrefs precedes $dtags idtags
+           }
+       }
+       1 {
+           set atags [anctags $currentid]
+           if {$atags ne {}} {
+               appendrefs follows $atags idtags
+           }
+       }
+       2 {
+           set dheads [descheads $currentid]
+           if {$dheads ne {}} {
+               if {[appendrefs branch $dheads idheads] > 1
+                   && [$ctext get "branch -3c"] eq "h"} {
+                   # turn "Branch" into "Branches"
+                   $ctext conf -state normal
+                   $ctext insert "branch -2c" "es"
+                   $ctext conf -state disabled
+               }
+           }
+       }
+    }
+    if {[incr tagphase] <= 2} {
+       after idle 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 ctext commitinfo selectedline
+    global displayorder
+    global canvy0 linespc parentlist children curview
+    global currentid sha1entry
+    global commentend idtags linknum
+    global mergemax numcommits pending_select
+    global cmitmode showneartags allcommits
+
+    catch {unset pending_select}
+    $canv delete hover
+    normalline
+    unsel_reflist
+    stopfinding
+    if {$l < 0 || $l >= $numcommits} return
+    set y [expr {$canvy0 + $l * $linespc}]
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    set ytop [expr {$y - $linespc - 1}]
+    set ybot [expr {$y + $linespc + 1}]
+    set wnow [$canv yview]
+    set wtop [expr {[lindex $wnow 0] * $ymax}]
+    set wbot [expr {[lindex $wnow 1] * $ymax}]
+    set wh [expr {$wbot - $wtop}]
+    set newtop $wtop
+    if {$ytop < $wtop} {
+       if {$ybot < $wtop} {
+           set newtop [expr {$y - $wh / 2.0}]
+       } else {
+           set newtop $ytop
+           if {$newtop > $wtop - $linespc} {
+               set newtop [expr {$wtop - $linespc}]
+           }
+       }
+    } elseif {$ybot > $wbot} {
+       if {$ytop > $wbot} {
+           set newtop [expr {$y - $wh / 2.0}]
+       } else {
+           set newtop [expr {$ybot - $wh}]
+           if {$newtop < $wtop + $linespc} {
+               set newtop [expr {$wtop + $linespc}]
+           }
+       }
+    }
+    if {$newtop != $wtop} {
+       if {$newtop < 0} {
+           set newtop 0
+       }
+       allcanvs yview moveto [expr {$newtop * 1.0 / $ymax}]
+       drawvisible
+    }
+
+    make_secsel $l
+
+    if {$isnew} {
+       addtohistory [list selectline $l 0]
+    }
+
+    set selectedline $l
+
+    set id [lindex $displayorder $l]
+    set currentid $id
+    $sha1entry delete 0 end
+    $sha1entry insert 0 $id
+    $sha1entry selection from 0
+    $sha1entry selection to end
+    rhighlight_sel $id
+
+    $ctext conf -state normal
+    clear_ctext
+    set linknum 0
+    set info $commitinfo($id)
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "Author: [lindex $info 1]  $date\n"
+    set date [formatdate [lindex $info 4]]
+    $ctext insert end "Committer: [lindex $info 3]  $date\n"
+    if {[info exists idtags($id)]} {
+       $ctext insert end "Tags:"
+       foreach tag $idtags($id) {
+           $ctext insert end " $tag"
+       }
+       $ctext insert end "\n"
+    }
+
+    set headers {}
+    set olds [lindex $parentlist $l]
+    if {[llength $olds] > 1} {
+       set np 0
+       foreach p $olds {
+           if {$np >= $mergemax} {
+               set tag mmax
+           } else {
+               set tag m$np
+           }
+           $ctext insert end "Parent: " $tag
+           appendwithlinks [commit_descriptor $p] {}
+           incr np
+       }
+    } else {
+       foreach p $olds {
+           append headers "Parent: [commit_descriptor $p]"
+       }
+    }
+
+    foreach c $children($curview,$id) {
+       append headers "Child:  [commit_descriptor $c]"
+    }
+
+    # make anything that looks like a SHA1 ID be a clickable link
+    appendwithlinks $headers {}
+    if {$showneartags} {
+       if {![info exists allcommits]} {
+           getallcommits
+       }
+       $ctext insert end "Branch: "
+       $ctext mark set branch "end -1c"
+       $ctext mark gravity branch left
+       $ctext insert end "\nFollows: "
+       $ctext mark set follows "end -1c"
+       $ctext mark gravity follows left
+       $ctext insert end "\nPrecedes: "
+       $ctext mark set precedes "end -1c"
+       $ctext mark gravity precedes left
+       $ctext insert end "\n"
+       dispneartags 1
+    }
+    $ctext insert end "\n"
+    set comment [lindex $info 5]
+    if {[string first "\r" $comment] >= 0} {
+       set comment [string map {"\r" "\n    "} $comment]
+    }
+    appendwithlinks $comment {comment}
+
+    $ctext tag remove found 1.0 end
+    $ctext conf -state disabled
+    set commentend [$ctext index "end - 1c"]
+
+    init_flist "Comments"
+    if {$cmitmode eq "tree"} {
+       gettree $id
+    } elseif {[llength $olds] <= 1} {
+       startdiff $id
+    } else {
+       mergediff $id $l
+    }
+}
+
+proc selfirstline {} {
+    unmarkmatches
+    selectline 0 1
+}
+
+proc sellastline {} {
+    global numcommits
+    unmarkmatches
+    set l [expr {$numcommits - 1}]
+    selectline $l 1
+}
+
+proc selnextline {dir} {
+    global selectedline
+    focus .
+    if {![info exists selectedline]} return
+    set l [expr {$selectedline + $dir}]
+    unmarkmatches
+    selectline $l 1
+}
+
+proc selnextpage {dir} {
+    global canv linespc selectedline numcommits
+
+    set lpp [expr {([winfo height $canv] - 2) / $linespc}]
+    if {$lpp < 1} {
+       set lpp 1
+    }
+    allcanvs yview scroll [expr {$dir * $lpp}] units
+    drawvisible
+    if {![info exists selectedline]} return
+    set l [expr {$selectedline + $dir * $lpp}]
+    if {$l < 0} {
+       set l 0
+    } elseif {$l >= $numcommits} {
+        set l [expr $numcommits - 1]
+    }
+    unmarkmatches
+    selectline $l 1
+}
+
+proc unselectline {} {
+    global selectedline currentid
+
+    catch {unset selectedline}
+    catch {unset currentid}
+    allcanvs delete secsel
+    rhighlight_none
+}
+
+proc reselectline {} {
+    global selectedline
+
+    if {[info exists selectedline]} {
+       selectline $selectedline 0
+    }
+}
+
+proc addtohistory {cmd} {
+    global history historyindex curview
+
+    set elt [list $curview $cmd]
+    if {$historyindex > 0
+       && [lindex $history [expr {$historyindex - 1}]] == $elt} {
+       return
+    }
+
+    if {$historyindex < [llength $history]} {
+       set history [lreplace $history $historyindex end $elt]
+    } else {
+       lappend history $elt
+    }
+    incr historyindex
+    if {$historyindex > 1} {
+       .tf.bar.leftbut conf -state normal
+    } else {
+       .tf.bar.leftbut conf -state disabled
+    }
+    .tf.bar.rightbut conf -state disabled
+}
+
+proc godo {elt} {
+    global curview
+
+    set view [lindex $elt 0]
+    set cmd [lindex $elt 1]
+    if {$curview != $view} {
+       showview $view
+    }
+    eval $cmd
+}
+
+proc goback {} {
+    global history historyindex
+    focus .
+
+    if {$historyindex > 1} {
+       incr historyindex -1
+       godo [lindex $history [expr {$historyindex - 1}]]
+       .tf.bar.rightbut conf -state normal
+    }
+    if {$historyindex <= 1} {
+       .tf.bar.leftbut conf -state disabled
+    }
+}
+
+proc goforw {} {
+    global history historyindex
+    focus .
+
+    if {$historyindex < [llength $history]} {
+       set cmd [lindex $history $historyindex]
+       incr historyindex
+       godo $cmd
+       .tf.bar.leftbut conf -state normal
+    }
+    if {$historyindex >= [llength $history]} {
+       .tf.bar.rightbut conf -state disabled
+    }
+}
+
+proc gettree {id} {
+    global treefilelist treeidlist diffids diffmergeid treepending
+    global nullid nullid2
+
+    set diffids $id
+    catch {unset diffmergeid}
+    if {![info exists treefilelist($id)]} {
+       if {![info exists treepending]} {
+           if {$id eq $nullid} {
+               set cmd [list | git ls-files]
+           } elseif {$id eq $nullid2} {
+               set cmd [list | git ls-files --stage -t]
+           } else {
+               set cmd [list | git ls-tree -r $id]
+           }
+           if {[catch {set gtf [open $cmd r]}]} {
+               return
+           }
+           set treepending $id
+           set treefilelist($id) {}
+           set treeidlist($id) {}
+           fconfigure $gtf -blocking 0
+           filerun $gtf [list gettreeline $gtf $id]
+       }
+    } else {
+       setfilelist $id
+    }
+}
+
+proc gettreeline {gtf id} {
+    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
+
+    set nl 0
+    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
+       if {$diffids eq $nullid} {
+           set fname $line
+       } else {
+           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
+           set i [string first "\t" $line]
+           if {$i < 0} continue
+           set sha1 [lindex $line 2]
+           set fname [string range $line [expr {$i+1}] end]
+           if {[string index $fname 0] eq "\""} {
+               set fname [lindex $fname 0]
+           }
+           lappend treeidlist($id) $sha1
+       }
+       lappend treefilelist($id) $fname
+    }
+    if {![eof $gtf]} {
+       return [expr {$nl >= 1000? 2: 1}]
+    }
+    close $gtf
+    unset treepending
+    if {$cmitmode ne "tree"} {
+       if {![info exists diffmergeid]} {
+           gettreediffs $diffids
+       }
+    } elseif {$id ne $diffids} {
+       gettree $diffids
+    } else {
+       setfilelist $id
+    }
+    return 0
+}
+
+proc showfile {f} {
+    global treefilelist treeidlist diffids nullid nullid2
+    global ctext commentend
+
+    set i [lsearch -exact $treefilelist($diffids) $f]
+    if {$i < 0} {
+       puts "oops, $f not in list for id $diffids"
+       return
+    }
+    if {$diffids eq $nullid} {
+       if {[catch {set bf [open $f r]} err]} {
+           puts "oops, can't read $f: $err"
+           return
+       }
+    } else {
+       set blob [lindex $treeidlist($diffids) $i]
+       if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
+           puts "oops, error reading blob $blob: $err"
+           return
+       }
+    }
+    fconfigure $bf -blocking 0
+    filerun $bf [list getblobline $bf $diffids]
+    $ctext config -state normal
+    clear_ctext $commentend
+    $ctext insert end "\n"
+    $ctext insert end "$f\n" filesep
+    $ctext config -state disabled
+    $ctext yview $commentend
+    settabs 0
+}
+
+proc getblobline {bf id} {
+    global diffids cmitmode ctext
+
+    if {$id ne $diffids || $cmitmode ne "tree"} {
+       catch {close $bf}
+       return 0
+    }
+    $ctext config -state normal
+    set nl 0
+    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
+       $ctext insert end "$line\n"
+    }
+    if {[eof $bf]} {
+       # delete last newline
+       $ctext delete "end - 2c" "end - 1c"
+       close $bf
+       return 0
+    }
+    $ctext config -state disabled
+    return [expr {$nl >= 1000? 2: 1}]
+}
+
+proc mergediff {id l} {
+    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 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
+    }
+    fconfigure $mdf -blocking 0
+    set mdifffd($id) $mdf
+    set np [llength [lindex $parentlist $l]]
+    settabs $np
+    filerun $mdf [list getmergediffline $mdf $id $np]
+}
+
+proc getmergediffline {mdf id np} {
+    global diffmergeid ctext cflist mergemax
+    global difffilestart mdifffd
+
+    $ctext conf -state normal
+    set nr 0
+    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
+       if {![info exists diffmergeid] || $id != $diffmergeid
+           || $mdf != $mdifffd($id)} {
+           close $mdf
+           return 0
+       }
+       if {[regexp {^diff --cc (.*)} $line match fname]} {
+           # start of a new file
+           $ctext insert end "\n"
+           set here [$ctext index "end - 1c"]
+           lappend difffilestart $here
+           add_flist [list $fname]
+           set l [expr {(78 - [string length $fname]) / 2}]
+           set pad [string range "----------------------------------------" 1 $l]
+           $ctext insert end "$pad $fname $pad\n" filesep
+       } elseif {[regexp {^@@} $line]} {
+           $ctext insert end "$line\n" hunksep
+       } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
+           # do nothing
+       } else {
+           # parse the prefix - one ' ', '-' or '+' for each parent
+           set spaces {}
+           set minuses {}
+           set pluses {}
+           set isbad 0
+           for {set j 0} {$j < $np} {incr j} {
+               set c [string range $line $j $j]
+               if {$c == " "} {
+                   lappend spaces $j
+               } elseif {$c == "-"} {
+                   lappend minuses $j
+               } elseif {$c == "+"} {
+                   lappend pluses $j
+               } else {
+                   set isbad 1
+                   break
+               }
+           }
+           set tags {}
+           set num {}
+           if {!$isbad && $minuses ne {} && $pluses eq {}} {
+               # line doesn't appear in result, parents in $minuses have the line
+               set num [lindex $minuses 0]
+           } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
+               # line appears in result, parents in $pluses don't have the line
+               lappend tags mresult
+               set num [lindex $spaces 0]
+           }
+           if {$num ne {}} {
+               if {$num >= $mergemax} {
+                   set num "max"
+               }
+               lappend tags m$num
+           }
+           $ctext insert end "$line\n" $tags
+       }
+    }
+    $ctext conf -state disabled
+    if {[eof $mdf]} {
+       close $mdf
+       return 0
+    }
+    return [expr {$nr >= 1000? 2: 1}]
+}
+
+proc startdiff {ids} {
+    global treediffs diffids treepending diffmergeid nullid nullid2
+
+    settabs 1
+    set diffids $ids
+    catch {unset diffmergeid}
+    if {![info exists treediffs($ids)] ||
+       [lsearch -exact $ids $nullid] >= 0 ||
+       [lsearch -exact $ids $nullid2] >= 0} {
+       if {![info exists treepending]} {
+           gettreediffs $ids
+       }
+    } else {
+       addtocflist $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
+
+    add_flist $treediffs($ids)
+    getblobdiffs $ids
+}
+
+proc diffcmd {ids flags} {
+    global nullid nullid2
+
+    set i [lsearch -exact $ids $nullid]
+    set j [lsearch -exact $ids $nullid2]
+    if {$i >= 0} {
+       if {[llength $ids] > 1 && $j < 0} {
+           # comparing working directory with some specific revision
+           set cmd [concat | git diff-index $flags]
+           if {$i == 0} {
+               lappend cmd -R [lindex $ids 1]
+           } else {
+               lappend cmd [lindex $ids 0]
+           }
+       } else {
+           # comparing working directory with index
+           set cmd [concat | git diff-files $flags]
+           if {$j == 1} {
+               lappend cmd -R
+           }
+       }
+    } elseif {$j >= 0} {
+       set cmd [concat | git diff-index --cached $flags]
+       if {[llength $ids] > 1} {
+           # comparing index with specific revision
+           if {$i == 0} {
+               lappend cmd -R [lindex $ids 1]
+           } else {
+               lappend cmd [lindex $ids 0]
+           }
+       } else {
+           # comparing index with HEAD
+           lappend cmd HEAD
+       }
+    } else {
+       set cmd [concat | git diff-tree -r $flags $ids]
+    }
+    return $cmd
+}
+
+proc gettreediffs {ids} {
+    global treediff treepending
+
+    set treepending $ids
+    set treediff {}
+    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
+    fconfigure $gdtf -blocking 0
+    filerun $gdtf [list gettreediffline $gdtf $ids]
+}
+
+proc gettreediffline {gdtf ids} {
+    global treediff treediffs treepending diffids diffmergeid
+    global cmitmode viewfiles curview limitdiffs
+
+    set nr 0
+    while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
+       set i [string first "\t" $line]
+       if {$i >= 0} {
+           set file [string range $line [expr {$i+1}] end]
+           if {[string index $file 0] eq "\""} {
+               set file [lindex $file 0]
+           }
+           lappend treediff $file
+       }
+    }
+    if {![eof $gdtf]} {
+       return [expr {$nr >= 1000? 2: 1}]
+    }
+    close $gdtf
+    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
+    } elseif {$ids != $diffids} {
+       if {![info exists diffmergeid]} {
+           gettreediffs $diffids
+       }
+    } else {
+       addtocflist $ids
+    }
+    return 0
+}
+
+# empty string or positive integer
+proc diffcontextvalidate {v} {
+    return [regexp {^(|[1-9][0-9]*)$} $v]
+}
+
+proc diffcontextchange {n1 n2 op} {
+    global diffcontextstring diffcontext
+
+    if {[string is integer -strict $diffcontextstring]} {
+       if {$diffcontextstring > 0} {
+           set diffcontext $diffcontextstring
+           reselectline
+       }
+    }
+}
+
+proc getblobdiffs {ids} {
+    global blobdifffd diffids env
+    global diffinhdr treediffs
+    global diffcontext
+    global limitdiffs viewfiles curview
+
+    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
+    }
+    set diffinhdr 0
+    fconfigure $bdf -blocking 0
+    set blobdifffd($ids) $bdf
+    filerun $bdf [list getblobdiffline $bdf $diffids]
+}
+
+proc setinlist {var i val} {
+    global $var
+
+    while {[llength [set $var]] < $i} {
+       lappend $var {}
+    }
+    if {[llength [set $var]] == $i} {
+       lappend $var $val
+    } else {
+       lset $var $i $val
+    }
+}
+
+proc makediffhdr {fname ids} {
+    global ctext curdiffstart treediffs
+
+    set i [lsearch -exact $treediffs($ids) $fname]
+    if {$i >= 0} {
+       setinlist difffilestart $i $curdiffstart
+    }
+    set l [expr {(78 - [string length $fname]) / 2}]
+    set pad [string range "----------------------------------------" 1 $l]
+    $ctext insert $curdiffstart "$pad $fname $pad" filesep
+}
+
+proc getblobdiffline {bdf ids} {
+    global diffids blobdifffd ctext curdiffstart
+    global diffnexthead diffnextnote difffilestart
+    global diffinhdr treediffs
+
+    set nr 0
+    $ctext conf -state normal
+    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
+       if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
+           close $bdf
+           return 0
+       }
+       if {![string compare -length 11 "diff --git " $line]} {
+           # trim off "diff --git "
+           set line [string range $line 11 end]
+           set diffinhdr 1
+           # start of a new file
+           $ctext insert end "\n"
+           set curdiffstart [$ctext index "end - 1c"]
+           $ctext insert end "\n" filesep
+           # If the name hasn't changed the length will be odd,
+           # the middle char will be a space, and the two bits either
+           # side will be a/name and b/name, or "a/name" and "b/name".
+           # If the name has changed we'll get "rename from" and
+           # "rename to" or "copy from" and "copy to" lines following this,
+           # and we'll use them to get the filenames.
+           # This complexity is necessary because spaces in the filename(s)
+           # don't get escaped.
+           set l [string length $line]
+           set i [expr {$l / 2}]
+           if {!(($l & 1) && [string index $line $i] eq " " &&
+                 [string range $line 2 [expr {$i - 1}]] eq \
+                     [string range $line [expr {$i + 3}] end])} {
+               continue
+           }
+           # unescape if quoted and chop off the a/ from the front
+           if {[string index $line 0] eq "\""} {
+               set fname [string range [lindex $line 0] 2 end]
+           } else {
+               set fname [string range $line 2 [expr {$i - 1}]]
+           }
+           makediffhdr $fname $ids
+
+       } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
+                      $line match f1l f1c f2l f2c rest]} {
+           $ctext insert end "$line\n" hunksep
+           set diffinhdr 0
+
+       } elseif {$diffinhdr} {
+           if {![string compare -length 12 "rename from " $line]} {
+               set fname [string range $line [expr 6 + [string first " from " $line] ] end]
+               if {[string index $fname 0] eq "\""} {
+                   set fname [lindex $fname 0]
+               }
+               set i [lsearch -exact $treediffs($ids) $fname]
+               if {$i >= 0} {
+                   setinlist difffilestart $i $curdiffstart
+               }
+           } elseif {![string compare -length 10 $line "rename to "] ||
+                     ![string compare -length 8 $line "copy to "]} {
+               set fname [string range $line [expr 4 + [string first " to " $line] ] end]
+               if {[string index $fname 0] eq "\""} {
+                   set fname [lindex $fname 0]
+               }
+               makediffhdr $fname $ids
+           } elseif {[string compare -length 3 $line "---"] == 0} {
+               # do nothing
+               continue
+           } elseif {[string compare -length 3 $line "+++"] == 0} {
+               set diffinhdr 0
+               continue
+           }
+           $ctext insert end "$line\n" filesep
+
+       } else {
+           set x [string range $line 0 0]
+           if {$x == "-" || $x == "+"} {
+               set tag [expr {$x == "+"}]
+               $ctext insert end "$line\n" d$tag
+           } elseif {$x == " "} {
+               $ctext insert end "$line\n"
+           } else {
+               # "\ No newline at end of file",
+               # or something else we don't recognize
+               $ctext insert end "$line\n" hunksep
+           }
+       }
+    }
+    $ctext conf -state disabled
+    if {[eof $bdf]} {
+       close $bdf
+       return 0
+    }
+    return [expr {$nr >= 1000? 2: 1}]
+}
+
+proc changediffdisp {} {
+    global ctext diffelide
+
+    $ctext tag conf d0 -elide [lindex $diffelide 0]
+    $ctext tag conf d1 -elide [lindex $diffelide 1]
+}
+
+proc prevfile {} {
+    global difffilestart ctext
+    set prev [lindex $difffilestart 0]
+    set here [$ctext index @0,0]
+    foreach loc $difffilestart {
+       if {[$ctext compare $loc >= $here]} {
+           $ctext yview $prev
+           return
+       }
+       set prev $loc
+    }
+    $ctext yview $prev
+}
+
+proc nextfile {} {
+    global difffilestart ctext
+    set here [$ctext index @0,0]
+    foreach loc $difffilestart {
+       if {[$ctext compare $loc > $here]} {
+           $ctext yview $loc
+           return
+       }
+    }
+}
+
+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]} {
+       set smarktop $l
+    }
+    if {![info exists smarkbot] || [$ctext compare $first < $smarkbot.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} {
+    global ctext searchstring searchdirn
+
+    $ctext tag remove found 1.0 end
+    if {[catch {$ctext index anchor}]} {
+       # no anchor set, use start of selection, or of visible area
+       set sel [$ctext tag ranges sel]
+       if {$sel ne {}} {
+           $ctext mark set anchor [lindex $sel 0]
+       } elseif {$searchdirn eq "-forwards"} {
+           $ctext mark set anchor @0,0
+       } else {
+           $ctext mark set anchor @0,[winfo height $ctext]
+       }
+    }
+    if {$searchstring ne {}} {
+       set here [$ctext search $searchdirn -- $searchstring anchor]
+       if {$here ne {}} {
+           $ctext see $here
+       }
+       searchmarkvisible 1
+    }
+}
+
+proc dosearch {} {
+    global sstring ctext searchstring searchdirn
+
+    focus $sstring
+    $sstring icursor end
+    set searchdirn -forwards
+    if {$searchstring ne {}} {
+       set sel [$ctext tag ranges sel]
+       if {$sel ne {}} {
+           set start "[lindex $sel 0] + 1c"
+       } elseif {[catch {set start [$ctext index anchor]}]} {
+           set start "@0,0"
+       }
+       set match [$ctext search -count mlen -- $searchstring $start]
+       $ctext tag remove sel 1.0 end
+       if {$match eq {}} {
+           bell
+           return
+       }
+       $ctext see $match
+       set mend "$match + $mlen c"
+       $ctext tag add sel $match $mend
+       $ctext mark unset anchor
+    }
+}
+
+proc dosearchback {} {
+    global sstring ctext searchstring searchdirn
+
+    focus $sstring
+    $sstring icursor end
+    set searchdirn -backwards
+    if {$searchstring ne {}} {
+       set sel [$ctext tag ranges sel]
+       if {$sel ne {}} {
+           set start [lindex $sel 0]
+       } elseif {[catch {set start [$ctext index anchor]}]} {
+           set start @0,[winfo height $ctext]
+       }
+       set match [$ctext search -backwards -count ml -- $searchstring $start]
+       $ctext tag remove sel 1.0 end
+       if {$match eq {}} {
+           bell
+           return
+       }
+       $ctext see $match
+       set mend "$match + $ml c"
+       $ctext tag add sel $match $mend
+       $ctext mark unset anchor
+    }
+}
+
+proc searchmark {first last} {
+    global ctext searchstring
+
+    set mend $first.0
+    while {1} {
+       set match [$ctext search -count mlen -- $searchstring $mend $last.end]
+       if {$match eq {}} break
+       set mend "$match + $mlen c"
+       $ctext tag add found $match $mend
+    }
+}
+
+proc searchmarkvisible {doall} {
+    global ctext smarktop smarkbot
+
+    set topline [lindex [split [$ctext index @0,0] .] 0]
+    set botline [lindex [split [$ctext index @0,[winfo height $ctext]] .] 0]
+    if {$doall || $botline < $smarktop || $topline > $smarkbot} {
+       # no overlap with previous
+       searchmark $topline $botline
+       set smarktop $topline
+       set smarkbot $botline
+    } else {
+       if {$topline < $smarktop} {
+           searchmark $topline [expr {$smarktop-1}]
+           set smarktop $topline
+       }
+       if {$botline > $smarkbot} {
+           searchmark [expr {$smarkbot+1}] $botline
+           set smarkbot $botline
+       }
+    }
+}
+
+proc scrolltext {f0 f1} {
+    global searchstring
+
+    .bleft.sb set $f0 $f1
+    if {$searchstring ne {}} {
+       searchmarkvisible 0
+    }
+}
+
+proc setcoords {} {
+    global linespc charspc canvx0 canvy0
+    global xspc1 xspc2 lthickness
+
+    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}]
+    set xspc1(0) $linespc
+    set xspc2 $linespc
+}
+
+proc redisplay {} {
+    global canv
+    global selectedline
+
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax == 0} return
+    set span [$canv yview]
+    clear_display
+    setcanvscroll
+    allcanvs yview moveto [lindex $span 0]
+    drawvisible
+    if {[info exists selectedline]} {
+       selectline $selectedline 0
+       allcanvs yview moveto [lindex $span 0]
+    }
+}
+
+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 stopped entries fontattr
+
+    unmarkmatches
+    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
+    settabs
+    redisplay
+}
+
+proc clearsha1 {} {
+    global sha1entry sha1string
+    if {[string length $sha1string] == 40} {
+       $sha1entry delete 0 end
+    }
+}
+
+proc sha1change {n1 n2 op} {
+    global sha1string currentid sha1but
+    if {$sha1string == {}
+       || ([info exists currentid] && $sha1string == $currentid)} {
+       set state disabled
+    } else {
+       set state normal
+    }
+    if {[$sha1but cget -state] == $state} return
+    if {$state == "normal"} {
+       $sha1but conf -state normal -relief raised -text "Goto: "
+    } else {
+       $sha1but conf -state disabled -relief flat -text "SHA1 ID: "
+    }
+}
+
+proc gotocommit {} {
+    global sha1string currentid commitrow tagids headids
+    global displayorder numcommits curview
+
+    if {$sha1string == {}
+       || ([info exists currentid] && $sha1string == $currentid)} return
+    if {[info exists tagids($sha1string)]} {
+       set id $tagids($sha1string)
+    } elseif {[info exists headids($sha1string)]} {
+       set id $headids($sha1string)
+    } else {
+       set id [string tolower $sha1string]
+       if {[regexp {^[0-9a-f]{4,39}$} $id]} {
+           set matches {}
+           foreach i $displayorder {
+               if {[string match $id* $i]} {
+                   lappend matches $i
+               }
+           }
+           if {$matches ne {}} {
+               if {[llength $matches] > 1} {
+                   error_popup "Short SHA1 id $id is ambiguous"
+                   return
+               }
+               set id [lindex $matches 0]
+           }
+       }
+    }
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
+       return
+    }
+    if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
+       set type "SHA1 id"
+    } else {
+       set type "Tag/Head"
+    }
+    error_popup "$type $sha1string is not known"
+}
+
+proc lineenter {x y id} {
+    global hoverx hovery hoverid hovertimer
+    global commitinfo canv
+
+    if {![info exists commitinfo($id)] && ![getcommit $id]} return
+    set hoverx $x
+    set hovery $y
+    set hoverid $id
+    if {[info exists hovertimer]} {
+       after cancel $hovertimer
+    }
+    set hovertimer [after 500 linehover]
+    $canv delete hover
+}
+
+proc linemotion {x y id} {
+    global hoverx hovery hoverid hovertimer
+
+    if {[info exists hoverid] && $id == $hoverid} {
+       set hoverx $x
+       set hovery $y
+       if {[info exists hovertimer]} {
+           after cancel $hovertimer
+       }
+       set hovertimer [after 500 linehover]
+    }
+}
+
+proc lineleave {id} {
+    global hoverid hovertimer canv
+
+    if {[info exists hoverid] && $id == $hoverid} {
+       $canv delete hover
+       if {[info exists hovertimer]} {
+           after cancel $hovertimer
+           unset hovertimer
+       }
+       unset hoverid
+    }
+}
+
+proc linehover {} {
+    global hoverx hovery hoverid hovertimer
+    global canv linespc lthickness
+    global commitinfo
+
+    set text [lindex $commitinfo($hoverid) 0]
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax == {}} return
+    set yfrac [lindex [$canv yview] 0]
+    set x [expr {$hoverx + 2 * $linespc}]
+    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 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]
+    $canv raise $t
+}
+
+proc clickisonarrow {id y} {
+    global lthickness
+
+    set ranges [rowranges $id]
+    set thresh [expr {2 * $lthickness + 6}]
+    set n [expr {[llength $ranges] - 1}]
+    for {set i 1} {$i < $n} {incr i} {
+       set row [lindex $ranges $i]
+       if {abs([yc $row] - $y) < $thresh} {
+           return $i
+       }
+    }
+    return {}
+}
+
+proc arrowjump {id n y} {
+    global canv
+
+    # 1 <-> 2, 3 <-> 4, etc...
+    set n [expr {(($n - 1) ^ 1) + 1}]
+    set row [lindex [rowranges $id] $n]
+    set yt [yc $row]
+    set ymax [lindex [$canv cget -scrollregion] 3]
+    if {$ymax eq {} || $ymax <= 0} return
+    set view [$canv yview]
+    set yspan [expr {[lindex $view 1] - [lindex $view 0]}]
+    set yfrac [expr {$yt / $ymax - $yspan / 2}]
+    if {$yfrac < 0} {
+       set yfrac 0
+    }
+    allcanvs yview moveto $yfrac
+}
+
+proc lineclick {x y id isnew} {
+    global ctext commitinfo children canv thickerline curview commitrow
+
+    if {![info exists commitinfo($id)] && ![getcommit $id]} return
+    unmarkmatches
+    unselectline
+    normalline
+    $canv delete hover
+    # draw this line thicker than normal
+    set thickerline $id
+    drawlines $id
+    if {$isnew} {
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       if {$ymax eq {}} return
+       set yfrac [lindex [$canv yview] 0]
+       set y [expr {$y + $yfrac * $ymax}]
+    }
+    set dirn [clickisonarrow $id $y]
+    if {$dirn ne {}} {
+       arrowjump $id $dirn $y
+       return
+    }
+
+    if {$isnew} {
+       addtohistory [list lineclick $x $y $id 0]
+    }
+    # fill the details pane with info about this line
+    $ctext conf -state normal
+    clear_ctext
+    settabs 0
+    $ctext insert end "Parent:\t"
+    $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"
+    set date [formatdate [lindex $info 2]]
+    $ctext insert end "\tDate:\t$date\n"
+    set kids $children($curview,$id)
+    if {$kids ne {}} {
+       $ctext insert end "\nChildren:"
+       set i 0
+       foreach child $kids {
+           incr i
+           if {![info exists commitinfo($child)] && ![getcommit $child]} continue
+           set info $commitinfo($child)
+           $ctext insert end "\n\t"
+           $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]]
+           $ctext insert end "\n\tDate:\t$date\n"
+       }
+    }
+    $ctext conf -state disabled
+    init_flist {}
+}
+
+proc normalline {} {
+    global thickerline
+    if {[info exists thickerline]} {
+       set id $thickerline
+       unset thickerline
+       drawlines $id
+    }
+}
+
+proc selbyid {id} {
+    global commitrow curview
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
+    }
+}
+
+proc mstime {} {
+    global startmstime
+    if {![info exists startmstime]} {
+       set startmstime [clock clicks -milliseconds]
+    }
+    return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
+}
+
+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} {
+       set state disabled
+    } else {
+       set state normal
+    }
+    if {$id ne $nullid && $id ne $nullid2} {
+       set menu $rowctxmenu
+       $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+    } else {
+       set menu $fakerowmenu
+    }
+    $menu entryconfigure "Diff this*" -state $state
+    $menu entryconfigure "Diff selected*" -state $state
+    $menu entryconfigure "Make patch" -state $state
+    tk_popup $menu $x $y
+}
+
+proc diffvssel {dirn} {
+    global rowmenuid selectedline displayorder
+
+    if {![info exists selectedline]} return
+    if {$dirn} {
+       set oldid [lindex $displayorder $selectedline]
+       set newid $rowmenuid
+    } else {
+       set oldid $rowmenuid
+       set newid [lindex $displayorder $selectedline]
+    }
+    addtohistory [list doseldiff $oldid $newid]
+    doseldiff $oldid $newid
+}
+
+proc doseldiff {oldid newid} {
+    global ctext
+    global commitinfo
+
+    $ctext conf -state normal
+    clear_ctext
+    init_flist "Top"
+    $ctext insert end "From "
+    $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 insert end $newid link1
+    setlink $newid link1
+    $ctext insert end "\n     "
+    $ctext insert end [lindex $commitinfo($newid) 0]
+    $ctext insert end "\n"
+    $ctext conf -state disabled
+    $ctext tag remove found 1.0 end
+    startdiff [list $oldid $newid]
+}
+
+proc mkpatch {} {
+    global rowmenuid currentid commitinfo patchtop patchnum
+
+    if {![info exists currentid]} return
+    set oldid $currentid
+    set oldhead [lindex $commitinfo($oldid) 0]
+    set newid $rowmenuid
+    set newhead [lindex $commitinfo($newid) 0]
+    set top .patch
+    set patchtop $top
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Generate patch"
+    grid $top.title - -pady 10
+    label $top.from -text "From:"
+    entry $top.fromsha1 -width 40 -relief flat
+    $top.fromsha1 insert 0 $oldid
+    $top.fromsha1 conf -state readonly
+    grid $top.from $top.fromsha1 -sticky w
+    entry $top.fromhead -width 60 -relief flat
+    $top.fromhead insert 0 $oldhead
+    $top.fromhead conf -state readonly
+    grid x $top.fromhead -sticky w
+    label $top.to -text "To:"
+    entry $top.tosha1 -width 40 -relief flat
+    $top.tosha1 insert 0 $newid
+    $top.tosha1 conf -state readonly
+    grid $top.to $top.tosha1 -sticky w
+    entry $top.tohead -width 60 -relief flat
+    $top.tohead insert 0 $newhead
+    $top.tohead conf -state readonly
+    grid x $top.tohead -sticky w
+    button $top.rev -text "Reverse" -command mkpatchrev -padx 5
+    grid $top.rev x -pady 10
+    label $top.flab -text "Output file:"
+    entry $top.fname -width 60
+    $top.fname insert 0 [file normalize "patch$patchnum.patch"]
+    incr patchnum
+    grid $top.flab $top.fname -sticky w
+    frame $top.buts
+    button $top.buts.gen -text "Generate" -command mkpatchgo
+    button $top.buts.can -text "Cancel" -command mkpatchcan
+    grid $top.buts.gen $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.fname
+}
+
+proc mkpatchrev {} {
+    global patchtop
+
+    set oldid [$patchtop.fromsha1 get]
+    set oldhead [$patchtop.fromhead get]
+    set newid [$patchtop.tosha1 get]
+    set newhead [$patchtop.tohead get]
+    foreach e [list fromsha1 fromhead tosha1 tohead] \
+           v [list $newid $newhead $oldid $oldhead] {
+       $patchtop.$e conf -state normal
+       $patchtop.$e delete 0 end
+       $patchtop.$e insert 0 $v
+       $patchtop.$e conf -state readonly
+    }
+}
+
+proc mkpatchgo {} {
+    global patchtop nullid nullid2
+
+    set oldid [$patchtop.fromsha1 get]
+    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"
+    }
+    catch {destroy $patchtop}
+    unset patchtop
+}
+
+proc mkpatchcan {} {
+    global patchtop
+
+    catch {destroy $patchtop}
+    unset patchtop
+}
+
+proc mktag {} {
+    global rowmenuid mktagtop commitinfo
+
+    set top .maketag
+    set mktagtop $top
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Create tag"
+    grid $top.title - -pady 10
+    label $top.id -text "ID:"
+    entry $top.sha1 -width 40 -relief flat
+    $top.sha1 insert 0 $rowmenuid
+    $top.sha1 conf -state readonly
+    grid $top.id $top.sha1 -sticky w
+    entry $top.head -width 60 -relief flat
+    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
+    $top.head conf -state readonly
+    grid x $top.head -sticky w
+    label $top.tlab -text "Tag name:"
+    entry $top.tag -width 60
+    grid $top.tlab $top.tag -sticky w
+    frame $top.buts
+    button $top.buts.gen -text "Create" -command mktaggo
+    button $top.buts.can -text "Cancel" -command mktagcan
+    grid $top.buts.gen $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.tag
+}
+
+proc domktag {} {
+    global mktagtop env tagids idtags
+
+    set id [$mktagtop.sha1 get]
+    set tag [$mktagtop.tag get]
+    if {$tag == {}} {
+       error_popup "No tag name specified"
+       return
+    }
+    if {[info exists tagids($tag)]} {
+       error_popup "Tag \"$tag\" already exists"
+       return
+    }
+    if {[catch {
+       set dir [gitdir]
+       set fname [file join $dir "refs/tags" $tag]
+       set f [open $fname w]
+       puts $f $id
+       close $f
+    } err]} {
+       error_popup "Error creating tag: $err"
+       return
+    }
+
+    set tagids($tag) $id
+    lappend idtags($id) $tag
+    redrawtags $id
+    addedtag $id
+    dispneartags 0
+    run refill_reflist
+}
+
+proc redrawtags {id} {
+    global canv linehtag commitrow idpos selectedline curview
+    global canvxmax iddrawn
+
+    if {![info exists commitrow($curview,$id)]} return
+    if {![info exists iddrawn($id)]} return
+    drawcommits $commitrow($curview,$id)
+    $canv delete tag.$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]}]
+    if {$xr > $canvxmax} {
+       set canvxmax $xr
+       setcanvscroll
+    }
+    if {[info exists selectedline]
+       && $selectedline == $commitrow($curview,$id)} {
+       selectline $selectedline 0
+    }
+}
+
+proc mktagcan {} {
+    global mktagtop
+
+    catch {destroy $mktagtop}
+    unset mktagtop
+}
+
+proc mktaggo {} {
+    domktag
+    mktagcan
+}
+
+proc writecommit {} {
+    global rowmenuid wrcomtop commitinfo wrcomcmd
+
+    set top .writecommit
+    set wrcomtop $top
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Write commit to file"
+    grid $top.title - -pady 10
+    label $top.id -text "ID:"
+    entry $top.sha1 -width 40 -relief flat
+    $top.sha1 insert 0 $rowmenuid
+    $top.sha1 conf -state readonly
+    grid $top.id $top.sha1 -sticky w
+    entry $top.head -width 60 -relief flat
+    $top.head insert 0 [lindex $commitinfo($rowmenuid) 0]
+    $top.head conf -state readonly
+    grid x $top.head -sticky w
+    label $top.clab -text "Command:"
+    entry $top.cmd -width 60 -textvariable wrcomcmd
+    grid $top.clab $top.cmd -sticky w -pady 10
+    label $top.flab -text "Output file:"
+    entry $top.fname -width 60
+    $top.fname insert 0 [file normalize "commit-[string range $rowmenuid 0 6]"]
+    grid $top.flab $top.fname -sticky w
+    frame $top.buts
+    button $top.buts.gen -text "Write" -command wrcomgo
+    button $top.buts.can -text "Cancel" -command wrcomcan
+    grid $top.buts.gen $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.fname
+}
+
+proc wrcomgo {} {
+    global wrcomtop
+
+    set id [$wrcomtop.sha1 get]
+    set cmd "echo $id | [$wrcomtop.cmd get]"
+    set fname [$wrcomtop.fname get]
+    if {[catch {exec sh -c $cmd >$fname &} err]} {
+       error_popup "Error writing commit: $err"
+    }
+    catch {destroy $wrcomtop}
+    unset wrcomtop
+}
+
+proc wrcomcan {} {
+    global wrcomtop
+
+    catch {destroy $wrcomtop}
+    unset wrcomtop
+}
+
+proc mkbranch {} {
+    global rowmenuid mkbrtop
+
+    set top .makebranch
+    catch {destroy $top}
+    toplevel $top
+    label $top.title -text "Create new branch"
+    grid $top.title - -pady 10
+    label $top.id -text "ID:"
+    entry $top.sha1 -width 40 -relief flat
+    $top.sha1 insert 0 $rowmenuid
+    $top.sha1 conf -state readonly
+    grid $top.id $top.sha1 -sticky w
+    label $top.nlab -text "Name:"
+    entry $top.name -width 40
+    grid $top.nlab $top.name -sticky w
+    frame $top.buts
+    button $top.buts.go -text "Create" -command [list mkbrgo $top]
+    button $top.buts.can -text "Cancel" -command "catch {destroy $top}"
+    grid $top.buts.go $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.name
+}
+
+proc mkbrgo {top} {
+    global headids idheads
+
+    set name [$top.name get]
+    set id [$top.sha1 get]
+    if {$name eq {}} {
+       error_popup "Please specify a name for the new branch"
+       return
+    }
+    catch {destroy $top}
+    nowbusy newbranch
+    update
+    if {[catch {
+       exec git branch $name $id
+    } err]} {
+       notbusy newbranch
+       error_popup $err
+    } else {
+       set headids($name) $id
+       lappend idheads($id) $name
+       addedhead $id $name
+       notbusy newbranch
+       redrawtags $id
+       dispneartags 0
+       run refill_reflist
+    }
+}
+
+proc cherrypick {} {
+    global rowmenuid curview commitrow
+    global mainhead
+
+    set oldhead [exec git rev-parse HEAD]
+    set dheads [descheads $rowmenuid]
+    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
+       set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
+                       included in branch $mainhead -- really re-apply it?"]
+       if {!$ok} return
+    }
+    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...
+    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
+       notbusy cherrypick
+       error_popup $err
+       return
+    }
+    set newhead [exec git rev-parse HEAD]
+    if {$newhead eq $oldhead} {
+       notbusy cherrypick
+       error_popup "No changes committed"
+       return
+    }
+    addnewchild $newhead $oldhead
+    if {[info exists commitrow($curview,$oldhead)]} {
+       insertrow $commitrow($curview,$oldhead) $newhead
+       if {$mainhead ne {}} {
+           movehead $newhead $mainhead
+           movedhead $newhead $mainhead
+       }
+       redrawtags $oldhead
+       redrawtags $newhead
+    }
+    notbusy cherrypick
+}
+
+proc resethead {} {
+    global mainheadid mainhead rowmenuid confirm_ok resettype
+
+    set confirm_ok 0
+    set w ".confirmreset"
+    toplevel $w
+    wm transient $w .
+    wm title $w "Confirm reset"
+    message $w.m -text \
+       "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+       -justify center -aspect 1000
+    pack $w.m -side top -fill x -padx 20 -pady 20
+    frame $w.f -relief sunken -border 2
+    message $w.f.rt -text "Reset type:" -aspect 1000
+    grid $w.f.rt -sticky w
+    set resettype mixed
+    radiobutton $w.f.soft -value soft -variable resettype -justify left \
+       -text "Soft: Leave working tree and index untouched"
+    grid $w.f.soft -sticky w
+    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+       -text "Mixed: Leave working tree untouched, reset index"
+    grid $w.f.mixed -sticky w
+    radiobutton $w.f.hard -value hard -variable resettype -justify left \
+       -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+    grid $w.f.hard -sticky w
+    pack $w.f -side top -fill x
+    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+    pack $w.ok -side left -fill x -padx 20 -pady 20
+    button $w.cancel -text Cancel -command "destroy $w"
+    pack $w.cancel -side right -fill x -padx 20 -pady 20
+    bind $w <Visibility> "grab $w; focus $w"
+    tkwait window $w
+    if {!$confirm_ok} return
+    if {[catch {set fd [open \
+           [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+       error_popup $err
+    } else {
+       dohidelocalchanges
+       filerun $fd [list readresetstat $fd]
+       nowbusy reset "Resetting"
+    }
+}
+
+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 rprogcoord [expr {1.0 * $m / $n}]
+           adjustprogress
+       }
+       return 1
+    }
+    set rprogcoord 0
+    adjustprogress
+    notbusy reset
+    if {[catch {close $fd} err]} {
+       error_popup $err
+    }
+    set oldhead $mainheadid
+    set newhead [exec git rev-parse HEAD]
+    if {$newhead ne $oldhead} {
+       movehead $newhead $mainhead
+       movedhead $newhead $mainhead
+       set mainheadid $newhead
+       redrawtags $oldhead
+       redrawtags $newhead
+    }
+    if {$showlocalchanges} {
+       doshowlocalchanges
+    }
+    return 0
+}
+
+# context menu for a head
+proc headmenu {x y id head} {
+    global headmenuid headmenuhead headctxmenu mainhead
+
+    stopfinding
+    set headmenuid $id
+    set headmenuhead $head
+    set state normal
+    if {$head eq $mainhead} {
+       set state disabled
+    }
+    $headctxmenu entryconfigure 0 -state $state
+    $headctxmenu entryconfigure 1 -state $state
+    tk_popup $headctxmenu $x $y
+}
+
+proc cobranch {} {
+    global headmenuid headmenuhead mainhead headids
+    global showlocalchanges mainheadid
+
+    # check the tree is clean first??
+    set oldmainhead $mainhead
+    nowbusy checkout "Checking out"
+    update
+    dohidelocalchanges
+    if {[catch {
+       exec git checkout -q $headmenuhead
+    } err]} {
+       notbusy checkout
+       error_popup $err
+    } else {
+       notbusy checkout
+       set mainhead $headmenuhead
+       set mainheadid $headmenuid
+       if {[info exists headids($oldmainhead)]} {
+           redrawtags $headids($oldmainhead)
+       }
+       redrawtags $headmenuid
+    }
+    if {$showlocalchanges} {
+       dodiffindex
+    }
+}
+
+proc rmbranch {} {
+    global headmenuid headmenuhead mainhead
+    global idheads
+
+    set head $headmenuhead
+    set id $headmenuid
+    # this check shouldn't be needed any more...
+    if {$head eq $mainhead} {
+       error_popup "Cannot delete the currently checked-out branch"
+       return
+    }
+    set dheads [descheads $id]
+    if {[llength $dheads] == 1 && $idheads($dheads) eq $head} {
+       # the stuff on this branch isn't on any other branch
+       if {![confirm_popup "The commits on branch $head aren't on any other\
+                       branch.\nReally delete branch $head?"]} return
+    }
+    nowbusy rmbranch
+    update
+    if {[catch {exec git branch -D $head} err]} {
+       notbusy rmbranch
+       error_popup $err
+       return
+    }
+    removehead $id $head
+    removedhead $id $head
+    redrawtags $id
+    notbusy rmbranch
+    dispneartags 0
+    run refill_reflist
+}
+
+# Display a list of tags and heads
+proc showrefs {} {
+    global showrefstop bgcolor fgcolor selectbgcolor
+    global bglist fglist reflistfilter reflist maincursor
+
+    set top .showrefs
+    set showrefstop $top
+    if {[winfo exists $top]} {
+       raise $top
+       refill_reflist
+       return
+    }
+    toplevel $top
+    wm title $top "Tags and heads: [file tail [pwd]]"
+    text $top.list -background $bgcolor -foreground $fgcolor \
+       -selectbackground $selectbgcolor -font mainfont \
+       -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \
+       -width 30 -height 20 -cursor $maincursor \
+       -spacing1 1 -spacing3 1 -state disabled
+    $top.list tag configure highlight -background $selectbgcolor
+    lappend bglist $top.list
+    lappend fglist $top.list
+    scrollbar $top.ysb -command "$top.list yview" -orient vertical
+    scrollbar $top.xsb -command "$top.list xview" -orient horizontal
+    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
+    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
+    grid $top.close -
+    grid columnconfigure $top 0 -weight 1
+    grid rowconfigure $top 0 -weight 1
+    bind $top.list <1> {break}
+    bind $top.list <B1-Motion> {break}
+    bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break}
+    set reflist {}
+    refill_reflist
+}
+
+proc sel_reflist {w x y} {
+    global showrefstop reflist headids tagids otherrefids
+
+    if {![winfo exists $showrefstop]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    set ref [lindex $reflist [expr {$l-1}]]
+    set n [lindex $ref 0]
+    switch -- [lindex $ref 1] {
+       "H" {selbyid $headids($n)}
+       "T" {selbyid $tagids($n)}
+       "o" {selbyid $otherrefids($n)}
+    }
+    $showrefstop.list tag add highlight $l.0 "$l.0 lineend"
+}
+
+proc unsel_reflist {} {
+    global showrefstop
+
+    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
+    $showrefstop.list tag remove highlight 0.0 end
+}
+
+proc reflistfilter_change {n1 n2 op} {
+    global reflistfilter
+
+    after cancel refill_reflist
+    after 200 refill_reflist
+}
+
+proc refill_reflist {} {
+    global reflist reflistfilter showrefstop headids tagids otherrefids
+    global commitrow curview commitinterest
+
+    if {![info exists showrefstop] || ![winfo exists $showrefstop]} return
+    set refs {}
+    foreach n [array names headids] {
+       if {[string match $reflistfilter $n]} {
+           if {[info exists commitrow($curview,$headids($n))]} {
+               lappend refs [list $n H]
+           } else {
+               set commitinterest($headids($n)) {run refill_reflist}
+           }
+       }
+    }
+    foreach n [array names tagids] {
+       if {[string match $reflistfilter $n]} {
+           if {[info exists commitrow($curview,$tagids($n))]} {
+               lappend refs [list $n T]
+           } else {
+               set commitinterest($tagids($n)) {run refill_reflist}
+           }
+       }
+    }
+    foreach n [array names otherrefids] {
+       if {[string match $reflistfilter $n]} {
+           if {[info exists commitrow($curview,$otherrefids($n))]} {
+               lappend refs [list $n o]
+           } else {
+               set commitinterest($otherrefids($n)) {run refill_reflist}
+           }
+       }
+    }
+    set refs [lsort -index 0 $refs]
+    if {$refs eq $reflist} return
+
+    # Update the contents of $showrefstop.list according to the
+    # differences between $reflist (old) and $refs (new)
+    $showrefstop.list conf -state normal
+    $showrefstop.list insert end "\n"
+    set i 0
+    set j 0
+    while {$i < [llength $reflist] || $j < [llength $refs]} {
+       if {$i < [llength $reflist]} {
+           if {$j < [llength $refs]} {
+               set cmp [string compare [lindex $reflist $i 0] \
+                            [lindex $refs $j 0]]
+               if {$cmp == 0} {
+                   set cmp [string compare [lindex $reflist $i 1] \
+                                [lindex $refs $j 1]]
+               }
+           } else {
+               set cmp -1
+           }
+       } else {
+           set cmp 1
+       }
+       switch -- $cmp {
+           -1 {
+               $showrefstop.list delete "[expr {$j+1}].0" "[expr {$j+2}].0"
+               incr i
+           }
+           0 {
+               incr i
+               incr j
+           }
+           1 {
+               set l [expr {$j + 1}]
+               $showrefstop.list image create $l.0 -align baseline \
+                   -image reficon-[lindex $refs $j 1] -padx 2
+               $showrefstop.list insert $l.1 "[lindex $refs $j 0]\n"
+               incr j
+           }
+       }
+    }
+    set reflist $refs
+    # delete last newline
+    $showrefstop.list delete end-2c end-1c
+    $showrefstop.list conf -state disabled
+}
+
+# Stuff for finding nearby tags
+proc getallcommits {} {
+    global allcommits nextarc seeds allccache allcwait cachedarcs allcupdate
+    global idheads idtags idotherrefs allparents tagobjid
+
+    if {![info exists allcommits]} {
+       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
+    }
+
+    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
+    }
+}
+
+# Since most commits have 1 parent and 1 child, we group strings of
+# such commits into "arcs" joining branch/merge points (BMPs), which
+# are commits that either don't have 1 parent or don't have 1 child.
+#
+# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
+# arcout(id) - outgoing arcs for BMP
+# arcids(a) - list of IDs on arc including end but not start
+# arcstart(a) - BMP ID at start of arc
+# arcend(a) - BMP ID at end of arc
+# growing(a) - arc a is still growing
+# arctags(a) - IDs out of arcids (excluding end) that have tags
+# archeads(a) - IDs out of arcids (excluding end) that have heads
+# The start of an arc is at the descendent end, so "incoming" means
+# coming from descendents, and "outgoing" means going towards ancestors.
+
+proc getallclines {fd} {
+    global allparents allchildren idtags idheads nextarc
+    global arcnos arcids arctags arcout arcend arcstart archeads growing
+    global seeds allcommits cachedarcs allcupdate
+    
+    set nid 0
+    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
+       set id [lindex $line 0]
+       if {[info exists allparents($id)]} {
+           # seen it already
+           continue
+       }
+       set cachedarcs 0
+       set olds [lrange $line 1 end]
+       set allparents($id) $olds
+       if {![info exists allchildren($id)]} {
+           set allchildren($id) {}
+           set arcnos($id) {}
+           lappend seeds $id
+       } else {
+           set a $arcnos($id)
+           if {[llength $olds] == 1 && [llength $a] == 1} {
+               lappend arcids($a) $id
+               if {[info exists idtags($id)]} {
+                   lappend arctags($a) $id
+               }
+               if {[info exists idheads($id)]} {
+                   lappend archeads($a) $id
+               }
+               if {[info exists allparents($olds)]} {
+                   # seen parent already
+                   if {![info exists arcout($olds)]} {
+                       splitarc $olds
+                   }
+                   lappend arcids($a) $olds
+                   set arcend($a) $olds
+                   unset growing($a)
+               }
+               lappend allchildren($olds) $id
+               lappend arcnos($olds) $a
+               continue
+           }
+       }
+       foreach a $arcnos($id) {
+           lappend arcids($a) $id
+           set arcend($a) $id
+           unset growing($a)
+       }
+
+       set ao {}
+       foreach p $olds {
+           lappend allchildren($p) $id
+           set a [incr nextarc]
+           set arcstart($a) $id
+           set archeads($a) {}
+           set arctags($a) {}
+           set archeads($a) {}
+           set arcids($a) {}
+           lappend ao $a
+           set growing($a) 1
+           if {[info exists allparents($p)]} {
+               # seen it already, may need to make a new branch
+               if {![info exists arcout($p)]} {
+                   splitarc $p
+               }
+               lappend arcids($a) $p
+               set arcend($a) $p
+               unset growing($a)
+           }
+           lappend arcnos($p) $a
+       }
+       set arcout($id) $ao
+    }
+    if {$nid > 0} {
+       global cached_dheads cached_dtags cached_atags
+       catch {unset cached_dheads}
+       catch {unset cached_dtags}
+       catch {unset cached_atags}
+    }
+    if {![eof $fd]} {
+       return [expr {$nid >= 1000? 2: 1}]
+    }
+    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
+}
+
+proc recalcarc {a} {
+    global arctags archeads arcids idtags idheads
+
+    set at {}
+    set ah {}
+    foreach id [lrange $arcids($a) 0 end-1] {
+       if {[info exists idtags($id)]} {
+           lappend at $id
+       }
+       if {[info exists idheads($id)]} {
+           lappend ah $id
+       }
+    }
+    set arctags($a) $at
+    set archeads($a) $ah
+}
+
+proc splitarc {p} {
+    global arcnos arcids nextarc arctags archeads idtags idheads
+    global arcstart arcend arcout allparents growing
+
+    set a $arcnos($p)
+    if {[llength $a] != 1} {
+       puts "oops splitarc called but [llength $a] arcs already"
+       return
+    }
+    set a [lindex $a 0]
+    set i [lsearch -exact $arcids($a) $p]
+    if {$i < 0} {
+       puts "oops splitarc $p not in arc $a"
+       return
+    }
+    set na [incr nextarc]
+    if {[info exists arcend($a)]} {
+       set arcend($na) $arcend($a)
+    } else {
+       set l [lindex $allparents([lindex $arcids($a) end]) 0]
+       set j [lsearch -exact $arcnos($l) $a]
+       set arcnos($l) [lreplace $arcnos($l) $j $j $na]
+    }
+    set tail [lrange $arcids($a) [expr {$i+1}] end]
+    set arcids($a) [lrange $arcids($a) 0 $i]
+    set arcend($a) $p
+    set arcstart($na) $p
+    set arcout($p) $na
+    set arcids($na) $tail
+    if {[info exists growing($a)]} {
+       set growing($na) 1
+       unset growing($a)
+    }
+
+    foreach id $tail {
+       if {[llength $arcnos($id)] == 1} {
+           set arcnos($id) $na
+       } else {
+           set j [lsearch -exact $arcnos($id) $a]
+           set arcnos($id) [lreplace $arcnos($id) $j $j $na]
+       }
+    }
+
+    # reconstruct tags and heads lists
+    if {$arctags($a) ne {} || $archeads($a) ne {}} {
+       recalcarc $a
+       recalcarc $na
+    } else {
+       set arctags($na) {}
+       set archeads($na) {}
+    }
+}
+
+# Update things for a new commit added that is a child of one
+# existing commit.  Used when cherry-picking.
+proc addnewchild {id p} {
+    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
+    set allparents($id) [list $p]
+    set allchildren($id) {}
+    set arcnos($id) {}
+    lappend seeds $id
+    lappend allchildren($p) $id
+    set a [incr nextarc]
+    set arcstart($a) $id
+    set archeads($a) {}
+    set arctags($a) {}
+    set arcids($a) [list $p]
+    set arcend($a) $p
+    if {![info exists arcout($p)]} {
+       splitarc $p
+    }
+    lappend arcnos($p) $a
+    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} {
+    global arcout arcstart arcend arcnos cached_isanc
+
+    if {$arcnos($a) eq $arcnos($b)} {
+       # Both are on the same arc(s); either both are the same BMP,
+       # or if one is not a BMP, the other is also not a BMP or is
+       # the BMP at end of the arc (and it only has 1 incoming arc).
+       # Or both can be BMPs with no incoming arcs.
+       if {$a eq $b || $arcnos($a) eq {}} {
+           return 0
+       }
+       # assert {[llength $arcnos($a)] == 1}
+       set arc [lindex $arcnos($a) 0]
+       set i [lsearch -exact $arcids($arc) $a]
+       set j [lsearch -exact $arcids($arc) $b]
+       if {$i < 0 || $i > $j} {
+           return 1
+       } else {
+           return -1
+       }
+    }
+
+    if {![info exists arcout($a)]} {
+       set arc [lindex $arcnos($a) 0]
+       if {[info exists arcend($arc)]} {
+           set aend $arcend($arc)
+       } else {
+           set aend {}
+       }
+       set a $arcstart($arc)
+    } else {
+       set aend $a
+    }
+    if {![info exists arcout($b)]} {
+       set arc [lindex $arcnos($b) 0]
+       if {[info exists arcend($arc)]} {
+           set bend $arcend($arc)
+       } else {
+           set bend {}
+       }
+       set b $arcstart($arc)
+    } else {
+       set bend $b
+    }
+    if {$a eq $bend} {
+       return 1
+    }
+    if {$b eq $aend} {
+       return -1
+    }
+    if {[info exists cached_isanc($a,$bend)]} {
+       if {$cached_isanc($a,$bend)} {
+           return 1
+       }
+    }
+    if {[info exists cached_isanc($b,$aend)]} {
+       if {$cached_isanc($b,$aend)} {
+           return -1
+       }
+       if {[info exists cached_isanc($a,$bend)]} {
+           return 0
+       }
+    }
+
+    set todo [list $a $b]
+    set anc($a) a
+    set anc($b) b
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set x [lindex $todo $i]
+       if {$anc($x) eq {}} {
+           continue
+       }
+       foreach arc $arcnos($x) {
+           set xd $arcstart($arc)
+           if {$xd eq $bend} {
+               set cached_isanc($a,$bend) 1
+               set cached_isanc($b,$aend) 0
+               return 1
+           } elseif {$xd eq $aend} {
+               set cached_isanc($b,$aend) 1
+               set cached_isanc($a,$bend) 0
+               return -1
+           }
+           if {![info exists anc($xd)]} {
+               set anc($xd) $anc($x)
+               lappend todo $xd
+           } elseif {$anc($xd) ne $anc($x)} {
+               set anc($xd) {}
+           }
+       }
+    }
+    set cached_isanc($a,$bend) 0
+    set cached_isanc($b,$aend) 0
+    return 0
+}
+
+# This identifies whether $desc has an ancestor that is
+# a growing tip of the graph and which is not an ancestor of $anc
+# and returns 0 if so and 1 if not.
+# If we subsequently discover a tag on such a growing tip, and that
+# turns out to be a descendent of $anc (which it could, since we
+# don't necessarily see children before parents), then $desc
+# isn't a good choice to display as a descendent tag of
+# $anc (since it is the descendent of another tag which is
+# a descendent of $anc).  Similarly, $anc isn't a good choice to
+# display as a ancestor tag of $desc.
+#
+proc is_certain {desc anc} {
+    global arcnos arcout arcstart arcend growing problems
+
+    set certain {}
+    if {[llength $arcnos($anc)] == 1} {
+       # tags on the same arc are certain
+       if {$arcnos($desc) eq $arcnos($anc)} {
+           return 1
+       }
+       if {![info exists arcout($anc)]} {
+           # if $anc is partway along an arc, use the start of the arc instead
+           set a [lindex $arcnos($anc) 0]
+           set anc $arcstart($a)
+       }
+    }
+    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
+       set x $desc
+    } else {
+       set a [lindex $arcnos($desc) 0]
+       set x $arcend($a)
+    }
+    if {$x == $anc} {
+       return 1
+    }
+    set anclist [list $x]
+    set dl($x) 1
+    set nnh 1
+    set ngrowanc 0
+    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
+       set x [lindex $anclist $i]
+       if {$dl($x)} {
+           incr nnh -1
+       }
+       set done($x) 1
+       foreach a $arcout($x) {
+           if {[info exists growing($a)]} {
+               if {![info exists growanc($x)] && $dl($x)} {
+                   set growanc($x) 1
+                   incr ngrowanc
+               }
+           } else {
+               set y $arcend($a)
+               if {[info exists dl($y)]} {
+                   if {$dl($y)} {
+                       if {!$dl($x)} {
+                           set dl($y) 0
+                           if {![info exists done($y)]} {
+                               incr nnh -1
+                           }
+                           if {[info exists growanc($x)]} {
+                               incr ngrowanc -1
+                           }
+                           set xl [list $y]
+                           for {set k 0} {$k < [llength $xl]} {incr k} {
+                               set z [lindex $xl $k]
+                               foreach c $arcout($z) {
+                                   if {[info exists arcend($c)]} {
+                                       set v $arcend($c)
+                                       if {[info exists dl($v)] && $dl($v)} {
+                                           set dl($v) 0
+                                           if {![info exists done($v)]} {
+                                               incr nnh -1
+                                           }
+                                           if {[info exists growanc($v)]} {
+                                               incr ngrowanc -1
+                                           }
+                                           lappend xl $v
+                                       }
+                                   }
+                               }
+                           }
+                       }
+                   }
+               } elseif {$y eq $anc || !$dl($x)} {
+                   set dl($y) 0
+                   lappend anclist $y
+               } else {
+                   set dl($y) 1
+                   lappend anclist $y
+                   incr nnh
+               }
+           }
+       }
+    }
+    foreach x [array names growanc] {
+       if {$dl($x)} {
+           return 0
+       }
+       return 0
+    }
+    return 1
+}
+
+proc validate_arctags {a} {
+    global arctags idtags
+
+    set i -1
+    set na $arctags($a)
+    foreach id $arctags($a) {
+       incr i
+       if {![info exists idtags($id)]} {
+           set na [lreplace $na $i $i]
+           incr i -1
+       }
+    }
+    set arctags($a) $na
+}
+
+proc validate_archeads {a} {
+    global archeads idheads
+
+    set i -1
+    set na $archeads($a)
+    foreach id $archeads($a) {
+       incr i
+       if {![info exists idheads($id)]} {
+           set na [lreplace $na $i $i]
+           incr i -1
+       }
+    }
+    set archeads($a) $na
+}
+
+# Return the list of IDs that have tags that are descendents of id,
+# ignoring IDs that are descendents of IDs already reported.
+proc desctags {id} {
+    global arcnos arcstart arcids arctags idtags allparents
+    global growing cached_dtags
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set t1 [clock clicks -milliseconds]
+    set argid $id
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check that arc first
+       set a [lindex $arcnos($id) 0]
+       if {$arctags($a) ne {}} {
+           validate_arctags $a
+           set i [lsearch -exact $arcids($a) $id]
+           set tid {}
+           foreach t $arctags($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j >= $i} break
+               set tid $t
+           }
+           if {$tid ne {}} {
+               return $tid
+           }
+       }
+       set id $arcstart($a)
+       if {[info exists idtags($id)]} {
+           return $id
+       }
+    }
+    if {[info exists cached_dtags($id)]} {
+       return $cached_dtags($id)
+    }
+
+    set origid $id
+    set todo [list $id]
+    set queued($id) 1
+    set nc 1
+    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+       set id [lindex $todo $i]
+       set done($id) 1
+       set ta [info exists hastaggedancestor($id)]
+       if {!$ta} {
+           incr nc -1
+       }
+       # ignore tags on starting node
+       if {!$ta && $i > 0} {
+           if {[info exists idtags($id)]} {
+               set tagloc($id) $id
+               set ta 1
+           } elseif {[info exists cached_dtags($id)]} {
+               set tagloc($id) $cached_dtags($id)
+               set ta 1
+           }
+       }
+       foreach a $arcnos($id) {
+           set d $arcstart($a)
+           if {!$ta && $arctags($a) ne {}} {
+               validate_arctags $a
+               if {$arctags($a) ne {}} {
+                   lappend tagloc($id) [lindex $arctags($a) end]
+               }
+           }
+           if {$ta || $arctags($a) ne {}} {
+               set tomark [list $d]
+               for {set j 0} {$j < [llength $tomark]} {incr j} {
+                   set dd [lindex $tomark $j]
+                   if {![info exists hastaggedancestor($dd)]} {
+                       if {[info exists done($dd)]} {
+                           foreach b $arcnos($dd) {
+                               lappend tomark $arcstart($b)
+                           }
+                           if {[info exists tagloc($dd)]} {
+                               unset tagloc($dd)
+                           }
+                       } elseif {[info exists queued($dd)]} {
+                           incr nc -1
+                       }
+                       set hastaggedancestor($dd) 1
+                   }
+               }
+           }
+           if {![info exists queued($d)]} {
+               lappend todo $d
+               set queued($d) 1
+               if {![info exists hastaggedancestor($d)]} {
+                   incr nc
+               }
+           }
+       }
+    }
+    set tags {}
+    foreach id [array names tagloc] {
+       if {![info exists hastaggedancestor($id)]} {
+           foreach t $tagloc($id) {
+               if {[lsearch -exact $tags $t] < 0} {
+                   lappend tags $t
+               }
+           }
+       }
+    }
+    set t2 [clock clicks -milliseconds]
+    set loopix $i
+
+    # remove tags that are descendents of other tags
+    for {set i 0} {$i < [llength $tags]} {incr i} {
+       set a [lindex $tags $i]
+       for {set j 0} {$j < $i} {incr j} {
+           set b [lindex $tags $j]
+           set r [anc_or_desc $a $b]
+           if {$r == 1} {
+               set tags [lreplace $tags $j $j]
+               incr j -1
+               incr i -1
+           } elseif {$r == -1} {
+               set tags [lreplace $tags $i $i]
+               incr i -1
+               break
+           }
+       }
+    }
+
+    if {[array names growing] ne {}} {
+       # graph isn't finished, need to check if any tag could get
+       # eclipsed by another tag coming later.  Simply ignore any
+       # tags that could later get eclipsed.
+       set ctags {}
+       foreach t $tags {
+           if {[is_certain $t $origid]} {
+               lappend ctags $t
+           }
+       }
+       if {$tags eq $ctags} {
+           set cached_dtags($origid) $tags
+       } else {
+           set tags $ctags
+       }
+    } else {
+       set cached_dtags($origid) $tags
+    }
+    set t3 [clock clicks -milliseconds]
+    if {0 && $t3 - $t1 >= 100} {
+       puts "iterating descendents ($loopix/[llength $todo] nodes) took\
+           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
+    }
+    return $tags
+}
+
+proc anctags {id} {
+    global arcnos arcids arcout arcend arctags idtags allparents
+    global growing cached_atags
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set t1 [clock clicks -milliseconds]
+    set argid $id
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check that arc first
+       set a [lindex $arcnos($id) 0]
+       if {$arctags($a) ne {}} {
+           validate_arctags $a
+           set i [lsearch -exact $arcids($a) $id]
+           foreach t $arctags($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j > $i} {
+                   return $t
+               }
+           }
+       }
+       if {![info exists arcend($a)]} {
+           return {}
+       }
+       set id $arcend($a)
+       if {[info exists idtags($id)]} {
+           return $id
+       }
+    }
+    if {[info exists cached_atags($id)]} {
+       return $cached_atags($id)
+    }
+
+    set origid $id
+    set todo [list $id]
+    set queued($id) 1
+    set taglist {}
+    set nc 1
+    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+       set id [lindex $todo $i]
+       set done($id) 1
+       set td [info exists hastaggeddescendent($id)]
+       if {!$td} {
+           incr nc -1
+       }
+       # ignore tags on starting node
+       if {!$td && $i > 0} {
+           if {[info exists idtags($id)]} {
+               set tagloc($id) $id
+               set td 1
+           } elseif {[info exists cached_atags($id)]} {
+               set tagloc($id) $cached_atags($id)
+               set td 1
+           }
+       }
+       foreach a $arcout($id) {
+           if {!$td && $arctags($a) ne {}} {
+               validate_arctags $a
+               if {$arctags($a) ne {}} {
+                   lappend tagloc($id) [lindex $arctags($a) 0]
+               }
+           }
+           if {![info exists arcend($a)]} continue
+           set d $arcend($a)
+           if {$td || $arctags($a) ne {}} {
+               set tomark [list $d]
+               for {set j 0} {$j < [llength $tomark]} {incr j} {
+                   set dd [lindex $tomark $j]
+                   if {![info exists hastaggeddescendent($dd)]} {
+                       if {[info exists done($dd)]} {
+                           foreach b $arcout($dd) {
+                               if {[info exists arcend($b)]} {
+                                   lappend tomark $arcend($b)
+                               }
+                           }
+                           if {[info exists tagloc($dd)]} {
+                               unset tagloc($dd)
+                           }
+                       } elseif {[info exists queued($dd)]} {
+                           incr nc -1
+                       }
+                       set hastaggeddescendent($dd) 1
+                   }
+               }
+           }
+           if {![info exists queued($d)]} {
+               lappend todo $d
+               set queued($d) 1
+               if {![info exists hastaggeddescendent($d)]} {
+                   incr nc
+               }
+           }
+       }
+    }
+    set t2 [clock clicks -milliseconds]
+    set loopix $i
+    set tags {}
+    foreach id [array names tagloc] {
+       if {![info exists hastaggeddescendent($id)]} {
+           foreach t $tagloc($id) {
+               if {[lsearch -exact $tags $t] < 0} {
+                   lappend tags $t
+               }
+           }
+       }
+    }
+
+    # remove tags that are ancestors of other tags
+    for {set i 0} {$i < [llength $tags]} {incr i} {
+       set a [lindex $tags $i]
+       for {set j 0} {$j < $i} {incr j} {
+           set b [lindex $tags $j]
+           set r [anc_or_desc $a $b]
+           if {$r == -1} {
+               set tags [lreplace $tags $j $j]
+               incr j -1
+               incr i -1
+           } elseif {$r == 1} {
+               set tags [lreplace $tags $i $i]
+               incr i -1
+               break
+           }
+       }
+    }
+
+    if {[array names growing] ne {}} {
+       # graph isn't finished, need to check if any tag could get
+       # eclipsed by another tag coming later.  Simply ignore any
+       # tags that could later get eclipsed.
+       set ctags {}
+       foreach t $tags {
+           if {[is_certain $origid $t]} {
+               lappend ctags $t
+           }
+       }
+       if {$tags eq $ctags} {
+           set cached_atags($origid) $tags
+       } else {
+           set tags $ctags
+       }
+    } else {
+       set cached_atags($origid) $tags
+    }
+    set t3 [clock clicks -milliseconds]
+    if {0 && $t3 - $t1 >= 100} {
+       puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
+           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
+    }
+    return $tags
+}
+
+# Return the list of IDs that have heads that are descendents of id,
+# including id itself if it has a head.
+proc descheads {id} {
+    global arcnos arcstart arcids archeads idheads cached_dheads
+    global allparents
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set aret {}
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check it first
+       set a [lindex $arcnos($id) 0]
+       if {$archeads($a) ne {}} {
+           validate_archeads $a
+           set i [lsearch -exact $arcids($a) $id]
+           foreach t $archeads($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j > $i} break
+               lappend aret $t
+           }
+       }
+       set id $arcstart($a)
+    }
+    set origid $id
+    set todo [list $id]
+    set seen($id) 1
+    set ret {}
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set id [lindex $todo $i]
+       if {[info exists cached_dheads($id)]} {
+           set ret [concat $ret $cached_dheads($id)]
+       } else {
+           if {[info exists idheads($id)]} {
+               lappend ret $id
+           }
+           foreach a $arcnos($id) {
+               if {$archeads($a) ne {}} {
+                   validate_archeads $a
+                   if {$archeads($a) ne {}} {
+                       set ret [concat $ret $archeads($a)]
+                   }
+               }
+               set d $arcstart($a)
+               if {![info exists seen($d)]} {
+                   lappend todo $d
+                   set seen($d) 1
+               }
+           }
+       }
+    }
+    set ret [lsort -unique $ret]
+    set cached_dheads($origid) $ret
+    return [concat $ret $aret]
+}
+
+proc addedtag {id} {
+    global arcnos arcout cached_dtags cached_atags
+
+    if {![info exists arcnos($id)]} return
+    if {![info exists arcout($id)]} {
+       recalcarc [lindex $arcnos($id) 0]
+    }
+    catch {unset cached_dtags}
+    catch {unset cached_atags}
+}
+
+proc addedhead {hid head} {
+    global arcnos arcout cached_dheads
+
+    if {![info exists arcnos($hid)]} return
+    if {![info exists arcout($hid)]} {
+       recalcarc [lindex $arcnos($hid) 0]
+    }
+    catch {unset cached_dheads}
+}
+
+proc removedhead {hid head} {
+    global cached_dheads
+
+    catch {unset cached_dheads}
+}
+
+proc movedhead {hid head} {
+    global arcnos arcout cached_dheads
+
+    if {![info exists arcnos($hid)]} return
+    if {![info exists arcout($hid)]} {
+       recalcarc [lindex $arcnos($hid) 0]
+    }
+    catch {unset cached_dheads}
+}
+
+proc changedrefs {} {
+    global cached_dheads cached_dtags cached_atags
+    global arctags archeads arcnos arcout idheads idtags
+
+    foreach id [concat [array names idheads] [array names idtags]] {
+       if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
+           set a [lindex $arcnos($id) 0]
+           if {![info exists donearc($a)]} {
+               recalcarc $a
+               set donearc($a) 1
+           }
+       }
+    }
+    catch {unset cached_dtags}
+    catch {unset cached_atags}
+    catch {unset cached_dheads}
+}
+
+proc rereadrefs {} {
+    global idtags idheads idotherrefs mainhead
+
+    set refids [concat [array names idtags] \
+                   [array names idheads] [array names idotherrefs]]
+    foreach id $refids {
+       if {![info exists ref($id)]} {
+           set ref($id) [listrefs $id]
+       }
+    }
+    set oldmainhead $mainhead
+    readrefs
+    changedrefs
+    set refids [lsort -unique [concat $refids [array names idtags] \
+                       [array names idheads] [array names idotherrefs]]]
+    foreach id $refids {
+       set v [listrefs $id]
+       if {![info exists ref($id)] || $ref($id) != $v ||
+           ($id eq $oldmainhead && $id ne $mainhead) ||
+           ($id eq $mainhead && $id ne $oldmainhead)} {
+           redrawtags $id
+       }
+    }
+    run refill_reflist
+}
+
+proc listrefs {id} {
+    global idtags idheads idotherrefs
+
+    set x {}
+    if {[info exists idtags($id)]} {
+       set x $idtags($id)
+    }
+    set y {}
+    if {[info exists idheads($id)]} {
+       set y $idheads($id)
+    }
+    set z {}
+    if {[info exists idotherrefs($id)]} {
+       set z $idotherrefs($id)
+    }
+    return [list $x $y $z]
+}
+
+proc showtag {tag isnew} {
+    global ctext tagcontents tagids linknum tagobjid
+
+    if {$isnew} {
+       addtohistory [list showtag $tag 0]
+    }
+    $ctext conf -state normal
+    clear_ctext
+    settabs 0
+    set linknum 0
+    if {![info exists tagcontents($tag)]} {
+       catch {
+           set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+       }
+    }
+    if {[info exists tagcontents($tag)]} {
+       set text $tagcontents($tag)
+    } else {
+       set text "Tag: $tag\nId:  $tagids($tag)"
+    }
+    appendwithlinks $text {}
+    $ctext conf -state disabled
+    init_flist {}
+}
+
+proc doquit {} {
+    global stopped
+    set stopped 100
+    savestuff .
+    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
+    global oldprefs prefstop showneartags showlocalchanges
+    global bgcolor fgcolor ctext diffcolors selectbgcolor
+    global uifont tabstop limitdiffs
+
+    set top .gitkprefs
+    set prefstop $top
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    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
+    grid $top.ldisp - -sticky w -pady 10
+    label $top.spacer -text " "
+    label $top.maxwidthl -text "Maximum graph width (lines)" \
+       -font optionfont
+    spinbox $top.maxwidth -from 0 -to 100 -width 4 -textvariable maxwidth
+    grid $top.spacer $top.maxwidthl $top.maxwidth -sticky w
+    label $top.maxpctl -text "Maximum graph width (% of pane)" \
+       -font optionfont
+    spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
+    grid x $top.maxpctl $top.maxpct -sticky w
+    frame $top.showlocal
+    label $top.showlocal.l -text "Show local changes" -font optionfont
+    checkbutton $top.showlocal.b -variable showlocalchanges
+    pack $top.showlocal.b $top.showlocal.l -side left
+    grid x $top.showlocal -sticky w
+
+    label $top.ddisp -text "Diff display options"
+    $top.ddisp configure -font uifont
+    grid $top.ddisp - -sticky w -pady 10
+    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
+    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
+    grid $top.cdisp - -sticky w -pady 10
+    label $top.bg -padx 40 -relief sunk -background $bgcolor
+    button $top.bgbut -text "Background" -font optionfont \
+       -command [list choosecolor bgcolor 0 $top.bg background setbg]
+    grid x $top.bgbut $top.bg -sticky w
+    label $top.fg -padx 40 -relief sunk -background $fgcolor
+    button $top.fgbut -text "Foreground" -font optionfont \
+       -command [list choosecolor fgcolor 0 $top.fg foreground setfg]
+    grid x $top.fgbut $top.fg -sticky w
+    label $top.diffold -padx 40 -relief sunk -background [lindex $diffcolors 0]
+    button $top.diffoldbut -text "Diff: old lines" -font optionfont \
+       -command [list choosecolor diffcolors 0 $top.diffold "diff old lines" \
+                     [list $ctext tag conf d0 -foreground]]
+    grid x $top.diffoldbut $top.diffold -sticky w
+    label $top.diffnew -padx 40 -relief sunk -background [lindex $diffcolors 1]
+    button $top.diffnewbut -text "Diff: new lines" -font optionfont \
+       -command [list choosecolor diffcolors 1 $top.diffnew "diff new lines" \
+                     [list $ctext tag conf d1 -foreground]]
+    grid x $top.diffnewbut $top.diffnew -sticky w
+    label $top.hunksep -padx 40 -relief sunk -background [lindex $diffcolors 2]
+    button $top.hunksepbut -text "Diff: hunk header" -font optionfont \
+       -command [list choosecolor diffcolors 2 $top.hunksep \
+                     "diff hunk header" \
+                     [list $ctext tag conf hunksep -foreground]]
+    grid x $top.hunksepbut $top.hunksep -sticky w
+    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
+    button $top.selbgbut -text "Select bg" -font optionfont \
+       -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
+    button $top.buts.can -text "Cancel" -command prefscan -default normal
+    $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
+    grid $top.buts - - -pady 10 -sticky ew
+    bind $top <Visibility> "focus $top.buts.ok"
+}
+
+proc choosecolor {v vi w x cmd} {
+    global $v
+
+    set c [tk_chooseColor -initialcolor [lindex [set $v] $vi] \
+              -title "Gitk: choose color for $x"]
+    if {$c eq {}} return
+    $w conf -background $c
+    lset $v $vi $c
+    eval $cmd $c
+}
+
+proc setselbg {c} {
+    global bglist cflist
+    foreach w $bglist {
+       $w configure -selectbackground $c
+    }
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
+    allcanvs itemconf secsel -fill $c
+}
+
+proc setbg {c} {
+    global bglist
+
+    foreach w $bglist {
+       $w conf -background $c
+    }
+}
+
+proc setfg {c} {
+    global fglist canv
+
+    foreach w $fglist {
+       $w conf -foreground $c
+    }
+    allcanvs itemconf text -fill $c
+    $canv itemconf circle -outline $c
+}
+
+proc prefscan {} {
+    global oldprefs prefstop
+
+    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 fontpref mainfont textfont uifont
+    global limitdiffs treediffs
+
+    catch {destroy $prefstop}
+    unset prefstop
+    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
+       } else {
+           dohidelocalchanges
+       }
+    }
+    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) ||
+         $limitdiffs != $oldprefs(limitdiffs)} {
+       reselectline
+    }
+}
+
+proc formatdate {d} {
+    global datetimeformat
+    if {$d ne {}} {
+       set d [clock format $d -format $datetimeformat]
+    }
+    return $d
+}
+
+# This list of encoding names and aliases is distilled from
+# http://www.iana.org/assignments/character-sets.
+# Not all of them are supported by Tcl.
+set encoding_aliases {
+    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
+      ISO646-US US-ASCII us IBM367 cp367 csASCII }
+    { ISO-10646-UTF-1 csISO10646UTF1 }
+    { ISO_646.basic:1983 ref csISO646basic1983 }
+    { INVARIANT csINVARIANT }
+    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
+    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
+    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
+    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
+    { NATS-DANO iso-ir-9-1 csNATSDANO }
+    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
+    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
+    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
+    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
+    { ISO-2022-KR csISO2022KR }
+    { EUC-KR csEUCKR }
+    { ISO-2022-JP csISO2022JP }
+    { ISO-2022-JP-2 csISO2022JP2 }
+    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
+      csISO13JISC6220jp }
+    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
+    { IT iso-ir-15 ISO646-IT csISO15Italian }
+    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
+    { ES iso-ir-17 ISO646-ES csISO17Spanish }
+    { greek7-old iso-ir-18 csISO18Greek7Old }
+    { latin-greek iso-ir-19 csISO19LatinGreek }
+    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
+    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
+    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
+    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
+    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
+    { BS_viewdata iso-ir-47 csISO47BSViewdata }
+    { INIS iso-ir-49 csISO49INIS }
+    { INIS-8 iso-ir-50 csISO50INIS8 }
+    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
+    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
+    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
+    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
+    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
+    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
+      csISO60Norwegian1 }
+    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
+    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
+    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
+    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
+    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
+    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
+    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
+    { greek7 iso-ir-88 csISO88Greek7 }
+    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
+    { iso-ir-90 csISO90 }
+    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
+    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
+      csISO92JISC62991984b }
+    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
+    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
+    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
+      csISO95JIS62291984handadd }
+    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
+    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
+    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
+    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
+      CP819 csISOLatin1 }
+    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
+    { T.61-7bit iso-ir-102 csISO102T617bit }
+    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
+    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
+    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
+    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
+    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
+    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
+    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
+    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
+      arabic csISOLatinArabic }
+    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
+    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
+    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
+      greek greek8 csISOLatinGreek }
+    { T.101-G2 iso-ir-128 csISO128T101G2 }
+    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
+      csISOLatinHebrew }
+    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
+    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
+    { CSN_369103 iso-ir-139 csISO139CSN369103 }
+    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
+    { ISO_6937-2-add iso-ir-142 csISOTextComm }
+    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
+    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
+      csISOLatinCyrillic }
+    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
+    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
+    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
+    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
+    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
+    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
+    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
+    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
+    { ISO_10367-box iso-ir-155 csISO10367Box }
+    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
+    { latin-lap lap iso-ir-158 csISO158Lap }
+    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
+    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
+    { us-dk csUSDK }
+    { dk-us csDKUS }
+    { JIS_X0201 X0201 csHalfWidthKatakana }
+    { KSC5636 ISO646-KR csKSC5636 }
+    { ISO-10646-UCS-2 csUnicode }
+    { ISO-10646-UCS-4 csUCS4 }
+    { DEC-MCS dec csDECMCS }
+    { hp-roman8 roman8 r8 csHPRoman8 }
+    { macintosh mac csMacintosh }
+    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
+      csIBM037 }
+    { IBM038 EBCDIC-INT cp038 csIBM038 }
+    { IBM273 CP273 csIBM273 }
+    { IBM274 EBCDIC-BE CP274 csIBM274 }
+    { IBM275 EBCDIC-BR cp275 csIBM275 }
+    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
+    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
+    { IBM280 CP280 ebcdic-cp-it csIBM280 }
+    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
+    { IBM284 CP284 ebcdic-cp-es csIBM284 }
+    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
+    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
+    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
+    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
+    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
+    { IBM424 cp424 ebcdic-cp-he csIBM424 }
+    { IBM437 cp437 437 csPC8CodePage437 }
+    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
+    { IBM775 cp775 csPC775Baltic }
+    { IBM850 cp850 850 csPC850Multilingual }
+    { IBM851 cp851 851 csIBM851 }
+    { IBM852 cp852 852 csPCp852 }
+    { IBM855 cp855 855 csIBM855 }
+    { IBM857 cp857 857 csIBM857 }
+    { IBM860 cp860 860 csIBM860 }
+    { IBM861 cp861 861 cp-is csIBM861 }
+    { IBM862 cp862 862 csPC862LatinHebrew }
+    { IBM863 cp863 863 csIBM863 }
+    { IBM864 cp864 csIBM864 }
+    { IBM865 cp865 865 csIBM865 }
+    { IBM866 cp866 866 csIBM866 }
+    { IBM868 CP868 cp-ar csIBM868 }
+    { IBM869 cp869 869 cp-gr csIBM869 }
+    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
+    { IBM871 CP871 ebcdic-cp-is csIBM871 }
+    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
+    { IBM891 cp891 csIBM891 }
+    { IBM903 cp903 csIBM903 }
+    { IBM904 cp904 904 csIBBM904 }
+    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
+    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
+    { IBM1026 CP1026 csIBM1026 }
+    { EBCDIC-AT-DE csIBMEBCDICATDE }
+    { EBCDIC-AT-DE-A csEBCDICATDEA }
+    { EBCDIC-CA-FR csEBCDICCAFR }
+    { EBCDIC-DK-NO csEBCDICDKNO }
+    { EBCDIC-DK-NO-A csEBCDICDKNOA }
+    { EBCDIC-FI-SE csEBCDICFISE }
+    { EBCDIC-FI-SE-A csEBCDICFISEA }
+    { EBCDIC-FR csEBCDICFR }
+    { EBCDIC-IT csEBCDICIT }
+    { EBCDIC-PT csEBCDICPT }
+    { EBCDIC-ES csEBCDICES }
+    { EBCDIC-ES-A csEBCDICESA }
+    { EBCDIC-ES-S csEBCDICESS }
+    { EBCDIC-UK csEBCDICUK }
+    { EBCDIC-US csEBCDICUS }
+    { UNKNOWN-8BIT csUnknown8BiT }
+    { MNEMONIC csMnemonic }
+    { MNEM csMnem }
+    { VISCII csVISCII }
+    { VIQR csVIQR }
+    { KOI8-R csKOI8R }
+    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
+    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
+    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
+    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
+    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
+    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
+    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
+    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
+    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
+    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
+    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
+    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
+    { IBM1047 IBM-1047 }
+    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
+    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
+    { UNICODE-1-1 csUnicode11 }
+    { CESU-8 csCESU-8 }
+    { BOCU-1 csBOCU-1 }
+    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
+    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
+      l8 }
+    { ISO-8859-15 ISO_8859-15 Latin-9 }
+    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
+    { GBK CP936 MS936 windows-936 }
+    { JIS_Encoding csJISEncoding }
+    { Shift_JIS MS_Kanji csShiftJIS }
+    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
+      EUC-JP }
+    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
+    { ISO-10646-UCS-Basic csUnicodeASCII }
+    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
+    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
+    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
+    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
+    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
+    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
+    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
+    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
+    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
+    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
+    { Adobe-Standard-Encoding csAdobeStandardEncoding }
+    { Ventura-US csVenturaUS }
+    { Ventura-International csVenturaInternational }
+    { PC8-Danish-Norwegian csPC8DanishNorwegian }
+    { PC8-Turkish csPC8Turkish }
+    { IBM-Symbols csIBMSymbols }
+    { IBM-Thai csIBMThai }
+    { HP-Legal csHPLegal }
+    { HP-Pi-font csHPPiFont }
+    { HP-Math8 csHPMath8 }
+    { Adobe-Symbol-Encoding csHPPSMath }
+    { HP-DeskTop csHPDesktop }
+    { Ventura-Math csVenturaMath }
+    { Microsoft-Publishing csMicrosoftPublishing }
+    { Windows-31J csWindows31J }
+    { GB2312 csGB2312 }
+    { Big5 csBig5 }
+}
+
+proc tcl_encoding {enc} {
+    global encoding_aliases
+    set names [encoding names]
+    set lcnames [string tolower $names]
+    set enc [string tolower $enc]
+    set i [lsearch -exact $lcnames $enc]
+    if {$i < 0} {
+       # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
+       if {[regsub {^iso[-_]} $enc iso encx]} {
+           set i [lsearch -exact $lcnames $encx]
+       }
+    }
+    if {$i < 0} {
+       foreach l $encoding_aliases {
+           set ll [string tolower $l]
+           if {[lsearch -exact $ll $enc] < 0} continue
+           # look through the aliases for one that tcl knows about
+           foreach e $ll {
+               set i [lsearch -exact $lcnames $e]
+               if {$i < 0} {
+                   if {[regsub {^iso[-_]} $e iso ex]} {
+                       set i [lsearch -exact $lcnames $ex]
+                   }
+               }
+               if {$i >= 0} break
+           }
+           break
+       }
+    }
+    if {$i >= 0} {
+       return [lindex $names $i]
+    }
+    return {}
+}
+
+# First check that Tcl/Tk is recent enough
+if {[catch {package require Tk 8.4} err]} {
+    show_error {} . "Sorry, gitk cannot run with this version of Tcl/Tk.\n\
+                    Gitk requires at least Tcl/Tk 8.4."
+    exit 1
+}
+
+# defaults...
+set datemode 0
+set wrcomcmd "git diff-tree --stdin -p --pretty"
+
+set gitencoding {}
+catch {
+    set gitencoding [exec git config --get i18n.commitencoding]
+}
+if {$gitencoding == ""} {
+    set gitencoding "utf-8"
+}
+set tclencoding [tcl_encoding $gitencoding]
+if {$tclencoding == {}} {
+    puts stderr "Warning: encoding $gitencoding is not supported by Tcl/Tk"
+}
+
+set mainfont {Helvetica 9}
+set textfont {Courier 9}
+set uifont {Helvetica 9 bold}
+set tabstop 8
+set findmergefiles 0
+set maxgraphpct 50
+set maxwidth 16
+set revlistorder 0
+set fastdate 0
+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}
+set bgcolor white
+set fgcolor black
+set diffcolors {red "#00a000" blue}
+set diffcontext 3
+set selectbgcolor gray85
+
+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."
+    exit 1
+}
+if {![file isdirectory $gitdir]} {
+    show_error {} . "Cannot find the git directory \"$gitdir\"."
+    exit 1
+}
+
+set mergeonly 0
+set revtreeargs {}
+set cmdline_files {}
+set i 0
+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
+       }
+       default {
+           lappend revtreeargs $arg
+       }
+    }
+    incr i
+}
+
+if {$i >= [llength $argv] && $revtreeargs ne {}} {
+    # no -- on command line, but some arguments (other than -d)
+    if {[catch {
+       set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
+       set cmdline_files [split $f "\n"]
+       set n [llength $cmdline_files]
+       set revtreeargs [lrange $revtreeargs 0 end-$n]
+       # Unfortunately git rev-parse doesn't produce an error when
+       # something is both a revision and a filename.  To be consistent
+       # with git log and git rev-list, check revtreeargs for filenames.
+       foreach arg $revtreeargs {
+           if {[file exists $arg]} {
+               show_error {} . "Ambiguous argument '$arg': both revision\
+                                and filename"
+               exit 1
+           }
+       }
+    } err]} {
+       # unfortunately we get both stdout and stderr in $err,
+       # so look for "fatal:".
+       set i [string first "fatal:" $err]
+       if {$i > 0} {
+           set err [string range $err [expr {$i + 6}] end]
+       }
+       show_error {} . "Bad arguments to gitk:\n$err"
+       exit 1
+    }
+}
+
+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 {}
+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 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) {}
+
+set cmdlineok 0
+set stopped 0
+set stuffsaved 0
+set patchnum 0
+set localirow -1
+set localfrow -1
+set lserial 0
+setcoords
+makewindow
+# wait for the window to become visible
+tkwait visibility .
+wm title . "[file tail $argv0]: [file tail [pwd]]"
+readrefs
+
+if {$cmdline_files ne {} || $revtreeargs ne {}} {
+    # create a view for the files/dirs specified on the command line
+    set curview 1
+    set selectedview 1
+    set nextviewnum 2
+    set viewname(1) "Command line"
+    set viewfiles(1) $cmdline_files
+    set viewargs(1) $revtreeargs
+    set viewperm(1) 0
+    addviewmenu 1
+    .bar.view entryconf Edit* -state normal
+    .bar.view entryconf Delete* -state normal
+}
+
+if {[info exists permviews]} {
+    foreach v $permviews {
+       set n $nextviewnum
+       incr nextviewnum
+       set viewname($n) [lindex $v 0]
+       set viewfiles($n) [lindex $v 1]
+       set viewargs($n) [lindex $v 2]
+       set viewperm($n) 1
+       addviewmenu $n
+    }
+}
+getcommits
index 7186cede2fad816ef4d497ac2c863869d5db76a0..b28f59f574e2a6b7da3a186bce4bf88b7cd71a6f 100644 (file)
@@ -10,28 +10,96 @@ From the git version 1.4.0 gitweb is bundled with git.
 How to configure gitweb for your local system
 ---------------------------------------------
 
+See also "Build time configuration" section in INSTALL
+file for gitweb (in gitweb/INSTALL).
+
 You can specify the following configuration variables when building GIT:
+ * GIT_BINDIR
+   Points out where to find git executable.  You should set up it to
+   the place where git binary was installed (usually /usr/bin) if you
+   don't install git from sources together with gitweb.  [Default: $(bindir)]
  * GITWEB_SITENAME
-   Shown in the title of all generated pages, defaults to the servers name.
+   Shown in the title of all generated pages, defaults to the server name
+   (SERVER_NAME CGI environment variable) if not set. [No default]
  * GITWEB_PROJECTROOT
-   The root directory for all projects shown by gitweb.
+   The root directory for all projects shown by gitweb. Must be set
+   correctly for gitweb to find repositories to display.  See also
+   "Gitweb repositories" in INSTALL file for gitweb.  [Default: /pub/git]
+ * GITWEB_PROJECT_MAXDEPTH
+   The filesystem traversing limit for getting projects list; the number
+   is taken as depth relative to the projectroot.  It is used when
+   GITWEB_LIST is a directory (or is not set; then project root is used).
+   Is is meant to speed up project listing on large work trees by limiting
+   find depth.  [Default: 2007]
  * GITWEB_LIST
-   points to a directory to scan for projects (defaults to project root)
-   or to a file for explicit listing of projects.
+   Points to a directory to scan for projects (defaults to project root
+   if not set / if empty) or to a file with explicit listing of projects
+   (together with projects' ownership). See "Generating projects list
+   using gitweb" in INSTALL file for gitweb to find out how to generate
+   such file from scan of a directory. [No default, which means use root
+   directory for projects]
+ * GITWEB_EXPORT_OK
+   Show repository only if this file exists (in repository).  Only
+   effective if this variable evaluates to true.  [No default / Not set]
+ * GITWEB_STRICT_EXPORT
+   Only allow viewing of repositories also shown on the overview page.
+   This for example makes GITWEB_EXPORT_OK to decide if repository is
+   available and not only if it is shown.  If GITWEB_LIST points to
+   file with list of project, only those repositories listed would be
+   available for gitweb.  [No default]
  * GITWEB_HOMETEXT
-   points to an .html file which is included on the gitweb project
-   overview page.
+   Points to an .html file which is included on the gitweb project
+   overview page ('projects_list' view), if it exists.  Relative to
+   gitweb.cgi script.  [Default: indextext.html]
+ * GITWEB_SITE_HEADER
+   Filename of html text to include at top of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_SITE_FOOTER
+   Filename of html text to include at bottom of each page.  Relative to
+   gitweb.cgi script.  [No default]
+ * GITWEB_HOME_LINK_STR
+   String of the home link on top of all pages, leading to $home_link
+   (usually main gitweb page, which means projects list).  Used as first
+   part of gitweb view "breadcrumb trail": <home> / <project> / <view>.
+   [Default: projects]
+ * GITWEB_SITENAME
+   Name of your site or organization to appear in page titles.  Set it
+   to something descriptive for clearer bookmarks etc.  If not set
+   (if empty) gitweb uses "$SERVER_NAME Git", or "Untitled Git" if
+   SERVER_NAME CGI environment variable is not set (e.g. if running
+   gitweb as standalone script).  [No default]
+ * GITWEB_BASE_URL
+   Git base URLs used for URL to where fetch project from, i.e. full
+   URL is "$git_base_url/$project".  Shown on projects summary page.
+   Repository URL for project can be also configured per repository; this
+   takes precendence over URL composed from base URL and project name.
+   Note that you can setup multiple base URLs (for example one for
+   git:// protocol access, one for http:// access) from gitweb config
+   file.  [No default]
  * GITWEB_CSS
-   Points to the location where you put gitweb.css on your web server.
+   Points to the location where you put gitweb.css on your web server
+   (or to be more generic URI of gitweb stylesheet).  Relative to base
+   URI of gitweb.  Note that you can setup multiple stylesheets from
+   gitweb config file.  [Default: gitweb.css]
  * GITWEB_LOGO
-   Points to the location where you put git-logo.png on your web server.
+   Points to the location where you put git-logo.png on your web server
+   (or to be more generic URI of logo, 72x27 size, displayed in top right
+   corner of each gitweb page, and used as logo for Atom feed).  Relative
+   to base URI of gitweb.  [Default: git-logo.png]
+ * GITWEB_FAVICON
+   Points to the location where you put git-favicon.png on your web server
+   (or to be more generic URI of favicon, assumed to be image/png type;
+   web browsers that support favicons (website icons) may display them
+   in the browser's URL bar and next to site name in bookmarks).  Relative
+   to base URI of gitweb.  [Default: git-favicon.png]
  * GITWEB_CONFIG
-   This file will be loaded using 'require' and can be used to override any
-   of the options above as well as some other options - see the top of
-   'gitweb.cgi' for their full list and description.  If the environment
-   $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the
-   environment variable will be loaded instead of the file
-   specified when gitweb.cgi was created.
+   This Perl file will be loaded using 'do' and can be used to override any
+   of the options above as well as some other options -- see the "Runtime
+   gitweb configuration" section below, and top of 'gitweb.cgi' for their
+   full list and description.  If the environment variable GITWEB_CONFIG
+   is set when gitweb.cgi is executed, then the file specified in the
+   environment variable will be loaded instead of the file specified
+   when gitweb.cgi was created.  [Default: gitweb_config.perl]
 
 
 Runtime gitweb configuration
@@ -39,11 +107,122 @@ Runtime gitweb configuration
 
 You can adjust gitweb behaviour using the file specified in `GITWEB_CONFIG`
 (defaults to 'gitweb_config.perl' in the same directory as the CGI).
-See the top of 'gitweb.cgi' for the list of variables and some description.
 The most notable thing that is not configurable at compile time are the
-optional features, stored in the '%features' variable. You can find further
-description on how to reconfigure the default features setting in your
-`GITWEB_CONFIG` or per-project in `project.git/config` inside 'gitweb.cgi'.
+optional features, stored in the '%features' variable.
+
+Ultimate description on how to reconfigure the default features setting
+in your `GITWEB_CONFIG` or per-project in `project.git/config` can be found
+as comments inside 'gitweb.cgi'.
+
+See also "Gitweb config file" (with example of gitweb config file), and
+"Gitweb repositories" sections in INSTALL file for gitweb.
+
+
+Gitweb config file is [fragment] of perl code. You can set variables
+using "our $variable = value"; text from "#" character until the end
+of a line is ignored. See perlsyn(1) man page for details.
+
+Below there is list of vaiables which you might want to set in gitweb config.
+See the top of 'gitweb.cgi' for the full list of variables and their
+descriptions.
+
+Gitweb config file variables
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can set, among others,  the following variables in gitweb config files:
+ * $GIT
+   Cure git executable to use.  By default set to "$GIT_BINDIR/git", which
+   in turn is by default set to "$(bindir)/git".  If you use git from binary
+   package, set this to "/usr/bin/git".  This can just be "git" if your
+   webserver has a sensible PATH.  If you have multiple git versions
+   installed it is / can be used to choose which one to use.
+ * $version
+   Gitweb version, set automatically when creating gitweb.cgi from
+   gitweb.perl. You might want to modify it if you are running modified
+   gitweb.
+ * $my_url, $my_uri
+   URL and absolute URL of gitweb script; you might need to set those
+   variables if you are using 'pathinfo' feature: see also below.
+ * $home_link
+   Target of the home link on top of all pages (the first part of view
+   "breadcrumbs").  By default set to absolute URI of a page; you might
+   need to set it up to [base] gitweb URI if you use 'pathinfo' feature
+   (alternative format of the URLs, with project name embedded directly
+   in the path part of URL).
+ * @stylesheets
+   List of URIs of stylesheets (relative to base URI of a page). You
+   might specify more than one stylesheet, for example use gitweb.css
+   as base, with site specific modifications in separate stylesheet
+   to make it easier to upgrade gitweb. You can add 'site' stylesheet
+   for example by using
+      push @stylesheets, "gitweb-site.css";
+   in gitweb config file.
+ * $logo_url, $logo_label
+   URI and label (title) of GIT logo link (or your site logo, if you choose
+   to use different logo image). By default they point to git homepage;
+   in the past they pointed to git documentation at www.kernel.org.
+ * $projects_list_description_width
+   The width (in characters) of the projects list "Description" column.
+   Longer descriptions will be cut (trying to cut at word boundary);
+   full description is available as 'title' attribute (usually shown on
+   mouseover).  By default set to 25, which might be too small if you
+   use long project descriptions.
+ * @git_base_url_list
+   List of git base URLs used for URL to where fetch project from, shown
+   in project summary page.  Full URL is "$git_base_url/$project".
+   You can setup multiple base URLs (for example one for  git:// protocol
+   access, and one for http:// "dumb" protocol access).  Note that per
+   repository configuration in 'cloneurl' file, or as values of gitweb.url
+   project config.
+ * $default_blob_plain_mimetype
+   Default mimetype for blob_plain (raw) view, if mimetype checking
+   doesn't result in some other type; by default 'text/plain'.
+ * $default_text_plain_charset
+   Default charset for text files. If not set, web serwer configuration
+   would be used.
+ * $mimetypes_file
+   File to use for (filename extension based) guessing of MIME types before
+   trying /etc/mime.types. Path, if relative, is taken currently as taken
+   relative to current git repositoy.
+ * $fallback_encoding
+   Gitweb assumes this charset if line contains non-UTF-8 characters.
+   Fallback decoding is used without error checking, so it can be even
+   'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man
+   page for a list.   By default 'latin1', aka. 'iso-8859-1'.
+ * @diff_opts
+   Rename detection options for git-diff and git-diff-tree. By default
+   ('-M'); set it to ('-C') or ('-C', '-C') to also detect copies, or
+   set it to () if you don't want to have renames detection.
+
+Per-repository gitweb configuration
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can also configure individual repositories shown in gitweb by creating
+file in the GIT_DIR of git repository, or by setting some repo configuration
+variable (in GIT_DIR/config).
+
+You can use the following files in repository:
+ * README.html
+   A .html file (HTML fragment) which is included on the gitweb project
+   summary page inside <div> block element. You can use it for longer
+   description of a project, to provide links for example to projects
+   homepage, etc.
+ * description (or gitweb.description)
+   Short (shortened by default to 25 characters in the projects list page)
+   single line description of a project (of a repository). Plain text file;
+   HTML will be escaped. By default set to
+     Unnamed repository; edit this file to name it for gitweb.
+   from the template during creating repository. You can use
+   gitweb.description repo configuration variable, but the file takes
+   precendence.
+ * cloneurl (or multiple-valued gitweb.url)
+   File with repository URL (used for clone and fetch), one per line.
+   Displayed in the project summary page. You can use multiple-valued
+   gitweb.url repository configuration variable for that, but the file
+   takes precendence.
+ * various gitweb.* config variables (in config)
+   Read description of %feature hash for detailed list, and some
+   descriptions.
 
 
 Webserver configuration
index 491a3f41d20deafa6513e7f574108022b762507b..ff5daa7901d9976dc35e1b4a05e1cf0140297d6a 100755 (executable)
@@ -3786,6 +3786,8 @@ sub git_search_grep_body {
                      "<td class=\"link\">" .
                      $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
                      " | " .
+                     $cgi->a({-href => href(action=>"commitdiff", hash=>$co{'id'})}, "commitdiff") .
+                     " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
                print "</td>\n" .
                      "</tr>\n";
diff --git a/help.c b/help.c
index d340b6a1b6c4ecb8132e81fe306883aa18feb090..37a9c25db72d2a69a8076517870b7b874bd336a9 100644 (file)
--- a/help.c
+++ b/help.c
@@ -237,7 +237,6 @@ void list_common_cmds_help(void)
                mput_char(' ', longest - strlen(common_cmds[i].name));
                puts(common_cmds[i].help);
        }
-       puts("(use 'git help -a' to get a list of all installed git commands)");
 }
 
 static void show_man_page(const char *git_cmd)
index d3e608ac45e2a0c6accb9d27ed8d407619cb70d2..e12b428c0a76a635041f678a3eaf05300d780061 100644 (file)
@@ -216,6 +216,9 @@ static int parse_long_opt(struct optparse_t *p, const char *arg,
        return error("unknown option `%s'", arg);
 }
 
+static NORETURN void usage_with_options_internal(const char * const *,
+                                                 const struct option *, int);
+
 int parse_options(int argc, const char **argv, const struct option *options,
                   const char * const usagestr[], int flags)
 {
@@ -249,6 +252,8 @@ int parse_options(int argc, const char **argv, const struct option *options,
                        break;
                }
 
+               if (!strcmp(arg + 2, "help-all"))
+                       usage_with_options_internal(usagestr, options, 1);
                if (!strcmp(arg + 2, "help"))
                        usage_with_options(usagestr, options);
                if (parse_long_opt(&args, arg + 2, options))
@@ -263,8 +268,8 @@ int parse_options(int argc, const char **argv, const struct option *options,
 #define USAGE_OPTS_WIDTH 24
 #define USAGE_GAP         2
 
-void usage_with_options(const char * const *usagestr,
-                        const struct option *opts)
+void usage_with_options_internal(const char * const *usagestr,
+                                 const struct option *opts, int full)
 {
        fprintf(stderr, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
@@ -285,6 +290,8 @@ void usage_with_options(const char * const *usagestr,
                                fprintf(stderr, "%s\n", opts->help);
                        continue;
                }
+               if (!full && (opts->flags & PARSE_OPT_HIDDEN))
+                       continue;
 
                pos = fprintf(stderr, "    ");
                if (opts->short_name)
@@ -335,6 +342,12 @@ void usage_with_options(const char * const *usagestr,
        exit(129);
 }
 
+void usage_with_options(const char * const *usagestr,
+                        const struct option *opts)
+{
+       usage_with_options_internal(usagestr, opts, 0);
+}
+
 /*----- some often used options -----*/
 #include "cache.h"
 
index a8760ac4b288a44d6404d0550b9640b49789d39d..102ac31fb727acfdc3c2159e1525c7bcca94e1ef 100644 (file)
@@ -24,6 +24,7 @@ enum parse_opt_option_flags {
        PARSE_OPT_OPTARG  = 1,
        PARSE_OPT_NOARG   = 2,
        PARSE_OPT_NONEG   = 4,
+       PARSE_OPT_HIDDEN  = 8,
 };
 
 struct option;
@@ -57,6 +58,8 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  *   PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs)
  *   PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
  *   PARSE_OPT_NONEG: says that this option cannot be negated
+ *   PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
+ *                    the long one.
  *
  * `callback`::
  *   pointer to the callback to use for OPTION_CALLBACK.
index dca92c8adb52e212ef96410d3efd4deaa28e385f..7468460f9a6d29d5c4bf14db4921bf28e23b6814 100644 (file)
@@ -549,6 +549,37 @@ sub config_bool {
        };
 }
 
+=item config_int ( VARIABLE )
+
+Retrieve the integer configuration C<VARIABLE>. The return value
+is simple decimal number.  An optional value suffix of 'k', 'm',
+or 'g' in the config file will cause the value to be multiplied
+by 1024, 1048576 (1024^2), or 1073741824 (1024^3) prior to output.
+It would return C<undef> if configuration variable is not defined,
+
+Must be called on a repository instance.
+
+This currently wraps command('config') so it is not so fast.
+
+=cut
+
+sub config_int {
+       my ($self, $var) = @_;
+       $self->repo_path()
+               or throw Error::Simple("not a repository");
+
+       try {
+               return $self->command_oneline('config', '--int', '--get', $var);
+       } catch Git::Error::Command with {
+               my $E = shift;
+               if ($E->value() == 1) {
+                       # Key not found.
+                       return undef;
+               } else {
+                       throw $E;
+               }
+       };
+}
 
 =item ident ( TYPE | IDENTSTR )
 
index ed44b897f67a0ef051fa1bf205b2bb5747a1bd67..fba4cf82353ff43eae7430c863680f481e03dcb0 100644 (file)
@@ -200,6 +200,10 @@ static const char *update(struct command *cmd)
        }
 
        if (is_null_sha1(new_sha1)) {
+               if (!parse_object(old_sha1)) {
+                       warning ("Allowing deletion of corrupt ref.");
+                       old_sha1 = NULL;
+               }
                if (delete_ref(name, old_sha1)) {
                        error("failed to delete %s", name);
                        return "failed to delete";
diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh
new file mode 100755 (executable)
index 0000000..9ef593f
--- /dev/null
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='branch --contains <commit>'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       git branch side &&
+
+       echo 1 >file &&
+       test_tick &&
+       git commit -a -m "second on master" &&
+
+       git checkout side &&
+       echo 1 >file &&
+       test_tick &&
+       git commit -a -m "second on side" &&
+
+       git merge master
+
+'
+
+test_expect_success 'branch --contains=master' '
+
+       git branch --contains=master >actual &&
+       {
+               echo "  master" && echo "* side"
+       } >expect &&
+       diff -u expect actual
+
+'
+
+test_expect_success 'branch --contains master' '
+
+       git branch --contains master >actual &&
+       {
+               echo "  master" && echo "* side"
+       } >expect &&
+       diff -u expect actual
+
+'
+
+test_expect_success 'branch --contains=side' '
+
+       git branch --contains=side >actual &&
+       {
+               echo "* side"
+       } >expect &&
+       diff -u expect actual
+
+'
+
+test_done
index 7d92ae3e996b31f8fa9e0b62f40a04ec1940cf70..c44b27aeb24816a346f0aa84d70546a0ffd83b2a 100755 (executable)
@@ -16,7 +16,7 @@ cat path0 >path1
 chmod +x path1
 
 test_expect_success \
-    'update-cache --add two files with and without +x.' \
+    'update-index --add two files with and without +x.' \
     'git update-index --add path0 path1'
 
 mv path0 path0-
index 063e79257a6e96d9021fa41d18a632560a272776..2fe50bc7ce050f32affa287c5c6e3ab7b1a66f45 100755 (executable)
@@ -27,7 +27,7 @@ Line 15
 '
 
 test_expect_success \
-    'update-cache --add a file.' \
+    'update-index --add a file.' \
     'git update-index --add path0'
 
 test_expect_success \
index 5836e3a899d62c3c86482ea254eb53c015a52823..26c2e4aa65c539c527ae8e2d71b5abb40c21fbbd 100755 (executable)
@@ -119,14 +119,14 @@ test_expect_success \
     'compare_diff_raw expected current'
 
 test_expect_success \
-    'run diff with -B' \
+    'run diff with -B -M' \
     'git diff-index -B -M "$tree" >current'
 
-# This should not mistake file0 as the copy source of new file1
-# due to type differences.
+# file0 changed from regular to symlink.  file1 is very close to the preimage of file0.
+# because we break file0, file1 can become a rename of it.
 cat >expected <<\EOF
 :100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
-:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
+:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R     file0   file1
 EOF
 
 test_expect_success \
diff --git a/t/t4023-diff-rename-typechange.sh b/t/t4023-diff-rename-typechange.sh
new file mode 100755 (executable)
index 0000000..255604e
--- /dev/null
@@ -0,0 +1,86 @@
+#!/bin/sh
+
+test_description='typechange rename detection'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       rm -f foo bar &&
+       cat ../../COPYING >foo &&
+       ln -s linklink bar &&
+       git add foo bar &&
+       git commit -a -m Initial &&
+       git tag one &&
+
+       rm -f foo bar &&
+       cat ../../COPYING >bar &&
+       ln -s linklink foo &&
+       git add foo bar &&
+       git commit -a -m Second &&
+       git tag two &&
+
+       rm -f foo bar &&
+       cat ../../COPYING >foo &&
+       git add foo &&
+       git commit -a -m Third &&
+       git tag three &&
+
+       mv foo bar &&
+       ln -s linklink foo &&
+       git add foo bar &&
+       git commit -a -m Fourth &&
+       git tag four &&
+
+       # This is purely for sanity check
+
+       rm -f foo bar &&
+       cat ../../COPYING >foo &&
+       cat ../../Makefile >bar &&
+       git add foo bar &&
+       git commit -a -m Fifth &&
+       git tag five &&
+
+       rm -f foo bar &&
+       cat ../../Makefile >foo &&
+       cat ../../COPYING >bar &&
+       git add foo bar &&
+       git commit -a -m Sixth &&
+       git tag six
+
+'
+
+test_expect_success 'cross renames to be detected for regular files' '
+
+       git diff-tree five six -r --name-status -B -M | sort >actual &&
+       {
+               echo "R100      foo     bar"
+               echo "R100      bar     foo"
+       } | sort >expect &&
+       diff -u expect actual
+
+'
+
+test_expect_success 'cross renames to be detected for typechange' '
+
+       git diff-tree one two -r --name-status -B -M | sort >actual &&
+       {
+               echo "R100      foo     bar"
+               echo "R100      bar     foo"
+       } | sort >expect &&
+       diff -u expect actual
+
+'
+
+test_expect_success 'moves and renames' '
+
+       git diff-tree three four -r --name-status -B -M | sort >actual &&
+       {
+               echo "R100      foo     bar"
+               echo "T100      foo"
+       } | sort >expect &&
+       diff -u expect actual
+
+'
+
+test_done
index de587517f434d3801d5e0aad94d4e084cbdd5b06..90ab54f0f586c87ace077be87fba396c8f2781a0 100644 (file)
@@ -90,7 +90,7 @@ diff --git a/Documentation/git.txt b/Documentation/git.txt
 diff --git a/Makefile b/Makefile
 --- a/Makefile
 +++ b/Makefile
-@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+@@ -30,7 +30,7 @@ PROG=   git-update-index git-diff-files
        git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
        git-check-files git-ls-tree git-merge-base git-merge-cache \
        git-unpack-file git-export git-diff-cache git-convert-cache \
index cfdc80885b30a8b9b184004072886adc1f88457a..f5c7d601fc955b15d15012a5b49df83364b6ebf4 100644 (file)
@@ -9,7 +9,7 @@ diff --git a/Makefile b/Makefile
 -      git-deltafy-script
 +      git-deltafy-script git-fetch-script
  
- PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+ PROG=   git-update-index git-diff-files git-init-db git-write-tree \
        git-read-tree git-commit-tree git-cat-file git-fsck-cache \
 diff --git a/git-pull-script b/git-fetch-script
 similarity index 87%
index de11623d1babf50952e45b46015ae45bb07ac2bf..5f6ddc105950eac20bcacb7987fabff015320ee3 100644 (file)
@@ -200,7 +200,7 @@ diff a/Documentation/git.txt b/Documentation/git.txt
 diff a/Makefile b/Makefile
 --- a/Makefile
 +++ b/Makefile
-@@ -30,7 +30,7 @@ PROG=   git-update-cache git-diff-files 
+@@ -30,7 +30,7 @@ PROG=   git-update-index git-diff-files
        git-checkout-cache git-diff-tree git-rev-tree git-ls-files \
        git-check-files git-ls-tree git-merge-base git-merge-cache \
        git-unpack-file git-export git-diff-cache git-convert-cache \
index d9753637fc23822795bca72c26b71d0ba71e6552..a72729a712038dc64f55d154eb9a94713e3269c9 100644 (file)
@@ -8,7 +8,7 @@ diff a/Makefile b/Makefile
 -      git-deltafy-script
 +      git-deltafy-script git-fetch-script
  
- PROG=   git-update-cache git-diff-files git-init-db git-write-tree \
+ PROG=   git-update-index git-diff-files git-init-db git-write-tree \
        git-read-tree git-commit-tree git-cat-file git-fsck-cache \
 diff a/git-fetch-script b/git-fetch-script
 --- /dev/null
index 4fbd5b1f473578ac5a1ac61a87883015c04fdc63..987c9d21caf2a3d7653157f5e6d74f0841863c8b 100755 (executable)
@@ -247,11 +247,50 @@ test_expect_success 'push with colon-less refspec (4)' '
 test_expect_success 'push with dry-run' '
 
        mk_test heads/master &&
-       cd testrepo &&
-       old_commit=$(git show-ref -s --verify refs/heads/master) &&
-       cd .. &&
+       (cd testrepo &&
+        old_commit=$(git show-ref -s --verify refs/heads/master)) &&
        git push --dry-run testrepo &&
        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) &&
+       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) &&
+       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 'allow deleting an invalid remote ref' '
+
+       pwd &&
+       rm -f testrepo/.git/objects/??/* &&
+       git push testrepo :refs/heads/master &&
+       (cd testrepo && ! git rev-parse --verify refs/heads/master)
+
+'
+
 test_done
index 93eaf2c1544b5374dbe8043c66478a0a80b0bb82..52b3a0c6dde59b8a955f28f4e9ffe037b0271513 100755 (executable)
@@ -53,4 +53,26 @@ test_expect_success 'the default remote . should not break explicit pull' '
        test `cat file` = modified
 '
 
+test_expect_success '--rebase' '
+       git branch to-rebase &&
+       echo modified again > file &&
+       git commit -m file file &&
+       git checkout to-rebase &&
+       echo new > file2 &&
+       git add file2 &&
+       git commit -m "new file" &&
+       git tag before-rebase &&
+       git pull --rebase . copy &&
+       test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
+       test new = $(git show HEAD:file2)
+'
+
+test_expect_success 'branch.to-rebase.rebase' '
+       git reset --hard before-rebase &&
+       git config branch.to-rebase.rebase 1 &&
+       git pull . copy &&
+       test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
+       test new = $(git show HEAD:file2)
+'
+
 test_done
index 2089351f7d157ce96a07ed1c6c1465fc58819ec0..5f60b22d872b5d034e137e0c6c7c5a9d664e57ee 100755 (executable)
@@ -114,7 +114,7 @@ test_expect_success 'use index-filter to move into a subdirectory' '
 
 test_expect_success 'stops when msg filter fails' '
        old=$(git rev-parse HEAD) &&
-       ! git-filter-branch -f --msg-filter false &&
+       ! git-filter-branch -f --msg-filter false HEAD &&
        test $old = $(git rev-parse HEAD) &&
        rm -rf .git-rewrite
 '
index 5f7e388d7add06b8392997037a86c4d0f8524ceb..c7130c4dcc31dda1f70160c81804096676943af4 100755 (executable)
@@ -339,20 +339,14 @@ test_expect_success \
 '
 
 test_expect_success \
-       'trying to create tags giving many -m or -F options should fail' '
+       'trying to create tags giving both -m or -F options should fail' '
        echo "message file 1" >msgfile1 &&
        echo "message file 2" >msgfile2 &&
        ! tag_exists msgtag &&
-       ! git-tag -m "message 1" -m "message 2" msgtag &&
-       ! tag_exists msgtag &&
-       ! git-tag -F msgfile1 -F msgfile2 msgtag &&
-       ! tag_exists msgtag &&
        ! git-tag -m "message 1" -F msgfile1 msgtag &&
        ! tag_exists msgtag &&
        ! git-tag -F msgfile1 -m "message 1" msgtag &&
        ! tag_exists msgtag &&
-       ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag &&
-       ! tag_exists msgtag &&
        ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag &&
        ! tag_exists msgtag
 '
@@ -673,6 +667,22 @@ test_expect_success 'creating a signed tag with -F - should succeed' '
        git diff expect actual
 '
 
+cat >fakeeditor <<'EOF'
+#!/bin/sh
+test -n "$1" && exec >"$1"
+echo A signed tag message
+echo from a fake editor.
+EOF
+chmod +x fakeeditor
+get_tag_header implied-annotate $commit commit $time >expect
+./fakeeditor >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success '-s implies annotated tag' '
+       GIT_EDITOR=./fakeeditor git-tag -s implied-annotate &&
+       get_tag_msg implied-annotate >actual &&
+       git diff expect actual
+'
+
 test_expect_success \
        'trying to create a signed tag with non-existing -F file should fail' '
        ! test -f nonexistingfile &&
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
new file mode 100755 (executable)
index 0000000..08f0f2a
--- /dev/null
@@ -0,0 +1,134 @@
+#!/bin/sh
+
+test_description='git-cvsimport basic tests'
+. ./test-lib.sh
+
+if ! ( type cvs && type cvsps ) >/dev/null 2>&1
+then
+       test_expect_success 'skipping cvsimport tests, cvs/cvsps not found' ''
+       test_done
+       exit
+fi
+
+CVSROOT=$(pwd)/cvsroot
+export CVSROOT
+# for clean cvsps cache
+HOME=$(pwd)
+export HOME
+
+test_expect_success 'setup cvsroot' 'cvs init'
+
+test_expect_success 'setup a cvs module' '
+
+       mkdir $CVSROOT/module &&
+       cvs co -d module-cvs module &&
+       cd module-cvs &&
+       cat <<EOF >o_fortuna &&
+O Fortuna
+velut luna
+statu variabilis,
+
+semper crescis
+aut decrescis;
+vita detestabilis
+
+nunc obdurat
+et tunc curat
+ludo mentis aciem,
+
+egestatem,
+potestatem
+dissolvit ut glaciem.
+EOF
+       cvs add o_fortuna &&
+       cat <<EOF >message &&
+add "O Fortuna" lyrics
+
+These public domain lyrics make an excellent sample text.
+EOF
+       cvs commit -F message &&
+       cd ..
+'
+
+test_expect_success 'import a trivial module' '
+
+       git cvsimport -a -z 0 -C module-git module &&
+       git diff module-cvs/o_fortuna module-git/o_fortuna
+
+'
+
+test_expect_success 'pack refs' 'cd module-git && git gc && cd ..'
+
+test_expect_success 'update cvs module' '
+
+       cd module-cvs &&
+       cat <<EOF >o_fortuna &&
+O Fortune,
+like the moon
+you are changeable,
+
+ever waxing
+and waning;
+hateful life
+
+first oppresses
+and then soothes
+as fancy takes it;
+
+poverty
+and power
+it melts them like ice.
+EOF
+       cat <<EOF >message &&
+translate to English
+
+My Latin is terrible.
+EOF
+       cvs commit -F message &&
+       cd ..
+'
+
+test_expect_success 'update git module' '
+
+       cd module-git &&
+       git cvsimport -a -z 0 module &&
+       git merge origin &&
+       cd .. &&
+       git diff module-cvs/o_fortuna module-git/o_fortuna
+
+'
+
+test_expect_success 'update cvs module' '
+
+       cd module-cvs &&
+               echo 1 >tick &&
+               cvs add tick &&
+               cvs commit -m 1
+       cd ..
+
+'
+
+test_expect_success 'cvsimport.module config works' '
+
+       cd module-git &&
+               git config cvsimport.module module &&
+               git cvsimport -a -z0 &&
+               git merge origin &&
+       cd .. &&
+       git diff module-cvs/tick module-git/tick
+
+'
+
+test_expect_success 'import from a CVS working tree' '
+
+       cvs co -d import-from-wt module &&
+       cd import-from-wt &&
+               git cvsimport -a -z0 &&
+               echo 1 >expect &&
+               git log -1 --pretty=format:%s%n >actual &&
+               git diff actual expect &&
+       cd ..
+
+'
+
+test_done
index 903a7b0f483fec5cbb6c6b372ab49cc28b655e75..db0fbdc701f1ef63cdc1a8b7d5c5e72322f91426 100644 (file)
@@ -7,13 +7,6 @@ struct name_entry {
        unsigned int mode;
 };
 
-static inline enum object_type object_type(unsigned int mode)
-{
-       return S_ISDIR(mode) ? OBJ_TREE :
-               S_ISGITLINK(mode) ? OBJ_COMMIT :
-               OBJ_BLOB;
-}
-
 struct tree_desc {
        const void *buffer;
        struct name_entry entry;
index aea16adde846b404900555f5f0dd6d87daea8bbf..e9eb795d64b9cd6a6940995d41dd9c3f3239df02 100644 (file)
@@ -71,12 +71,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
        int remove;
        int baselen = strlen(base);
        int src_size = len + 1;
-       int i_stk = i_stk;
        int retval = 0;
 
-       if (o->dir)
-               i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
-
        do {
                int i;
                const char *first;
@@ -255,8 +251,6 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
        } while (1);
 
  leave_directory:
-       if (o->dir)
-               pop_exclude_per_directory(o->dir, i_stk);
        return retval;
 }
 
index 9a6ef4a89ae659855ae0d2fa3006daf080190ed4..bf2fe8d237b51e87d40dbe60eb70f485273083ff 100644 (file)
@@ -231,6 +231,7 @@ static void wt_status_print_updated(struct wt_status *s)
        rev.diffopt.format_callback_data = s;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 100;
+       rev.diffopt.break_opt = 0;
        wt_read_cache(s);
        run_diff_index(&rev, 1);
 }