Code

Merge branch 'mo/cvsserver'
authorJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 20:37:04 +0000 (13:37 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 25 May 2008 20:37:04 +0000 (13:37 -0700)
* mo/cvsserver:
  Documentation: Fix skipped section level
  git-cvsserver: add ability to guess -kb from contents
  implement gitcvs.usecrlfattr
  git-cvsserver: add mechanism for managing working tree and current directory

88 files changed:
Documentation/CodingGuidelines
Documentation/Makefile
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/cvs-migration.txt [deleted file]
Documentation/git-add.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-daemon.txt
Documentation/git-filter-branch.txt
Documentation/git-prune.txt
Documentation/git-push.txt
Documentation/git-repack.txt
Documentation/git-rev-parse.txt
Documentation/git-show.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-web--browse.txt
Documentation/git.txt
Documentation/gitcvs-migration.txt [new file with mode: 0644]
Documentation/gittutorial-2.txt [new file with mode: 0644]
Documentation/gittutorial.txt [new file with mode: 0644]
Documentation/rev-list-options.txt
Documentation/technical/api-history-graph.txt [new file with mode: 0644]
Documentation/tutorial-2.txt [deleted file]
Documentation/tutorial.txt [deleted file]
Documentation/user-manual.txt
Makefile
builtin-add.c
builtin-checkout.c
builtin-commit.c
builtin-gc.c
builtin-init-db.c
builtin-log.c
builtin-mv.c
builtin-pack-objects.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-send-pack.c
cache.h
fast-import.c
git-am.sh
git-cvsexportcommit.perl
git-cvsimport.perl
git-gui/git-gui.sh
git-gui/lib/branch_create.tcl
git-gui/lib/branch_delete.tcl
git-gui/lib/checkout_op.tcl
git-gui/lib/database.tcl
git-gui/lib/spellcheck.tcl
git-gui/po/de.po
git-merge.sh
git-pull.sh
git-repack.sh
git-send-email.perl
git-submodule.sh
gitk-git/Makefile
gitk-git/gitk
gitk-git/po/de.po
gitk-git/po/es.po [new file with mode: 0644]
gitk-git/po/sv.po [new file with mode: 0644]
gitweb/gitweb.perl
graph.c [new file with mode: 0644]
graph.h [new file with mode: 0644]
http-push.c
lockfile.c
log-tree.c
read-cache.c
remote.c
remote.h
revision.c
revision.h
sha1_file.c
t/Makefile
t/README
t/t0050-filesystem.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t5511-refspec.sh
t/t5516-fetch-push.sh
t/t5520-pull.sh
t/t7400-submodule-basic.sh
t/t7502-commit.sh
t/t7701-repack-unpack-unreachable.sh [new file with mode: 0755]
t/t9001-send-email.sh
t/test-lib.sh
templates/hooks--prepare-commit-msg
wt-status.c
wt-status.h

index 994eb9159a2b0e8a10f4f9510165d420004203bf..d2a0a76e6cfb275080f3b66309591888f5302850 100644 (file)
@@ -89,6 +89,8 @@ For C programs:
    of "else if" statements, it can make sense to add braces to
    single line blocks.
 
+ - We try to avoid assignments inside if().
+
  - Try to make your code understandable.  You may put comments
    in, but comments invariably tend to stale out when the code
    they were describing changes.  Often splitting a function
index 4144d1e086d5e8443da41a0939e5bead4efcafba..9750334b9764dc5901069501fd6b6fe196d22ca5 100644 (file)
@@ -3,7 +3,8 @@ MAN1_TXT= \
                $(wildcard git-*.txt)) \
        gitk.txt
 MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt
-MAN7_TXT=git.txt gitcli.txt
+MAN7_TXT=git.txt gitcli.txt gittutorial.txt gittutorial-2.txt \
+       gitcvs-migration.txt
 
 MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
@@ -11,10 +12,7 @@ MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
 
 DOC_HTML=$(MAN_HTML)
 
-ARTICLES = tutorial
-ARTICLES += tutorial-2
-ARTICLES += core-tutorial
-ARTICLES += cvs-migration
+ARTICLES = core-tutorial
 ARTICLES += diffcore
 ARTICLES += howto-index
 ARTICLES += repository-layout
index 917ef5b4febaedece798aa4a97e9ebf90ff13d36..c298dc21c5a4ba28eaf35c4d12ae9f0030487e83 100644 (file)
@@ -523,8 +523,10 @@ color.status.<slot>::
        one of `header` (the header text of the status message),
        `added` or `updated` (files which are added but not committed),
        `changed` (files which are changed but not added in the index),
-       or `untracked` (files which are not tracked by git). The values of
-       these variables may be specified as in color.branch.<slot>.
+       `untracked` (files which are not tracked by git), or
+       `nobranch` (the color the 'no branch' warning is shown in, defaulting
+       to red). The values of these variables may be specified as in
+       color.branch.<slot>.
 
 commit.template::
        Specify a file to use as the template for new commit messages.
@@ -829,6 +831,12 @@ instaweb.port::
        The port number to bind the gitweb httpd to. See
        linkgit:git-instaweb[1].
 
+log.date::
+       Set default date-time mode for the log command. Setting log.date
+       value is similar to using git log's --date option. The value is one of
+       following alternatives: {relative,local,default,iso,rfc,short}.
+       See linkgit:git-log[1].
+
 log.showroot::
        If true, the initial commit will be shown as a big creation event.
        This is equivalent to a diff against an empty tree.
index 5a5531222d8f514b27ba63d9fc9ecc17335cdc3c..b50b5dd487fc7d7fef32408683373e9dade0f3c1 100644 (file)
@@ -8,7 +8,7 @@ This tutorial explains how to use the "core" git programs to set up and
 work with a git repository.
 
 If you just need to use git as a revision control system you may prefer
-to start with link:tutorial.html[a tutorial introduction to git] or
+to start with linkgit:gittutorial[7][a tutorial introduction to git] or
 link:user-manual.html[the git user manual].
 
 However, an understanding of these low-level tools can be helpful if
@@ -1581,7 +1581,7 @@ suggested in the previous section may be new to you. You do not
 have to worry. git supports "shared public repository" style of
 cooperation you are probably more familiar with as well.
 
-See link:cvs-migration.html[git for CVS users] for the details.
+See linkgit:gitcvs-migration[7][git for CVS users] for the details.
 
 Bundling your work together
 ---------------------------
diff --git a/Documentation/cvs-migration.txt b/Documentation/cvs-migration.txt
deleted file mode 100644 (file)
index 374bc87..0000000
+++ /dev/null
@@ -1,172 +0,0 @@
-git for CVS users
-=================
-
-Git differs from CVS in that every working tree contains a repository with
-a full copy of the project history, and no repository is inherently more
-important than any other.  However, you can emulate the CVS model by
-designating a single shared repository which people can synchronize with;
-this document explains how to do that.
-
-Some basic familiarity with git is required.  This
-link:tutorial.html[tutorial introduction to git] and the
-link:glossary.html[git glossary] should be sufficient.
-
-Developing against a shared repository
---------------------------------------
-
-Suppose a shared repository is set up in /pub/repo.git on the host
-foo.com.  Then as an individual committer you can clone the shared
-repository over ssh with:
-
-------------------------------------------------
-$ git clone foo.com:/pub/repo.git/ my-project
-$ cd my-project
-------------------------------------------------
-
-and hack away.  The equivalent of `cvs update` is
-
-------------------------------------------------
-$ git pull origin
-------------------------------------------------
-
-which merges in any work that others might have done since the clone
-operation.  If there are uncommitted changes in your working tree, commit
-them first before running git pull.
-
-[NOTE]
-================================
-The `pull` command knows where to get updates from because of certain
-configuration variables that were set by the first `git clone`
-command; see `git config -l` and the linkgit:git-config[1] man
-page for details.
-================================
-
-You can update the shared repository with your changes by first committing
-your changes, and then using the linkgit:git-push[1] command:
-
-------------------------------------------------
-$ git push origin master
-------------------------------------------------
-
-to "push" those commits to the shared repository.  If someone else has
-updated the repository more recently, `git push`, like `cvs commit`, will
-complain, in which case you must pull any changes before attempting the
-push again.
-
-In the `git push` command above we specify the name of the remote branch
-to update (`master`).  If we leave that out, `git push` tries to update
-any branches in the remote repository that have the same name as a branch
-in the local repository.  So the last `push` can be done with either of:
-
-------------
-$ git push origin
-$ git push foo.com:/pub/project.git/
-------------
-
-as long as the shared repository does not have any branches
-other than `master`.
-
-Setting Up a Shared Repository
-------------------------------
-
-We assume you have already created a git repository for your project,
-possibly created from scratch or from a tarball (see the
-link:tutorial.html[tutorial]), or imported from an already existing CVS
-repository (see the next section).
-
-Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
-repository (a repository without a working tree) and fetch your project into
-it:
-
-------------------------------------------------
-$ mkdir /pub/my-repo.git
-$ cd /pub/my-repo.git
-$ git --bare init --shared
-$ git --bare fetch /home/alice/myproject master:master
-------------------------------------------------
-
-Next, give every team member read/write access to this repository.  One
-easy way to do this is to give all the team members ssh access to the
-machine where the repository is hosted.  If you don't want to give them a
-full shell on the machine, there is a restricted shell which only allows
-users to do git pushes and pulls; see linkgit:git-shell[1].
-
-Put all the committers in the same group, and make the repository
-writable by that group:
-
-------------------------------------------------
-$ chgrp -R $group /pub/my-repo.git
-------------------------------------------------
-
-Make sure committers have a umask of at most 027, so that the directories
-they create are writable and searchable by other group members.
-
-Importing a CVS archive
------------------------
-
-First, install version 2.1 or higher of cvsps from
-link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
-sure it is in your path.  Then cd to a checked out CVS working directory
-of the project you are interested in and run linkgit:git-cvsimport[1]:
-
--------------------------------------------
-$ git cvsimport -C <destination> <module>
--------------------------------------------
-
-This puts a git archive of the named CVS module in the directory
-<destination>, which will be created if necessary.
-
-The import checks out from CVS every revision of every file.  Reportedly
-cvsimport can average some twenty revisions per second, so for a
-medium-sized project this should not take more than a couple of minutes.
-Larger projects or remote repositories may take longer.
-
-The main trunk is stored in the git branch named `origin`, and additional
-CVS branches are stored in git branches with the same names.  The most
-recent version of the main trunk is also left checked out on the `master`
-branch, so you can start adding your own changes right away.
-
-The import is incremental, so if you call it again next month it will
-fetch any CVS updates that have been made in the meantime.  For this to
-work, you must not modify the imported branches; instead, create new
-branches for your own changes, and merge in the imported branches as
-necessary.
-
-Advanced Shared Repository Management
--------------------------------------
-
-Git allows you to specify scripts called "hooks" to be run at certain
-points.  You can use these, for example, to send all commits to the shared
-repository to a mailing list.  See linkgit:githooks[5][Hooks used by git].
-
-You can enforce finer grained permissions using update hooks.  See
-link:howto/update-hook-example.txt[Controlling access to branches using
-update hooks].
-
-Providing CVS Access to a git Repository
-----------------------------------------
-
-It is also possible to provide true CVS access to a git repository, so
-that developers can still use CVS; see linkgit:git-cvsserver[1] for
-details.
-
-Alternative Development Models
-------------------------------
-
-CVS users are accustomed to giving a group of developers commit access to
-a common repository.  As we've seen, this is also possible with git.
-However, the distributed nature of git allows other development models,
-and you may want to first consider whether one of them might be a better
-fit for your project.
-
-For example, you can choose a single person to maintain the project's
-primary public repository.  Other developers then clone this repository
-and each work in their own clone.  When they have a series of changes that
-they're happy with, they ask the maintainer to pull from the branch
-containing the changes.  The maintainer reviews their changes and pulls
-them into the primary repository, which other developers pull from as
-necessary to stay coordinated.  The Linux kernel and other projects use
-variants of this model.
-
-With a small group, developers may just pull changes from each other's
-repositories without the need for a central maintainer.
index e0e730b6c44d473ad6cc9a23f7b727ab818d63a5..bb4abe26bb5d12916e8f396e3b94573f42e37a29 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh]
-          [--] <filepattern>...
+         [--ignore-errors] [--] <filepattern>...
 
 DESCRIPTION
 -----------
@@ -83,6 +83,11 @@ OPTIONS
        Don't add the file(s), but only refresh their stat()
        information in the index.
 
+\--ignore-errors::
+       If some files could not be added because of errors indexing
+       them, do not abort the operation, but continue adding the
+       others. The command shall still exit with non-zero status.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
index 9a47b4c397cec8e6782962bfe6766eee48b78ce1..363c36d694231c5876527ff0d062fbd385d66630 100644 (file)
@@ -65,11 +65,17 @@ OPTIONS
 -w::
        Specify the location of the CVS checkout to use for the export. This
        option does not require GIT_DIR to be set before execution if the
-       current directory is within a git repository.
+       current directory is within a git repository.  The default is the
+       value of 'cvsexportcommit.cvsdir'.
 
 -v::
        Verbose.
 
+CONFIGURATION
+-------------
+cvsexportcommit.cvsdir::
+       The default location of the CVS checkout to use for the export.
+
 EXAMPLES
 --------
 
index fd83bc7833312b20a7ef045a9dfd128fc2539838..cf261dd40d98a95d8eda4ee1008451ca67029bd3 100644 (file)
@@ -174,7 +174,7 @@ upload-pack::
 upload-archive::
        This serves `git-archive --remote`.  It is disabled by
        default, but a repository can enable it by setting
-       `daemon.uploadarchive` configuration item to `true`.
+       `daemon.uploadarch` configuration item to `true`.
 
 receive-pack::
        This serves `git-send-pack` clients, allowing anonymous
@@ -257,7 +257,7 @@ selectively enable/disable services per repository::
 ----------------------------------------------------------------
        [daemon]
                uploadpack = false
-               uploadarchive = true
+               uploadarch = true
 ----------------------------------------------------------------
 
 
index 8d80f0d074c92fccc2a9746c5f374c1626fdde97..506c37af7021e2e16caab5ffe72bca9bc2f28927 100644 (file)
@@ -183,6 +183,10 @@ or copyright violation) from all commits:
 git filter-branch --tree-filter 'rm filename' HEAD
 -------------------------------------------------------
 
+However, if the file is absent from the tree of some commit,
+a simple `rm filename` will fail for that tree and commit.
+Thus you may instead want to use `rm -f filename` as the script.
+
 A significantly faster version:
 
 --------------------------------------------------------------------------
index f92bb8cfa069283b0e8b78b717eedbef81bbe647..3178bc44ca4640e77a799128c9de631168e40c8a 100644 (file)
@@ -18,12 +18,15 @@ git-prune. See the section "NOTES", below.
 
 This runs `git-fsck --unreachable` using all the refs
 available in `$GIT_DIR/refs`, optionally with additional set of
-objects specified on the command line, and prunes all
+objects specified on the command line, and prunes all unpacked
 objects unreachable from any of these head objects from the object database.
 In addition, it
 prunes the unpacked objects that are also found in packs by
 running `git prune-packed`.
 
+Note that unreachable, packed objects will remain.  If this is
+not desired, see linkgit:git-repack[1].
+
 OPTIONS
 -------
 
index f06d94e318d6d0bf918430bafc746e96a2abe7a1..0cc44d79993735a8978af9e191e6846c6ceddb74 100644 (file)
@@ -46,12 +46,6 @@ specified, the same ref that <src> referred to locally).  If
 the optional leading plus `+` is used, the remote ref is updated
 even if it does not result in a fast forward update.
 +
-Note: If no explicit refspec is found, (that is neither
-on the command line nor in any Push line of the
-corresponding remotes file---see below), then "matching" heads are
-pushed: for every head that exists on the local side, the remote side is
-updated if a head of the same name already exists on the remote side.
-+
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
 A parameter <ref> without a colon pushes the <ref> from the source
@@ -59,6 +53,13 @@ repository to the destination repository under the same name.
 +
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
++
+The special refspec `:` (or `+:` to allow non-fast forward updates)
+directs git to push "matching" heads: for every head that exists on
+the local side, the remote side is updated if a head of the same name
+already exists on the remote side.  This is the default operation mode
+if no explicit refspec is found (that is neither on the command line
+nor in any Push line of the corresponding remotes file---see below).
 
 \--all::
        Instead of naming each ref to push, specifies that all
index 3d957492f8aea7c2760f7274605d71f5a25349b2..f81a6607dec7bbe9c985106770ecae8b5f01f3c0 100644 (file)
@@ -8,7 +8,7 @@ git-repack - Pack unpacked objects in a repository
 
 SYNOPSIS
 --------
-'git-repack' [-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
+'git-repack' [-a] [-A] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]
 
 DESCRIPTION
 -----------
@@ -37,6 +37,18 @@ OPTIONS
        leaves behind, but `git fsck --full` shows as
        dangling.
 
+-A::
+       Same as `-a`, but any unreachable objects in a previous
+       pack become loose, unpacked objects, instead of being
+       left in the old pack.  Unreachable objects are never
+       intentionally added to a pack, even when repacking.
+       When used with '-d', this option
+       prevents unreachable objects from being immediately
+       deleted by way of being left in the old pack and then
+       removed.  Instead, the loose unreachable objects
+       will be pruned according to normal expiry rules
+       with the next linkgit:git-gc[1].
+
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
@@ -55,8 +67,11 @@ OPTIONS
        linkgit:git-pack-objects[1].
 
 -n::
-        Do not update the server information with
-        `git update-server-info`.
+       Do not update the server information with
+       `git update-server-info`.  This option skips
+       updating local catalog files needed to publish
+       this repository (or a direct copy of it)
+       over HTTP or FTP.  See gitlink:git-update-server-info[1].
 
 --window=[N], --depth=[N]::
        These two options affect how the objects contained in the pack are
index b6b2fe92a15bcb3ea314e5411e957e41e67904d8..69599ffb679b17ebbe6ba85c772cd10a3511d89e 100644 (file)
@@ -378,6 +378,31 @@ C?        option C with an optional argument"
 eval `echo "$OPTS_SPEC" | git-rev-parse --parseopt -- "$@" || echo exit $?`
 ------------
 
+EXAMPLES
+--------
+
+* Print the object name of the current commit:
++
+------------
+$ git rev-parse --verify HEAD
+------------
+
+* Print the commit object name from the revision in the $REV shell variable:
++
+------------
+$ git rev-parse --verify $REV
+------------
++
+This will error out if $REV is empty or not a valid revision.
+
+* Same as above:
++
+------------
+$ git rev-parse --default master --verify $REV
+------------
++
+but if $REV is empty, the commit object name from master will be printed.
+
 
 Author
 ------
index dccf0e20eccd20d45ac83828dbc7c67fa6a86e1a..29ed0acc62156a743db2741d1f576fca87eebff1 100644 (file)
@@ -79,8 +79,6 @@ Documentation
 -------------
 Documentation by David Greaves, Petr Baudis and the git-list <git@vger.kernel.org>.
 
-This manual page is a stub. You can help the git documentation by expanding it.
-
 GIT
 ---
 Part of the linkgit:git[7] suite
index 6ffd896fbc78b783a7e00ed462f879cbff29d758..0668f295eee14facf0f9c4dd6ed2830cb6a348f8 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 [verse]
 'git-submodule' [--quiet] add [-b branch] [--] <repository> [<path>]
 'git-submodule' [--quiet] status [--cached] [--] [<path>...]
-'git-submodule' [--quiet] [init|update] [--] [<path>...]
+'git-submodule' [--quiet] init [--] [<path>...]
+'git-submodule' [--quiet] update [--init] [--] [<path>...]
 'git-submodule' [--quiet] summary [--summary-limit <n>] [commit] [--] [<path>...]
 
 
@@ -47,6 +48,10 @@ update::
        Update the registered submodules, i.e. clone missing submodules and
        checkout the commit specified in the index of the containing repository.
        This will make the submodules HEAD be detached.
++
+If the submodule is not yet initialized, and you just want to use the
+setting as stored in .gitmodules, you can automatically initialize the
+submodule with the --init option.
 
 summary::
        Show commit summary between the given commit (defaults to HEAD) and
index c6b56b4ef35879124f9e1bb8468a59761d09cb3b..3eae1ebb7dc104fd51c5e25d163ec33ab7b61ef1 100644 (file)
@@ -196,10 +196,10 @@ Any other arguments are passed directly to `git log'
        independently of git-svn functions.
 
 'create-ignore'::
-
        Recursively finds the svn:ignore property on directories and
        creates matching .gitignore files. The resulting files are staged to
-       be committed, but are not committed.
+       be committed, but are not committed. Use -r/--revision to refer to a
+       specfic revision.
 
 'show-ignore'::
        Recursively finds and lists the svn:ignore property on
@@ -223,6 +223,19 @@ Any other arguments are passed directly to `git log'
        argument.  Use the --url option to output only the value of the
        'URL:' field.
 
+'proplist'::
+       Lists the properties stored in the Subversion repository about a
+       given file or directory.  Use -r/--revision to refer to a specific
+       Subversion revision.
+
+'propget'::
+       Gets the Subversion property given as the first argument, for a
+       file.  A specific revision can be specified with -r/--revision.
+
+'show-externals'::
+       Shows the Subversion externals.  Use -r/--revision to specify a
+       specific revision.
+
 --
 
 OPTIONS
index 92ef57456581d98f066cabd2e6be84e73b7ae063..5ba0b9f1c9214b33948354f6b85c2b343674cbe5 100644 (file)
@@ -78,7 +78,7 @@ When 'konqueror' is specified by the a command line option or a
 configuration variable, we launch 'kfmclient' to try to open the HTML
 man page on an already opened konqueror in a new tab if possible.
 
-For consistency, we also try such a trick if 'brower.konqueror.path' is
+For consistency, we also try such a trick if 'browser.konqueror.path' is
 set to something like 'A_PATH_TO/konqueror'. That means we will try to
 launch 'A_PATH_TO/kfmclient' instead.
 
index adcd3e00b201af6182bbc441aa0b288a5d2db8a5..735f0d19c87408c730b261ec0e136d830bab821c 100644 (file)
@@ -20,10 +20,10 @@ Git is a fast, scalable, distributed revision control system with an
 unusually rich command set that provides both high-level operations
 and full access to internals.
 
-See this link:tutorial.html[tutorial] to get started, then see
+See this linkgit:gittutorial[7][tutorial] to get started, then see
 link:everyday.html[Everyday Git] for a useful minimum set of commands, and
 "man git-commandname" for documentation of each command.  CVS users may
-also want to read link:cvs-migration.html[CVS migration].  See
+also want to read linkgit:gitcvs-migration[7][CVS migration].  See
 link:user-manual.html[Git User's Manual] for a more in-depth
 introduction.
 
diff --git a/Documentation/gitcvs-migration.txt b/Documentation/gitcvs-migration.txt
new file mode 100644 (file)
index 0000000..c410805
--- /dev/null
@@ -0,0 +1,193 @@
+gitcvs-migration(7)
+===================
+
+NAME
+----
+gitcvs-migration - git for CVS users
+
+SYNOPSIS
+--------
+git cvsimport *
+
+DESCRIPTION
+-----------
+
+Git differs from CVS in that every working tree contains a repository with
+a full copy of the project history, and no repository is inherently more
+important than any other.  However, you can emulate the CVS model by
+designating a single shared repository which people can synchronize with;
+this document explains how to do that.
+
+Some basic familiarity with git is required.  This
+linkgit:gittutorial[7][tutorial introduction to git] and the
+link:glossary.html[git glossary] should be sufficient.
+
+Developing against a shared repository
+--------------------------------------
+
+Suppose a shared repository is set up in /pub/repo.git on the host
+foo.com.  Then as an individual committer you can clone the shared
+repository over ssh with:
+
+------------------------------------------------
+$ git clone foo.com:/pub/repo.git/ my-project
+$ cd my-project
+------------------------------------------------
+
+and hack away.  The equivalent of `cvs update` is
+
+------------------------------------------------
+$ git pull origin
+------------------------------------------------
+
+which merges in any work that others might have done since the clone
+operation.  If there are uncommitted changes in your working tree, commit
+them first before running git pull.
+
+[NOTE]
+================================
+The `pull` command knows where to get updates from because of certain
+configuration variables that were set by the first `git clone`
+command; see `git config -l` and the linkgit:git-config[1] man
+page for details.
+================================
+
+You can update the shared repository with your changes by first committing
+your changes, and then using the linkgit:git-push[1] command:
+
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
+
+to "push" those commits to the shared repository.  If someone else has
+updated the repository more recently, `git push`, like `cvs commit`, will
+complain, in which case you must pull any changes before attempting the
+push again.
+
+In the `git push` command above we specify the name of the remote branch
+to update (`master`).  If we leave that out, `git push` tries to update
+any branches in the remote repository that have the same name as a branch
+in the local repository.  So the last `push` can be done with either of:
+
+------------
+$ git push origin
+$ git push foo.com:/pub/project.git/
+------------
+
+as long as the shared repository does not have any branches
+other than `master`.
+
+Setting Up a Shared Repository
+------------------------------
+
+We assume you have already created a git repository for your project,
+possibly created from scratch or from a tarball (see the
+linkgit:gittutorial[7][tutorial]), or imported from an already existing CVS
+repository (see the next section).
+
+Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
+repository (a repository without a working tree) and fetch your project into
+it:
+
+------------------------------------------------
+$ mkdir /pub/my-repo.git
+$ cd /pub/my-repo.git
+$ git --bare init --shared
+$ git --bare fetch /home/alice/myproject master:master
+------------------------------------------------
+
+Next, give every team member read/write access to this repository.  One
+easy way to do this is to give all the team members ssh access to the
+machine where the repository is hosted.  If you don't want to give them a
+full shell on the machine, there is a restricted shell which only allows
+users to do git pushes and pulls; see linkgit:git-shell[1].
+
+Put all the committers in the same group, and make the repository
+writable by that group:
+
+------------------------------------------------
+$ chgrp -R $group /pub/my-repo.git
+------------------------------------------------
+
+Make sure committers have a umask of at most 027, so that the directories
+they create are writable and searchable by other group members.
+
+Importing a CVS archive
+-----------------------
+
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path.  Then cd to a checked out CVS working directory
+of the project you are interested in and run linkgit:git-cvsimport[1]:
+
+-------------------------------------------
+$ git cvsimport -C <destination> <module>
+-------------------------------------------
+
+This puts a git archive of the named CVS module in the directory
+<destination>, which will be created if necessary.
+
+The import checks out from CVS every revision of every file.  Reportedly
+cvsimport can average some twenty revisions per second, so for a
+medium-sized project this should not take more than a couple of minutes.
+Larger projects or remote repositories may take longer.
+
+The main trunk is stored in the git branch named `origin`, and additional
+CVS branches are stored in git branches with the same names.  The most
+recent version of the main trunk is also left checked out on the `master`
+branch, so you can start adding your own changes right away.
+
+The import is incremental, so if you call it again next month it will
+fetch any CVS updates that have been made in the meantime.  For this to
+work, you must not modify the imported branches; instead, create new
+branches for your own changes, and merge in the imported branches as
+necessary.
+
+Advanced Shared Repository Management
+-------------------------------------
+
+Git allows you to specify scripts called "hooks" to be run at certain
+points.  You can use these, for example, to send all commits to the shared
+repository to a mailing list.  See linkgit:githooks[5][Hooks used by git].
+
+You can enforce finer grained permissions using update hooks.  See
+link:howto/update-hook-example.txt[Controlling access to branches using
+update hooks].
+
+Providing CVS Access to a git Repository
+----------------------------------------
+
+It is also possible to provide true CVS access to a git repository, so
+that developers can still use CVS; see linkgit:git-cvsserver[1] for
+details.
+
+Alternative Development Models
+------------------------------
+
+CVS users are accustomed to giving a group of developers commit access to
+a common repository.  As we've seen, this is also possible with git.
+However, the distributed nature of git allows other development models,
+and you may want to first consider whether one of them might be a better
+fit for your project.
+
+For example, you can choose a single person to maintain the project's
+primary public repository.  Other developers then clone this repository
+and each work in their own clone.  When they have a series of changes that
+they're happy with, they ask the maintainer to pull from the branch
+containing the changes.  The maintainer reviews their changes and pulls
+them into the primary repository, which other developers pull from as
+necessary to stay coordinated.  The Linux kernel and other projects use
+variants of this model.
+
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7], linkgit:gittutorial-2[7],
+link:everyday.html[Everyday Git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
diff --git a/Documentation/gittutorial-2.txt b/Documentation/gittutorial-2.txt
new file mode 100644 (file)
index 0000000..5bbbf43
--- /dev/null
@@ -0,0 +1,428 @@
+gittutorial-2(7)
+================
+
+NAME
+----
+gittutorial-2 - A tutorial introduction to git: part two
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+You should work through linkgit:gittutorial[7][A tutorial introduction to
+git] before reading this tutorial.
+
+The goal of this tutorial is to introduce two fundamental pieces of
+git's architecture--the object database and the index file--and to
+provide the reader with everything necessary to understand the rest
+of the git documentation.
+
+The git object database
+-----------------------
+
+Let's start a new project and create a small amount of history:
+
+------------------------------------------------
+$ mkdir test-project
+$ cd test-project
+$ git init
+Initialized empty Git repository in .git/
+$ echo 'hello world' > file.txt
+$ git add .
+$ git commit -a -m "initial commit"
+Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+ create mode 100644 file.txt
+$ echo 'hello world!' >file.txt
+$ git commit -a -m "add emphasis"
+Created commit c4d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+What are the 40 digits of hex that git responded to the commit with?
+
+We saw in part one of the tutorial that commits have names like this.
+It turns out that every object in the git history is stored under
+such a 40-digit hex name.  That name is the SHA1 hash of the object's
+contents; among other things, this ensures that git will never store
+the same data twice (since identical data is given an identical SHA1
+name), and that the contents of a git object will never change (since
+that would change the object's name as well).
+
+It is expected that the content of the commit object you created while
+following the example above generates a different SHA1 hash than
+the one shown above because the commit object records the time when
+it was created and the name of the person performing the commit.
+
+We can ask git about this particular object with the cat-file
+command. Don't copy the 40 hex digits from this example but use those
+from your own version. Note that you can shorten it to only a few
+characters to save yourself typing all 40 hex digits:
+
+------------------------------------------------
+$ git-cat-file -t 54196cc2
+commit
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+A tree can refer to one or more "blob" objects, each corresponding to
+a file.  In addition, a tree can also refer to other tree objects,
+thus creating a directory hierarchy.  You can examine the contents of
+any tree using ls-tree (remember that a long enough initial portion
+of the SHA1 will also work):
+
+------------------------------------------------
+$ git ls-tree 92b8b694
+100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt
+------------------------------------------------
+
+Thus we see that this tree has one file in it.  The SHA1 hash is a
+reference to that file's data:
+
+------------------------------------------------
+$ git cat-file -t 3b18e512
+blob
+------------------------------------------------
+
+A "blob" is just file data, which we can also examine with cat-file:
+
+------------------------------------------------
+$ git cat-file blob 3b18e512
+hello world
+------------------------------------------------
+
+Note that this is the old file data; so the object that git named in
+its response to the initial tree was a tree with a snapshot of the
+directory state that was recorded by the first commit.
+
+All of these objects are stored under their SHA1 names inside the git
+directory:
+
+------------------------------------------------
+$ find .git/objects/
+.git/objects/
+.git/objects/pack
+.git/objects/info
+.git/objects/3b
+.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
+.git/objects/92
+.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
+.git/objects/54
+.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
+.git/objects/a0
+.git/objects/a0/423896973644771497bdc03eb99d5281615b51
+.git/objects/d0
+.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
+.git/objects/c4
+.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
+------------------------------------------------
+
+and the contents of these files is just the compressed data plus a
+header identifying their length and their type.  The type is either a
+blob, a tree, a commit, or a tag.
+
+The simplest commit to find is the HEAD commit, which we can find
+from .git/HEAD:
+
+------------------------------------------------
+$ cat .git/HEAD
+ref: refs/heads/master
+------------------------------------------------
+
+As you can see, this tells us which branch we're currently on, and it
+tells us this by naming a file under the .git directory, which itself
+contains a SHA1 name referring to a commit object, which we can
+examine with cat-file:
+
+------------------------------------------------
+$ cat .git/refs/heads/master
+c4d59f390b9cfd4318117afde11d601c1085f241
+$ git cat-file -t c4d59f39
+commit
+$ git cat-file commit c4d59f39
+tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
+parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
+
+add emphasis
+------------------------------------------------
+
+The "tree" object here refers to the new state of the tree:
+
+------------------------------------------------
+$ git ls-tree d0492b36
+100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
+$ git cat-file blob a0423896
+hello world!
+------------------------------------------------
+
+and the "parent" object refers to the previous commit:
+
+------------------------------------------------
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
+------------------------------------------------
+
+The tree object is the tree we examined first, and this commit is
+unusual in that it lacks any parent.
+
+Most commits have only one parent, but it is also common for a commit
+to have multiple parents.   In that case the commit represents a
+merge, with the parent references pointing to the heads of the merged
+branches.
+
+Besides blobs, trees, and commits, the only remaining type of object
+is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
+for details.
+
+So now we know how git uses the object database to represent a
+project's history:
+
+  * "commit" objects refer to "tree" objects representing the
+    snapshot of a directory tree at a particular point in the
+    history, and refer to "parent" commits to show how they're
+    connected into the project history.
+  * "tree" objects represent the state of a single directory,
+    associating directory names to "blob" objects containing file
+    data and "tree" objects containing subdirectory information.
+  * "blob" objects contain file data without any other structure.
+  * References to commit objects at the head of each branch are
+    stored in files under .git/refs/heads/.
+  * The name of the current branch is stored in .git/HEAD.
+
+Note, by the way, that lots of commands take a tree as an argument.
+But as we can see above, a tree can be referred to in many different
+ways--by the SHA1 name for that tree, by the name of a commit that
+refers to the tree, by the name of a branch whose head refers to that
+tree, etc.--and most such commands can accept any of these names.
+
+In command synopses, the word "tree-ish" is sometimes used to
+designate such an argument.
+
+The index file
+--------------
+
+The primary tool we've been using to create commits is "git commit
+-a", which creates a commit including every change you've made to
+your working tree.  But what if you want to commit changes only to
+certain files?  Or only certain changes to certain files?
+
+If we look at the way commits are created under the cover, we'll see
+that there are more flexible ways creating commits.
+
+Continuing with our test-project, let's modify file.txt again:
+
+------------------------------------------------
+$ echo "hello world, again" >>file.txt
+------------------------------------------------
+
+but this time instead of immediately making the commit, let's take an
+intermediate step, and ask for diffs along the way to keep track of
+what's happening:
+
+------------------------------------------------
+$ git diff
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+$ git add file.txt
+$ git diff
+------------------------------------------------
+
+The last diff is empty, but no new commits have been made, and the
+head still doesn't contain the new line:
+
+------------------------------------------------
+$ git-diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+So "git diff" is comparing against something other than the head.
+The thing that it's comparing against is actually the index file,
+which is stored in .git/index in a binary format, but whose contents
+we can examine with ls-files:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+$ git cat-file -t 513feba2
+blob
+$ git cat-file blob 513feba2
+hello world!
+hello world, again
+------------------------------------------------
+
+So what our "git add" did was store a new blob and then put
+a reference to it in the index file.  If we modify the file again,
+we'll see that the new modifications are reflected in the "git-diff"
+output:
+
+------------------------------------------------
+$ echo 'again?' >>file.txt
+$ git diff
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+With the right arguments, git diff can also show us the difference
+between the working directory and the last commit, or between the
+index and the last commit:
+
+------------------------------------------------
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index a042389..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,3 @@
+ hello world!
++hello world, again
++again?
+$ git diff --cached
+diff --git a/file.txt b/file.txt
+index a042389..513feba 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1 +1,2 @@
+ hello world!
++hello world, again
+------------------------------------------------
+
+At any time, we can create a new commit using "git commit" (without
+the -a option), and verify that the state committed only includes the
+changes stored in the index file, not the additional change that is
+still only in our working tree:
+
+------------------------------------------------
+$ git commit -m "repeat"
+$ git diff HEAD
+diff --git a/file.txt b/file.txt
+index 513feba..ba3da7b 100644
+--- a/file.txt
++++ b/file.txt
+@@ -1,2 +1,3 @@
+ hello world!
+ hello world, again
++again?
+------------------------------------------------
+
+So by default "git commit" uses the index to create the commit, not
+the working tree; the -a option to commit tells it to first update
+the index with all changes in the working tree.
+
+Finally, it's worth looking at the effect of "git add" on the index
+file:
+
+------------------------------------------------
+$ echo "goodbye, world" >closing.txt
+$ git add closing.txt
+------------------------------------------------
+
+The effect of the "git add" was to add one entry to the index file:
+
+------------------------------------------------
+$ git ls-files --stage
+100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
+100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
+------------------------------------------------
+
+And, as you can see with cat-file, this new entry refers to the
+current contents of the file:
+
+------------------------------------------------
+$ git cat-file blob 8b9743b2
+goodbye, world
+------------------------------------------------
+
+The "status" command is a useful way to get a quick summary of the
+situation:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#       new file: closing.txt
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#
+#       modified: file.txt
+#
+------------------------------------------------
+
+Since the current state of closing.txt is cached in the index file,
+it is listed as "Changes to be committed".  Since file.txt has
+changes in the working directory that aren't reflected in the index,
+it is marked "changed but not updated".  At this point, running "git
+commit" would create a commit that added closing.txt (with its new
+contents), but that didn't modify file.txt.
+
+Also, note that a bare "git diff" shows the changes to file.txt, but
+not the addition of closing.txt, because the version of closing.txt
+in the index file is identical to the one in the working directory.
+
+In addition to being the staging area for new commits, the index file
+is also populated from the object database when checking out a
+branch, and is used to hold the trees involved in a merge operation.
+See the link:core-tutorial.html[core tutorial] and the relevant man
+pages for details.
+
+What next?
+----------
+
+At this point you should know everything necessary to read the man
+pages for any of the git commands; one good place to start would be
+with the commands mentioned in link:everyday.html[Everyday git].  You
+should be able to find any unknown jargon in the
+link:glossary.html[Glossary].
+
+The link:user-manual.html[Git User's Manual] provides a more
+comprehensive introduction to git.
+
+The linkgit:gitcvs-migration[7][CVS migration] document explains how to
+import a CVS repository into git, and shows how to use git in a
+CVS-like way.
+
+For some interesting examples of git use, see the
+link:howto-index.html[howtos].
+
+For git developers, the link:core-tutorial.html[Core tutorial] goes
+into detail on the lower-level git mechanisms involved in, for
+example, creating a new commit.
+
+SEE ALSO
+--------
+linkgit:gittutorial[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
diff --git a/Documentation/gittutorial.txt b/Documentation/gittutorial.txt
new file mode 100644 (file)
index 0000000..898acdb
--- /dev/null
@@ -0,0 +1,606 @@
+gittutorial(7)
+==============
+
+NAME
+----
+gittutorial - A tutorial introduction to git (for version 1.5.1 or newer)
+
+SYNOPSIS
+--------
+git *
+
+DESCRIPTION
+-----------
+
+This tutorial explains how to import a new project into git, make
+changes to it, and share changes with other developers.
+
+If you are instead primarily interested in using git to fetch a project,
+for example, to test the latest version, you may prefer to start with
+the first two chapters of link:user-manual.html[The Git User's Manual].
+
+First, note that you can get documentation for a command such as "git
+diff" with:
+
+------------------------------------------------
+$ man git-diff
+------------------------------------------------
+
+It is a good idea to introduce yourself to git with your name and
+public email address before doing any operation.  The easiest
+way to do so is:
+
+------------------------------------------------
+$ git config --global user.name "Your Name Comes Here"
+$ git config --global user.email you@yourdomain.example.com
+------------------------------------------------
+
+
+Importing a new project
+-----------------------
+
+Assume you have a tarball project.tar.gz with your initial work.  You
+can place it under git revision control as follows.
+
+------------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init
+------------------------------------------------
+
+Git will reply
+
+------------------------------------------------
+Initialized empty Git repository in .git/
+------------------------------------------------
+
+You've now initialized the working directory--you may notice a new
+directory created, named ".git".
+
+Next, tell git to take a snapshot of the contents of all files under the
+current directory (note the '.'), with linkgit:git-add[1]:
+
+------------------------------------------------
+$ git add .
+------------------------------------------------
+
+This snapshot is now stored in a temporary staging area which git calls
+the "index".  You can permanently store the contents of the index in the
+repository with linkgit:git-commit[1]:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will prompt you for a commit message.  You've now stored the first
+version of your project in git.
+
+Making changes
+--------------
+
+Modify some files, then add their updated contents to the index:
+
+------------------------------------------------
+$ git add file1 file2 file3
+------------------------------------------------
+
+You are now ready to commit.  You can see what is about to be committed
+using linkgit:git-diff[1] with the --cached option:
+
+------------------------------------------------
+$ git diff --cached
+------------------------------------------------
+
+(Without --cached, linkgit:git-diff[1] will show you any changes that
+you've made but not yet added to the index.)  You can also get a brief
+summary of the situation with linkgit:git-status[1]:
+
+------------------------------------------------
+$ git status
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   file1
+#      modified:   file2
+#      modified:   file3
+#
+------------------------------------------------
+
+If you need to make any further adjustments, do so now, and then add any
+newly modified content to the index.  Finally, commit your changes with:
+
+------------------------------------------------
+$ git commit
+------------------------------------------------
+
+This will again prompt your for a message describing the change, and then
+record a new version of the project.
+
+Alternatively, instead of running `git add` beforehand, you can use
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+which will automatically notice any modified (but not new) files, add
+them to the index, and commit, all in one step.
+
+A note on commit messages: Though not required, it's a good idea to
+begin the commit message with a single short (less than 50 character)
+line summarizing the change, followed by a blank line and then a more
+thorough description.  Tools that turn commits into email, for
+example, use the first line on the Subject: line and the rest of the
+commit in the body.
+
+Git tracks content not files
+----------------------------
+
+Many revision control systems provide an "add" command that tells the
+system to start tracking changes to a new file.  Git's "add" command
+does something simpler and more powerful: `git add` is used both for new
+and newly modified files, and in both cases it takes a snapshot of the
+given files and stages that content in the index, ready for inclusion in
+the next commit.
+
+Viewing project history
+-----------------------
+
+At any point you can view the history of your changes using
+
+------------------------------------------------
+$ git log
+------------------------------------------------
+
+If you also want to see complete diffs at each step, use
+
+------------------------------------------------
+$ git log -p
+------------------------------------------------
+
+Often the overview of the change is useful to get a feel of
+each step
+
+------------------------------------------------
+$ git log --stat --summary
+------------------------------------------------
+
+Managing branches
+-----------------
+
+A single git repository can maintain multiple branches of
+development.  To create a new branch named "experimental", use
+
+------------------------------------------------
+$ git branch experimental
+------------------------------------------------
+
+If you now run
+
+------------------------------------------------
+$ git branch
+------------------------------------------------
+
+you'll get a list of all existing branches:
+
+------------------------------------------------
+  experimental
+* master
+------------------------------------------------
+
+The "experimental" branch is the one you just created, and the
+"master" branch is a default branch that was created for you
+automatically.  The asterisk marks the branch you are currently on;
+type
+
+------------------------------------------------
+$ git checkout experimental
+------------------------------------------------
+
+to switch to the experimental branch.  Now edit a file, commit the
+change, and switch back to the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+$ git checkout master
+------------------------------------------------
+
+Check that the change you made is no longer visible, since it was
+made on the experimental branch and you're back on the master branch.
+
+You can make a different change on the master branch:
+
+------------------------------------------------
+(edit file)
+$ git commit -a
+------------------------------------------------
+
+at this point the two branches have diverged, with different changes
+made in each.  To merge the changes made in experimental into master, run
+
+------------------------------------------------
+$ git merge experimental
+------------------------------------------------
+
+If the changes don't conflict, you're done.  If there are conflicts,
+markers will be left in the problematic files showing the conflict;
+
+------------------------------------------------
+$ git diff
+------------------------------------------------
+
+will show this.  Once you've edited the files to resolve the
+conflicts,
+
+------------------------------------------------
+$ git commit -a
+------------------------------------------------
+
+will commit the result of the merge. Finally,
+
+------------------------------------------------
+$ gitk
+------------------------------------------------
+
+will show a nice graphical representation of the resulting history.
+
+At this point you could delete the experimental branch with
+
+------------------------------------------------
+$ git branch -d experimental
+------------------------------------------------
+
+This command ensures that the changes in the experimental branch are
+already in the current branch.
+
+If you develop on a branch crazy-idea, then regret it, you can always
+delete the branch with
+
+-------------------------------------
+$ git branch -D crazy-idea
+-------------------------------------
+
+Branches are cheap and easy, so this is a good way to try something
+out.
+
+Using git for collaboration
+---------------------------
+
+Suppose that Alice has started a new project with a git repository in
+/home/alice/project, and that Bob, who has a home directory on the
+same machine, wants to contribute.
+
+Bob begins with:
+
+------------------------------------------------
+$ git clone /home/alice/project myrepo
+------------------------------------------------
+
+This creates a new directory "myrepo" containing a clone of Alice's
+repository.  The clone is on an equal footing with the original
+project, possessing its own copy of the original project's history.
+
+Bob then makes some changes and commits them:
+
+------------------------------------------------
+(edit files)
+$ git commit -a
+(repeat as necessary)
+------------------------------------------------
+
+When he's ready, he tells Alice to pull changes from the repository
+at /home/bob/myrepo.  She does this with:
+
+------------------------------------------------
+$ cd /home/alice/project
+$ git pull /home/bob/myrepo master
+------------------------------------------------
+
+This merges the changes from Bob's "master" branch into Alice's
+current branch.  If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts.  (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
+
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
+
+When you are working in a small closely knit group, it is not
+unusual to interact with the same repository over and over
+again.  By defining 'remote' repository shorthand, you can make
+it easier:
+
+------------------------------------------------
+$ git remote add bob /home/bob/myrepo
+------------------------------------------------
+
+With this, Alice can perform the first operation alone using the
+"git fetch" command without merging them with her own branch,
+using:
+
+-------------------------------------
+$ git fetch bob
+-------------------------------------
+
+Unlike the longhand form, when Alice fetches from Bob using a
+remote repository shorthand set up with `git remote`, what was
+fetched is stored in a remote tracking branch, in this case
+`bob/master`.  So after this:
+
+-------------------------------------
+$ git log -p master..bob/master
+-------------------------------------
+
+shows a list of all the changes that Bob made since he branched from
+Alice's master branch.
+
+After examining those changes, Alice
+could merge the changes into her master branch:
+
+-------------------------------------
+$ git merge bob/master
+-------------------------------------
+
+This `merge` can also be done by 'pulling from her own remote
+tracking branch', like this:
+
+-------------------------------------
+$ git pull . remotes/bob/master
+-------------------------------------
+
+Note that git pull always merges into the current branch,
+regardless of what else is given on the command line.
+
+Later, Bob can update his repo with Alice's latest changes using
+
+-------------------------------------
+$ git pull
+-------------------------------------
+
+Note that he doesn't need to give the path to Alice's repository;
+when Bob cloned Alice's repository, git stored the location of her
+repository in the repository configuration, and that location is
+used for pulls:
+
+-------------------------------------
+$ git config --get remote.origin.url
+/home/alice/project
+-------------------------------------
+
+(The complete configuration created by git-clone is visible using
+"git config -l", and the linkgit:git-config[1] man page
+explains the meaning of each option.)
+
+Git also keeps a pristine copy of Alice's master branch under the
+name "origin/master":
+
+-------------------------------------
+$ git branch -r
+  origin/master
+-------------------------------------
+
+If Bob later decides to work from a different host, he can still
+perform clones and pulls using the ssh protocol:
+
+-------------------------------------
+$ git clone alice.org:/home/alice/project myrepo
+-------------------------------------
+
+Alternatively, git has a native protocol, or can use rsync or http;
+see linkgit:git-pull[1] for details.
+
+Git can also be used in a CVS-like mode, with a central repository
+that various users push changes to; see linkgit:git-push[1] and
+linkgit:gitcvs-migration[7][git for CVS users].
+
+Exploring history
+-----------------
+
+Git history is represented as a series of interrelated commits.  We
+have already seen that the git log command can list those commits.
+Note that first line of each git log entry also gives a name for the
+commit:
+
+-------------------------------------
+$ git log
+commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+Author: Junio C Hamano <junkio@cox.net>
+Date:   Tue May 16 17:18:22 2006 -0700
+
+    merge-base: Clarify the comments on post processing.
+-------------------------------------
+
+We can give this name to git show to see the details about this
+commit.
+
+-------------------------------------
+$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
+-------------------------------------
+
+But there are other ways to refer to commits.  You can use any initial
+part of the name that is long enough to uniquely identify the commit:
+
+-------------------------------------
+$ git show c82a22c39c  # the first few characters of the name are
+                       # usually enough
+$ git show HEAD                # the tip of the current branch
+$ git show experimental        # the tip of the "experimental" branch
+-------------------------------------
+
+Every commit usually has one "parent" commit
+which points to the previous state of the project:
+
+-------------------------------------
+$ git show HEAD^  # to see the parent of HEAD
+$ git show HEAD^^ # to see the grandparent of HEAD
+$ git show HEAD~4 # to see the great-great grandparent of HEAD
+-------------------------------------
+
+Note that merge commits may have more than one parent:
+
+-------------------------------------
+$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
+$ git show HEAD^2 # show the second parent of HEAD
+-------------------------------------
+
+You can also give commits names of your own; after running
+
+-------------------------------------
+$ git-tag v2.5 1b2e1d63ff
+-------------------------------------
+
+you can refer to 1b2e1d63ff by the name "v2.5".  If you intend to
+share this name with other people (for example, to identify a release
+version), you should create a "tag" object, and perhaps sign it; see
+linkgit:git-tag[1] for details.
+
+Any git command that needs to know a commit can take any of these
+names.  For example:
+
+-------------------------------------
+$ git diff v2.5 HEAD    # compare the current HEAD to v2.5
+$ git branch stable v2.5 # start a new branch named "stable" based
+                        # at v2.5
+$ git reset --hard HEAD^ # reset your current branch and working
+                        # directory to its state at HEAD^
+-------------------------------------
+
+Be careful with that last command: in addition to losing any changes
+in the working directory, it will also remove all later commits from
+this branch.  If this branch is the only branch containing those
+commits, they will be lost.  Also, don't use "git reset" on a
+publicly-visible branch that other developers pull from, as it will
+force needless merges on other developers to clean up the history.
+If you need to undo changes that you have pushed, use linkgit:git-revert[1]
+instead.
+
+The git grep command can search for strings in any version of your
+project, so
+
+-------------------------------------
+$ git grep "hello" v2.5
+-------------------------------------
+
+searches for all occurrences of "hello" in v2.5.
+
+If you leave out the commit name, git grep will search any of the
+files it manages in your current directory.  So
+
+-------------------------------------
+$ git grep "hello"
+-------------------------------------
+
+is a quick way to search just the files that are tracked by git.
+
+Many git commands also take sets of commits, which can be specified
+in a number of ways.  Here are some examples with git log:
+
+-------------------------------------
+$ git log v2.5..v2.6            # commits between v2.5 and v2.6
+$ git log v2.5..                # commits since v2.5
+$ git log --since="2 weeks ago" # commits from the last 2 weeks
+$ git log v2.5.. Makefile       # commits since v2.5 which modify
+                               # Makefile
+-------------------------------------
+
+You can also give git log a "range" of commits where the first is not
+necessarily an ancestor of the second; for example, if the tips of
+the branches "stable-release" and "master" diverged from a common
+commit some time ago, then
+
+-------------------------------------
+$ git log stable..experimental
+-------------------------------------
+
+will list commits made in the experimental branch but not in the
+stable branch, while
+
+-------------------------------------
+$ git log experimental..stable
+-------------------------------------
+
+will show the list of commits made on the stable branch but not
+the experimental branch.
+
+The "git log" command has a weakness: it must present commits in a
+list.  When the history has lines of development that diverged and
+then merged back together, the order in which "git log" presents
+those commits is meaningless.
+
+Most projects with multiple contributors (such as the linux kernel,
+or git itself) have frequent merges, and gitk does a better job of
+visualizing their history.  For example,
+
+-------------------------------------
+$ gitk --since="2 weeks ago" drivers/
+-------------------------------------
+
+allows you to browse any commits from the last 2 weeks of commits
+that modified files under the "drivers" directory.  (Note: you can
+adjust gitk's fonts by holding down the control key while pressing
+"-" or "+".)
+
+Finally, most commands that take filenames will optionally allow you
+to precede any filename by a commit, to specify a particular version
+of the file:
+
+-------------------------------------
+$ git diff v2.5:Makefile HEAD:Makefile.in
+-------------------------------------
+
+You can also use "git show" to see any such file:
+
+-------------------------------------
+$ git show v2.5:Makefile
+-------------------------------------
+
+Next Steps
+----------
+
+This tutorial should be enough to perform basic distributed revision
+control for your projects.  However, to fully understand the depth
+and power of git you need to understand two simple ideas on which it
+is based:
+
+  * The object database is the rather elegant system used to
+    store the history of your project--files, directories, and
+    commits.
+
+  * The index file is a cache of the state of a directory tree,
+    used to create commits, check out working directories, and
+    hold the various trees involved in a merge.
+
+linkgit:gittutorial-2[7][Part two of this tutorial] explains the object
+database, the index file, and a few other odds and ends that you'll
+need to make the most of git.
+
+If you don't want to continue with that right away, a few other
+digressions that may be interesting at this point are:
+
+  * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
+    series of git commits into emailed patches, and vice versa,
+    useful for projects such as the linux kernel which rely heavily
+    on emailed patches.
+
+  * linkgit:git-bisect[1]: When there is a regression in your
+    project, one way to track down the bug is by searching through
+    the history to find the exact commit that's to blame.  Git bisect
+    can help you perform a binary search for that commit.  It is
+    smart enough to perform a close-to-optimal search even in the
+    case of complex non-linear history with lots of merged branches.
+
+  * link:everyday.html[Everyday GIT with 20 Commands Or So]
+
+  * linkgit:gitcvs-migration[7][git for CVS users].
+
+SEE ALSO
+--------
+linkgit:gittutorial-2[7],
+linkgit:gitcvs-migration[7],
+link:everyday.html[Everyday git],
+link:user-manual.html[The Git User's Manual]
+
+GIT
+---
+Part of the linkgit:git[7] suite.
index 2648a550850bf07128e4a0a14c82860d6bad17b8..dfcef79a6b772ae5fb11fc378772042ba465615f 100644 (file)
@@ -13,10 +13,11 @@ include::pretty-options.txt[]
 
        Synonym for `--date=relative`.
 
---date={relative,local,default,iso,rfc}::
+--date={relative,local,default,iso,rfc,short}::
 
        Only takes effect for dates shown in human-readable format, such
-       as when using "--pretty".
+       as when using "--pretty". `log.date` config variable sets a default
+       value for log command's --date option.
 +
 `--date=relative` shows dates relative to the current time,
 e.g. "2 hours ago".
@@ -75,6 +76,16 @@ you would get an output line this:
        -xxxxxxx... 1st on a
 -----------------------------------------------------------------------
 
+--graph::
+
+       Draw a text-based graphical representation of the commit history
+       on the left hand side of the output.  This may cause extra lines
+       to be printed in between commits, in order for the graph history
+       to be drawn properly.
++
+This implies the '--topo-order' option by default, but the
+'--date-order' option may also be specified.
+
 Diff Formatting
 ~~~~~~~~~~~~~~~
 
diff --git a/Documentation/technical/api-history-graph.txt b/Documentation/technical/api-history-graph.txt
new file mode 100644 (file)
index 0000000..ce1c08e
--- /dev/null
@@ -0,0 +1,179 @@
+history graph API
+=================
+
+The graph API is used to draw a text-based representation of the commit
+history.  The API generates the graph in a line-by-line fashion.
+
+Functions
+---------
+
+Core functions:
+
+* `graph_init()` creates a new `struct git_graph`
+
+* `graph_release()` destroys a `struct git_graph`, and frees the memory
+  associated with it.
+
+* `graph_update()` moves the graph to a new commit.
+
+* `graph_next_line()` outputs the next line of the graph into a strbuf.  It
+  does not add a terminating newline.
+
+* `graph_padding_line()` outputs a line of vertical padding in the graph.  It
+  is similar to `graph_next_line()`, but is guaranteed to never print the line
+  containing the current commit.  Where `graph_next_line()` would print the
+  commit line next, `graph_padding_line()` prints a line that simply extends
+  all branch lines downwards one row, leaving their positions unchanged.
+
+* `graph_is_commit_finished()` determines if the graph has output all lines
+  necessary for the current commit.  If `graph_update()` is called before all
+  lines for the current commit have been printed, the next call to
+  `graph_next_line()` will output an ellipsis, to indicate that a portion of
+  the graph was omitted.
+
+The following utility functions are wrappers around `graph_next_line()` and
+`graph_is_commit_finished()`.  They always print the output to stdout.
+They can all be called with a NULL graph argument, in which case no graph
+output will be printed.
+
+* `graph_show_commit()` calls `graph_next_line()` until it returns non-zero.
+  This prints all graph lines up to, and including, the line containing this
+  commit.  Output is printed to stdout.  The last line printed does not contain
+  a terminating newline.  This should not be called if the commit line has
+  already been printed, or it will loop forever.
+
+* `graph_show_oneline()` calls `graph_next_line()` and prints the result to
+  stdout.  The line printed does not contain a terminating newline.
+
+* `graph_show_padding()` calls `graph_padding_line()` and prints the result to
+  stdout.  The line printed does not contain a terminating newline.
+
+* `graph_show_remainder()` calls `graph_next_line()` until
+  `graph_is_commit_finished()` returns non-zero.  Output is printed to stdout.
+  The last line printed does not contain a terminating newline.  Returns 1 if
+  output was printed, and 0 if no output was necessary.
+
+* `graph_show_strbuf()` prints the specified strbuf to stdout, prefixing all
+  lines but the first with a graph line.  The caller is responsible for
+  ensuring graph output for the first line has already been printed to stdout.
+  (This can be done with `graph_show_commit()` or `graph_show_oneline()`.)  If
+  a NULL graph is supplied, the strbuf is printed as-is.
+
+* `graph_show_commit_msg()` is similar to `graph_show_strbuf()`, but it also
+  prints the remainder of the graph, if more lines are needed after the strbuf
+  ends.  It is better than directly calling `graph_show_strbuf()` followed by
+  `graph_show_remainder()` since it properly handles buffers that do not end in
+  a terminating newline.  The output printed by `graph_show_commit_msg()` will
+  end in a newline if and only if the strbuf ends in a newline.
+
+Data structure
+--------------
+`struct git_graph` is an opaque data type used to store the current graph
+state.
+
+Calling sequence
+----------------
+
+* Create a `struct git_graph` by calling `graph_init()`.  When using the
+  revision walking API, this is done automatically by `setup_revisions()` if
+  the '--graph' option is supplied.
+
+* Use the revision walking API to walk through a group of contiguous commits.
+  The `get_revision()` function automatically calls `graph_update()` each time
+  it is invoked.
+
+* For each commit, call `graph_next_line()` repeatedly, until
+  `graph_is_commit_finished()` returns non-zero.  Each call go
+  `graph_next_line()` will output a single line of the graph.  The resulting
+  lines will not contain any newlines.  `graph_next_line()` returns 1 if the
+  resulting line contains the current commit, or 0 if this is merely a line
+  needed to adjust the graph before or after the current commit.  This return
+  value can be used to determine where to print the commit summary information
+  alongside the graph output.
+
+Limitations
+-----------
+
+* `graph_update()` must be called with commits in topological order.  It should
+  not be called on a commit if it has already been invoked with an ancestor of
+  that commit, or the graph output will be incorrect.
+
+* `graph_update()` must be called on a contiguous group of commits.  If
+  `graph_update()` is called on a particular commit, it should later be called
+  on all parents of that commit.  Parents must not be skipped, or the graph
+  output will appear incorrect.
++
+`graph_update()` may be used on a pruned set of commits only if the parent list
+has been rewritten so as to include only ancestors from the pruned set.
+
+* The graph API does not currently support reverse commit ordering.  In
+  order to implement reverse ordering, the graphing API needs an
+  (efficient) mechanism to find the children of a commit.
+
+Sample usage
+------------
+
+------------
+struct commit *commit;
+struct git_graph *graph = graph_init();
+
+while ((commit = get_revision(opts)) != NULL) {
+       graph_update(graph, commit);
+       while (!graph_is_commit_finished(graph))
+       {
+               struct strbuf sb;
+               int is_commit_line;
+
+               strbuf_init(&sb, 0);
+               is_commit_line = graph_next_line(graph, &sb);
+               fputs(sb.buf, stdout);
+
+               if (is_commit_line)
+                       log_tree_commit(opts, commit);
+               else
+                       putchar(opts->diffopt.line_termination);
+       }
+}
+
+graph_release(graph);
+------------
+
+Sample output
+-------------
+
+The following is an example of the output from the graph API.  This output does
+not include any commit summary information--callers are responsible for
+outputting that information, if desired.
+
+------------
+*
+*
+M
+|\
+* |
+| | *
+| \ \
+|  \ \
+M-. \ \
+|\ \ \ \
+| | * | |
+| | | | | *
+| | | | | *
+| | | | | M
+| | | | | |\
+| | | | | | *
+| * | | | | |
+| | | | | M  \
+| | | | | |\  |
+| | | | * | | |
+| | | | * | | |
+* | | | | | | |
+| |/ / / / / /
+|/| / / / / /
+* | | | | | |
+|/ / / / / /
+* | | | | |
+| | | | | *
+| | | | |/
+| | | | *
+------------
diff --git a/Documentation/tutorial-2.txt b/Documentation/tutorial-2.txt
deleted file mode 100644 (file)
index 7fac47d..0000000
+++ /dev/null
@@ -1,406 +0,0 @@
-A tutorial introduction to git: part two
-========================================
-
-You should work through link:tutorial.html[A tutorial introduction to
-git] before reading this tutorial.
-
-The goal of this tutorial is to introduce two fundamental pieces of
-git's architecture--the object database and the index file--and to
-provide the reader with everything necessary to understand the rest
-of the git documentation.
-
-The git object database
------------------------
-
-Let's start a new project and create a small amount of history:
-
-------------------------------------------------
-$ mkdir test-project
-$ cd test-project
-$ git init
-Initialized empty Git repository in .git/
-$ echo 'hello world' > file.txt
-$ git add .
-$ git commit -a -m "initial commit"
-Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
- create mode 100644 file.txt
-$ echo 'hello world!' >file.txt
-$ git commit -a -m "add emphasis"
-Created commit c4d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-What are the 40 digits of hex that git responded to the commit with?
-
-We saw in part one of the tutorial that commits have names like this.
-It turns out that every object in the git history is stored under
-such a 40-digit hex name.  That name is the SHA1 hash of the object's
-contents; among other things, this ensures that git will never store
-the same data twice (since identical data is given an identical SHA1
-name), and that the contents of a git object will never change (since
-that would change the object's name as well).
-
-It is expected that the content of the commit object you created while
-following the example above generates a different SHA1 hash than
-the one shown above because the commit object records the time when
-it was created and the name of the person performing the commit.
-
-We can ask git about this particular object with the cat-file
-command. Don't copy the 40 hex digits from this example but use those
-from your own version. Note that you can shorten it to only a few
-characters to save yourself typing all 40 hex digits:
-
-------------------------------------------------
-$ git-cat-file -t 54196cc2
-commit
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-A tree can refer to one or more "blob" objects, each corresponding to
-a file.  In addition, a tree can also refer to other tree objects,
-thus creating a directory hierarchy.  You can examine the contents of
-any tree using ls-tree (remember that a long enough initial portion
-of the SHA1 will also work):
-
-------------------------------------------------
-$ git ls-tree 92b8b694
-100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad    file.txt
-------------------------------------------------
-
-Thus we see that this tree has one file in it.  The SHA1 hash is a
-reference to that file's data:
-
-------------------------------------------------
-$ git cat-file -t 3b18e512
-blob
-------------------------------------------------
-
-A "blob" is just file data, which we can also examine with cat-file:
-
-------------------------------------------------
-$ git cat-file blob 3b18e512
-hello world
-------------------------------------------------
-
-Note that this is the old file data; so the object that git named in
-its response to the initial tree was a tree with a snapshot of the
-directory state that was recorded by the first commit.
-
-All of these objects are stored under their SHA1 names inside the git
-directory:
-
-------------------------------------------------
-$ find .git/objects/
-.git/objects/
-.git/objects/pack
-.git/objects/info
-.git/objects/3b
-.git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad
-.git/objects/92
-.git/objects/92/b8b694ffb1675e5975148e1121810081dbdffe
-.git/objects/54
-.git/objects/54/196cc2703dc165cbd373a65a4dcf22d50ae7f7
-.git/objects/a0
-.git/objects/a0/423896973644771497bdc03eb99d5281615b51
-.git/objects/d0
-.git/objects/d0/492b368b66bdabf2ac1fd8c92b39d3db916e59
-.git/objects/c4
-.git/objects/c4/d59f390b9cfd4318117afde11d601c1085f241
-------------------------------------------------
-
-and the contents of these files is just the compressed data plus a
-header identifying their length and their type.  The type is either a
-blob, a tree, a commit, or a tag.
-
-The simplest commit to find is the HEAD commit, which we can find
-from .git/HEAD:
-
-------------------------------------------------
-$ cat .git/HEAD
-ref: refs/heads/master
-------------------------------------------------
-
-As you can see, this tells us which branch we're currently on, and it
-tells us this by naming a file under the .git directory, which itself
-contains a SHA1 name referring to a commit object, which we can
-examine with cat-file:
-
-------------------------------------------------
-$ cat .git/refs/heads/master
-c4d59f390b9cfd4318117afde11d601c1085f241
-$ git cat-file -t c4d59f39
-commit
-$ git cat-file commit c4d59f39
-tree d0492b368b66bdabf2ac1fd8c92b39d3db916e59
-parent 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143418702 -0500
-
-add emphasis
-------------------------------------------------
-
-The "tree" object here refers to the new state of the tree:
-
-------------------------------------------------
-$ git ls-tree d0492b36
-100644 blob a0423896973644771497bdc03eb99d5281615b51    file.txt
-$ git cat-file blob a0423896
-hello world!
-------------------------------------------------
-
-and the "parent" object refers to the previous commit:
-
-------------------------------------------------
-$ git-cat-file commit 54196cc2
-tree 92b8b694ffb1675e5975148e1121810081dbdffe
-author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
-
-initial commit
-------------------------------------------------
-
-The tree object is the tree we examined first, and this commit is
-unusual in that it lacks any parent.
-
-Most commits have only one parent, but it is also common for a commit
-to have multiple parents.   In that case the commit represents a
-merge, with the parent references pointing to the heads of the merged
-branches.
-
-Besides blobs, trees, and commits, the only remaining type of object
-is a "tag", which we won't discuss here; refer to linkgit:git-tag[1]
-for details.
-
-So now we know how git uses the object database to represent a
-project's history:
-
-  * "commit" objects refer to "tree" objects representing the
-    snapshot of a directory tree at a particular point in the
-    history, and refer to "parent" commits to show how they're
-    connected into the project history.
-  * "tree" objects represent the state of a single directory,
-    associating directory names to "blob" objects containing file
-    data and "tree" objects containing subdirectory information.
-  * "blob" objects contain file data without any other structure.
-  * References to commit objects at the head of each branch are
-    stored in files under .git/refs/heads/.
-  * The name of the current branch is stored in .git/HEAD.
-
-Note, by the way, that lots of commands take a tree as an argument.
-But as we can see above, a tree can be referred to in many different
-ways--by the SHA1 name for that tree, by the name of a commit that
-refers to the tree, by the name of a branch whose head refers to that
-tree, etc.--and most such commands can accept any of these names.
-
-In command synopses, the word "tree-ish" is sometimes used to
-designate such an argument.
-
-The index file
---------------
-
-The primary tool we've been using to create commits is "git commit
--a", which creates a commit including every change you've made to
-your working tree.  But what if you want to commit changes only to
-certain files?  Or only certain changes to certain files?
-
-If we look at the way commits are created under the cover, we'll see
-that there are more flexible ways creating commits.
-
-Continuing with our test-project, let's modify file.txt again:
-
-------------------------------------------------
-$ echo "hello world, again" >>file.txt
-------------------------------------------------
-
-but this time instead of immediately making the commit, let's take an
-intermediate step, and ask for diffs along the way to keep track of
-what's happening:
-
-------------------------------------------------
-$ git diff
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-$ git add file.txt
-$ git diff
-------------------------------------------------
-
-The last diff is empty, but no new commits have been made, and the
-head still doesn't contain the new line:
-
-------------------------------------------------
-$ git-diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-So "git diff" is comparing against something other than the head.
-The thing that it's comparing against is actually the index file,
-which is stored in .git/index in a binary format, but whose contents
-we can examine with ls-files:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
-$ git cat-file -t 513feba2
-blob
-$ git cat-file blob 513feba2
-hello world!
-hello world, again
-------------------------------------------------
-
-So what our "git add" did was store a new blob and then put
-a reference to it in the index file.  If we modify the file again,
-we'll see that the new modifications are reflected in the "git-diff"
-output:
-
-------------------------------------------------
-$ echo 'again?' >>file.txt
-$ git diff
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-With the right arguments, git diff can also show us the difference
-between the working directory and the last commit, or between the
-index and the last commit:
-
-------------------------------------------------
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index a042389..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,3 @@
- hello world!
-+hello world, again
-+again?
-$ git diff --cached
-diff --git a/file.txt b/file.txt
-index a042389..513feba 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1 +1,2 @@
- hello world!
-+hello world, again
-------------------------------------------------
-
-At any time, we can create a new commit using "git commit" (without
-the -a option), and verify that the state committed only includes the
-changes stored in the index file, not the additional change that is
-still only in our working tree:
-
-------------------------------------------------
-$ git commit -m "repeat"
-$ git diff HEAD
-diff --git a/file.txt b/file.txt
-index 513feba..ba3da7b 100644
---- a/file.txt
-+++ b/file.txt
-@@ -1,2 +1,3 @@
- hello world!
- hello world, again
-+again?
-------------------------------------------------
-
-So by default "git commit" uses the index to create the commit, not
-the working tree; the -a option to commit tells it to first update
-the index with all changes in the working tree.
-
-Finally, it's worth looking at the effect of "git add" on the index
-file:
-
-------------------------------------------------
-$ echo "goodbye, world" >closing.txt
-$ git add closing.txt
-------------------------------------------------
-
-The effect of the "git add" was to add one entry to the index file:
-
-------------------------------------------------
-$ git ls-files --stage
-100644 8b9743b20d4b15be3955fc8d5cd2b09cd2336138 0       closing.txt
-100644 513feba2e53ebbd2532419ded848ba19de88ba00 0       file.txt
-------------------------------------------------
-
-And, as you can see with cat-file, this new entry refers to the
-current contents of the file:
-
-------------------------------------------------
-$ git cat-file blob 8b9743b2
-goodbye, world
-------------------------------------------------
-
-The "status" command is a useful way to get a quick summary of the
-situation:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#       new file: closing.txt
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#
-#       modified: file.txt
-#
-------------------------------------------------
-
-Since the current state of closing.txt is cached in the index file,
-it is listed as "Changes to be committed".  Since file.txt has
-changes in the working directory that aren't reflected in the index,
-it is marked "changed but not updated".  At this point, running "git
-commit" would create a commit that added closing.txt (with its new
-contents), but that didn't modify file.txt.
-
-Also, note that a bare "git diff" shows the changes to file.txt, but
-not the addition of closing.txt, because the version of closing.txt
-in the index file is identical to the one in the working directory.
-
-In addition to being the staging area for new commits, the index file
-is also populated from the object database when checking out a
-branch, and is used to hold the trees involved in a merge operation.
-See the link:core-tutorial.html[core tutorial] and the relevant man
-pages for details.
-
-What next?
-----------
-
-At this point you should know everything necessary to read the man
-pages for any of the git commands; one good place to start would be
-with the commands mentioned in link:everyday.html[Everyday git].  You
-should be able to find any unknown jargon in the
-link:glossary.html[Glossary].
-
-The link:user-manual.html[Git User's Manual] provides a more
-comprehensive introduction to git.
-
-The link:cvs-migration.html[CVS migration] document explains how to
-import a CVS repository into git, and shows how to use git in a
-CVS-like way.
-
-For some interesting examples of git use, see the
-link:howto-index.html[howtos].
-
-For git developers, the link:core-tutorial.html[Core tutorial] goes
-into detail on the lower-level git mechanisms involved in, for
-example, creating a new commit.
diff --git a/Documentation/tutorial.txt b/Documentation/tutorial.txt
deleted file mode 100644 (file)
index e2bbda5..0000000
+++ /dev/null
@@ -1,584 +0,0 @@
-A tutorial introduction to git (for version 1.5.1 or newer)
-===========================================================
-
-This tutorial explains how to import a new project into git, make
-changes to it, and share changes with other developers.
-
-If you are instead primarily interested in using git to fetch a project,
-for example, to test the latest version, you may prefer to start with
-the first two chapters of link:user-manual.html[The Git User's Manual].
-
-First, note that you can get documentation for a command such as "git
-diff" with:
-
-------------------------------------------------
-$ man git-diff
-------------------------------------------------
-
-It is a good idea to introduce yourself to git with your name and
-public email address before doing any operation.  The easiest
-way to do so is:
-
-------------------------------------------------
-$ git config --global user.name "Your Name Comes Here"
-$ git config --global user.email you@yourdomain.example.com
-------------------------------------------------
-
-
-Importing a new project
------------------------
-
-Assume you have a tarball project.tar.gz with your initial work.  You
-can place it under git revision control as follows.
-
-------------------------------------------------
-$ tar xzf project.tar.gz
-$ cd project
-$ git init
-------------------------------------------------
-
-Git will reply
-
-------------------------------------------------
-Initialized empty Git repository in .git/
-------------------------------------------------
-
-You've now initialized the working directory--you may notice a new
-directory created, named ".git".
-
-Next, tell git to take a snapshot of the contents of all files under the
-current directory (note the '.'), with linkgit:git-add[1]:
-
-------------------------------------------------
-$ git add .
-------------------------------------------------
-
-This snapshot is now stored in a temporary staging area which git calls
-the "index".  You can permanently store the contents of the index in the
-repository with linkgit:git-commit[1]:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will prompt you for a commit message.  You've now stored the first
-version of your project in git.
-
-Making changes
---------------
-
-Modify some files, then add their updated contents to the index:
-
-------------------------------------------------
-$ git add file1 file2 file3
-------------------------------------------------
-
-You are now ready to commit.  You can see what is about to be committed
-using linkgit:git-diff[1] with the --cached option:
-
-------------------------------------------------
-$ git diff --cached
-------------------------------------------------
-
-(Without --cached, linkgit:git-diff[1] will show you any changes that
-you've made but not yet added to the index.)  You can also get a brief
-summary of the situation with linkgit:git-status[1]:
-
-------------------------------------------------
-$ git status
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      modified:   file1
-#      modified:   file2
-#      modified:   file3
-#
-------------------------------------------------
-
-If you need to make any further adjustments, do so now, and then add any
-newly modified content to the index.  Finally, commit your changes with:
-
-------------------------------------------------
-$ git commit
-------------------------------------------------
-
-This will again prompt your for a message describing the change, and then
-record a new version of the project.
-
-Alternatively, instead of running `git add` beforehand, you can use
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-which will automatically notice any modified (but not new) files, add
-them to the index, and commit, all in one step.
-
-A note on commit messages: Though not required, it's a good idea to
-begin the commit message with a single short (less than 50 character)
-line summarizing the change, followed by a blank line and then a more
-thorough description.  Tools that turn commits into email, for
-example, use the first line on the Subject: line and the rest of the
-commit in the body.
-
-Git tracks content not files
-----------------------------
-
-Many revision control systems provide an "add" command that tells the
-system to start tracking changes to a new file.  Git's "add" command
-does something simpler and more powerful: `git add` is used both for new
-and newly modified files, and in both cases it takes a snapshot of the
-given files and stages that content in the index, ready for inclusion in
-the next commit.
-
-Viewing project history
------------------------
-
-At any point you can view the history of your changes using
-
-------------------------------------------------
-$ git log
-------------------------------------------------
-
-If you also want to see complete diffs at each step, use
-
-------------------------------------------------
-$ git log -p
-------------------------------------------------
-
-Often the overview of the change is useful to get a feel of
-each step
-
-------------------------------------------------
-$ git log --stat --summary
-------------------------------------------------
-
-Managing branches
------------------
-
-A single git repository can maintain multiple branches of
-development.  To create a new branch named "experimental", use
-
-------------------------------------------------
-$ git branch experimental
-------------------------------------------------
-
-If you now run
-
-------------------------------------------------
-$ git branch
-------------------------------------------------
-
-you'll get a list of all existing branches:
-
-------------------------------------------------
-  experimental
-* master
-------------------------------------------------
-
-The "experimental" branch is the one you just created, and the
-"master" branch is a default branch that was created for you
-automatically.  The asterisk marks the branch you are currently on;
-type
-
-------------------------------------------------
-$ git checkout experimental
-------------------------------------------------
-
-to switch to the experimental branch.  Now edit a file, commit the
-change, and switch back to the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-$ git checkout master
-------------------------------------------------
-
-Check that the change you made is no longer visible, since it was
-made on the experimental branch and you're back on the master branch.
-
-You can make a different change on the master branch:
-
-------------------------------------------------
-(edit file)
-$ git commit -a
-------------------------------------------------
-
-at this point the two branches have diverged, with different changes
-made in each.  To merge the changes made in experimental into master, run
-
-------------------------------------------------
-$ git merge experimental
-------------------------------------------------
-
-If the changes don't conflict, you're done.  If there are conflicts,
-markers will be left in the problematic files showing the conflict;
-
-------------------------------------------------
-$ git diff
-------------------------------------------------
-
-will show this.  Once you've edited the files to resolve the
-conflicts,
-
-------------------------------------------------
-$ git commit -a
-------------------------------------------------
-
-will commit the result of the merge. Finally,
-
-------------------------------------------------
-$ gitk
-------------------------------------------------
-
-will show a nice graphical representation of the resulting history.
-
-At this point you could delete the experimental branch with
-
-------------------------------------------------
-$ git branch -d experimental
-------------------------------------------------
-
-This command ensures that the changes in the experimental branch are
-already in the current branch.
-
-If you develop on a branch crazy-idea, then regret it, you can always
-delete the branch with
-
--------------------------------------
-$ git branch -D crazy-idea
--------------------------------------
-
-Branches are cheap and easy, so this is a good way to try something
-out.
-
-Using git for collaboration
----------------------------
-
-Suppose that Alice has started a new project with a git repository in
-/home/alice/project, and that Bob, who has a home directory on the
-same machine, wants to contribute.
-
-Bob begins with:
-
-------------------------------------------------
-$ git clone /home/alice/project myrepo
-------------------------------------------------
-
-This creates a new directory "myrepo" containing a clone of Alice's
-repository.  The clone is on an equal footing with the original
-project, possessing its own copy of the original project's history.
-
-Bob then makes some changes and commits them:
-
-------------------------------------------------
-(edit files)
-$ git commit -a
-(repeat as necessary)
-------------------------------------------------
-
-When he's ready, he tells Alice to pull changes from the repository
-at /home/bob/myrepo.  She does this with:
-
-------------------------------------------------
-$ cd /home/alice/project
-$ git pull /home/bob/myrepo master
-------------------------------------------------
-
-This merges the changes from Bob's "master" branch into Alice's
-current branch.  If Alice has made her own changes in the meantime,
-then she may need to manually fix any conflicts.  (Note that the
-"master" argument in the above command is actually unnecessary, as it
-is the default.)
-
-The "pull" command thus performs two operations: it fetches changes
-from a remote branch, then merges them into the current branch.
-
-When you are working in a small closely knit group, it is not
-unusual to interact with the same repository over and over
-again.  By defining 'remote' repository shorthand, you can make
-it easier:
-
-------------------------------------------------
-$ git remote add bob /home/bob/myrepo
-------------------------------------------------
-
-With this, Alice can perform the first operation alone using the
-"git fetch" command without merging them with her own branch,
-using:
-
--------------------------------------
-$ git fetch bob
--------------------------------------
-
-Unlike the longhand form, when Alice fetches from Bob using a
-remote repository shorthand set up with `git remote`, what was
-fetched is stored in a remote tracking branch, in this case
-`bob/master`.  So after this:
-
--------------------------------------
-$ git log -p master..bob/master
--------------------------------------
-
-shows a list of all the changes that Bob made since he branched from
-Alice's master branch.
-
-After examining those changes, Alice
-could merge the changes into her master branch:
-
--------------------------------------
-$ git merge bob/master
--------------------------------------
-
-This `merge` can also be done by 'pulling from her own remote
-tracking branch', like this:
-
--------------------------------------
-$ git pull . remotes/bob/master
--------------------------------------
-
-Note that git pull always merges into the current branch,
-regardless of what else is given on the command line.
-
-Later, Bob can update his repo with Alice's latest changes using
-
--------------------------------------
-$ git pull
--------------------------------------
-
-Note that he doesn't need to give the path to Alice's repository;
-when Bob cloned Alice's repository, git stored the location of her
-repository in the repository configuration, and that location is
-used for pulls:
-
--------------------------------------
-$ git config --get remote.origin.url
-/home/alice/project
--------------------------------------
-
-(The complete configuration created by git-clone is visible using
-"git config -l", and the linkgit:git-config[1] man page
-explains the meaning of each option.)
-
-Git also keeps a pristine copy of Alice's master branch under the
-name "origin/master":
-
--------------------------------------
-$ git branch -r
-  origin/master
--------------------------------------
-
-If Bob later decides to work from a different host, he can still
-perform clones and pulls using the ssh protocol:
-
--------------------------------------
-$ git clone alice.org:/home/alice/project myrepo
--------------------------------------
-
-Alternatively, git has a native protocol, or can use rsync or http;
-see linkgit:git-pull[1] for details.
-
-Git can also be used in a CVS-like mode, with a central repository
-that various users push changes to; see linkgit:git-push[1] and
-link:cvs-migration.html[git for CVS users].
-
-Exploring history
------------------
-
-Git history is represented as a series of interrelated commits.  We
-have already seen that the git log command can list those commits.
-Note that first line of each git log entry also gives a name for the
-commit:
-
--------------------------------------
-$ git log
-commit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
-Author: Junio C Hamano <junkio@cox.net>
-Date:   Tue May 16 17:18:22 2006 -0700
-
-    merge-base: Clarify the comments on post processing.
--------------------------------------
-
-We can give this name to git show to see the details about this
-commit.
-
--------------------------------------
-$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
--------------------------------------
-
-But there are other ways to refer to commits.  You can use any initial
-part of the name that is long enough to uniquely identify the commit:
-
--------------------------------------
-$ git show c82a22c39c  # the first few characters of the name are
-                       # usually enough
-$ git show HEAD                # the tip of the current branch
-$ git show experimental        # the tip of the "experimental" branch
--------------------------------------
-
-Every commit usually has one "parent" commit
-which points to the previous state of the project:
-
--------------------------------------
-$ git show HEAD^  # to see the parent of HEAD
-$ git show HEAD^^ # to see the grandparent of HEAD
-$ git show HEAD~4 # to see the great-great grandparent of HEAD
--------------------------------------
-
-Note that merge commits may have more than one parent:
-
--------------------------------------
-$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)
-$ git show HEAD^2 # show the second parent of HEAD
--------------------------------------
-
-You can also give commits names of your own; after running
-
--------------------------------------
-$ git-tag v2.5 1b2e1d63ff
--------------------------------------
-
-you can refer to 1b2e1d63ff by the name "v2.5".  If you intend to
-share this name with other people (for example, to identify a release
-version), you should create a "tag" object, and perhaps sign it; see
-linkgit:git-tag[1] for details.
-
-Any git command that needs to know a commit can take any of these
-names.  For example:
-
--------------------------------------
-$ git diff v2.5 HEAD    # compare the current HEAD to v2.5
-$ git branch stable v2.5 # start a new branch named "stable" based
-                        # at v2.5
-$ git reset --hard HEAD^ # reset your current branch and working
-                        # directory to its state at HEAD^
--------------------------------------
-
-Be careful with that last command: in addition to losing any changes
-in the working directory, it will also remove all later commits from
-this branch.  If this branch is the only branch containing those
-commits, they will be lost.  Also, don't use "git reset" on a
-publicly-visible branch that other developers pull from, as it will
-force needless merges on other developers to clean up the history.
-If you need to undo changes that you have pushed, use linkgit:git-revert[1]
-instead.
-
-The git grep command can search for strings in any version of your
-project, so
-
--------------------------------------
-$ git grep "hello" v2.5
--------------------------------------
-
-searches for all occurrences of "hello" in v2.5.
-
-If you leave out the commit name, git grep will search any of the
-files it manages in your current directory.  So
-
--------------------------------------
-$ git grep "hello"
--------------------------------------
-
-is a quick way to search just the files that are tracked by git.
-
-Many git commands also take sets of commits, which can be specified
-in a number of ways.  Here are some examples with git log:
-
--------------------------------------
-$ git log v2.5..v2.6            # commits between v2.5 and v2.6
-$ git log v2.5..                # commits since v2.5
-$ git log --since="2 weeks ago" # commits from the last 2 weeks
-$ git log v2.5.. Makefile       # commits since v2.5 which modify
-                               # Makefile
--------------------------------------
-
-You can also give git log a "range" of commits where the first is not
-necessarily an ancestor of the second; for example, if the tips of
-the branches "stable-release" and "master" diverged from a common
-commit some time ago, then
-
--------------------------------------
-$ git log stable..experimental
--------------------------------------
-
-will list commits made in the experimental branch but not in the
-stable branch, while
-
--------------------------------------
-$ git log experimental..stable
--------------------------------------
-
-will show the list of commits made on the stable branch but not
-the experimental branch.
-
-The "git log" command has a weakness: it must present commits in a
-list.  When the history has lines of development that diverged and
-then merged back together, the order in which "git log" presents
-those commits is meaningless.
-
-Most projects with multiple contributors (such as the linux kernel,
-or git itself) have frequent merges, and gitk does a better job of
-visualizing their history.  For example,
-
--------------------------------------
-$ gitk --since="2 weeks ago" drivers/
--------------------------------------
-
-allows you to browse any commits from the last 2 weeks of commits
-that modified files under the "drivers" directory.  (Note: you can
-adjust gitk's fonts by holding down the control key while pressing
-"-" or "+".)
-
-Finally, most commands that take filenames will optionally allow you
-to precede any filename by a commit, to specify a particular version
-of the file:
-
--------------------------------------
-$ git diff v2.5:Makefile HEAD:Makefile.in
--------------------------------------
-
-You can also use "git show" to see any such file:
-
--------------------------------------
-$ git show v2.5:Makefile
--------------------------------------
-
-Next Steps
-----------
-
-This tutorial should be enough to perform basic distributed revision
-control for your projects.  However, to fully understand the depth
-and power of git you need to understand two simple ideas on which it
-is based:
-
-  * The object database is the rather elegant system used to
-    store the history of your project--files, directories, and
-    commits.
-
-  * The index file is a cache of the state of a directory tree,
-    used to create commits, check out working directories, and
-    hold the various trees involved in a merge.
-
-link:tutorial-2.html[Part two of this tutorial] explains the object
-database, the index file, and a few other odds and ends that you'll
-need to make the most of git.
-
-If you don't want to continue with that right away, a few other
-digressions that may be interesting at this point are:
-
-  * linkgit:git-format-patch[1], linkgit:git-am[1]: These convert
-    series of git commits into emailed patches, and vice versa,
-    useful for projects such as the linux kernel which rely heavily
-    on emailed patches.
-
-  * linkgit:git-bisect[1]: When there is a regression in your
-    project, one way to track down the bug is by searching through
-    the history to find the exact commit that's to blame.  Git bisect
-    can help you perform a binary search for that commit.  It is
-    smart enough to perform a close-to-optimal search even in the
-    case of complex non-linear history with lots of merged branches.
-
-  * link:everyday.html[Everyday GIT with 20 Commands Or So]
-
-  * link:cvs-migration.html[git for CVS users].
index e2db850150a683087491016159a0ff0a75eb879a..fd8cdb625afbfc8d8921a5e085abe32e05c2da9a 100644 (file)
@@ -1993,7 +1993,7 @@ the right to push to the same repository.  In that case, the correct
 solution is to retry the push after first updating your work by either a
 pull or a fetch followed by a rebase; see the
 <<setting-up-a-shared-repository,next section>> and
-link:cvs-migration.html[git for CVS users] for more.
+linkgit:gitcvs-migration[7][git for CVS users] for more.
 
 [[setting-up-a-shared-repository]]
 Setting up a shared repository
@@ -2002,7 +2002,7 @@ Setting up a shared repository
 Another way to collaborate is by using a model similar to that
 commonly used in CVS, where several developers with special rights
 all push to and pull from a single shared repository.  See
-link:cvs-migration.html[git for CVS users] for instructions on how to
+linkgit:gitcvs-migration[7][git for CVS users] for instructions on how to
 set this up.
 
 However, while there is nothing wrong with git's support for shared
index 649ee56c96f44c6b403907bf03826c2a3f577315..865e2bfcf1f3e640989efc5e80f1efb9403cc76e 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -346,6 +346,7 @@ LIB_H += diff.h
 LIB_H += dir.h
 LIB_H += fsck.h
 LIB_H += git-compat-util.h
+LIB_H += graph.h
 LIB_H += grep.h
 LIB_H += hash.h
 LIB_H += list-objects.h
@@ -411,6 +412,7 @@ LIB_OBJS += entry.o
 LIB_OBJS += environment.o
 LIB_OBJS += exec_cmd.o
 LIB_OBJS += fsck.o
+LIB_OBJS += graph.o
 LIB_OBJS += grep.o
 LIB_OBJS += hash.o
 LIB_OBJS += help.o
@@ -636,8 +638,12 @@ endif
 ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
        NO_MEMMEM = YesPlease
+       NO_MKDTEMP = YesPlease
        NO_STRLCPY = YesPlease
+       FREAD_READS_DIRECTORIES = UnfortunatelyYes
+       INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
+       BASIC_CFLAGS += -D_LARGE_FILES
 endif
 ifeq ($(uname_S),GNU)
        # GNU/Hurd
index 4a91e3eb118850882fbc22e8d8f37e8bbfaa7617..73235ed08a9d60f8134235d9b68632303f359ca9 100644 (file)
@@ -79,12 +79,18 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
                prune_directory(dir, pathspec, baselen);
 }
 
+struct update_callback_data
+{
+       int flags;
+       int add_errors;
+};
+
 static void update_callback(struct diff_queue_struct *q,
                            struct diff_options *opt, void *cbdata)
 {
-       int i, verbose;
+       int i;
+       struct update_callback_data *data = cbdata;
 
-       verbose = *((int *)cbdata);
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
                const char *path = p->one->path;
@@ -94,27 +100,35 @@ static void update_callback(struct diff_queue_struct *q,
                case DIFF_STATUS_UNMERGED:
                case DIFF_STATUS_MODIFIED:
                case DIFF_STATUS_TYPE_CHANGED:
-                       add_file_to_cache(path, verbose);
+                       if (add_file_to_cache(path, data->flags & ADD_FILES_VERBOSE)) {
+                               if (!(data->flags & ADD_FILES_IGNORE_ERRORS))
+                                       die("updating files failed");
+                               data->add_errors++;
+                       }
                        break;
                case DIFF_STATUS_DELETED:
                        remove_file_from_cache(path);
-                       if (verbose)
+                       if (data->flags & ADD_FILES_VERBOSE)
                                printf("remove '%s'\n", path);
                        break;
                }
        }
 }
 
-void add_files_to_cache(int verbose, const char *prefix, const char **pathspec)
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
 {
+       struct update_callback_data data;
        struct rev_info rev;
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
        rev.prune_data = pathspec;
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
-       rev.diffopt.format_callback_data = &verbose;
+       data.flags = flags;
+       data.add_errors = 0;
+       rev.diffopt.format_callback_data = &data;
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
+       return !!data.add_errors;
 }
 
 static void refresh(int verbose, const char **pathspec)
@@ -177,6 +191,7 @@ static const char ignore_error[] =
 "The following paths are ignored by one of your .gitignore files:\n";
 
 static int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
+static int ignore_add_errors;
 
 static struct option builtin_add_options[] = {
        OPT__DRY_RUN(&show_only),
@@ -187,11 +202,22 @@ static struct option builtin_add_options[] = {
        OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"),
        OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"),
        OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"),
+       OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"),
        OPT_END(),
 };
 
+static int add_config(const char *var, const char *value)
+{
+       if (!strcasecmp(var, "add.ignore-errors")) {
+               ignore_add_errors = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
+       int exit_status = 0;
        int i, newfd;
        const char **pathspec;
        struct dir_struct dir;
@@ -203,16 +229,23 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (add_interactive)
                exit(interactive_add(argc, argv, prefix));
 
-       git_config(git_default_config);
+       git_config(add_config);
 
        newfd = hold_locked_index(&lock_file, 1);
 
        if (take_worktree_changes) {
+               int flags = 0;
                const char **pathspec;
                if (read_cache() < 0)
                        die("index file corrupt");
                pathspec = get_pathspec(prefix, argv);
-               add_files_to_cache(verbose, prefix, pathspec);
+
+               if (verbose)
+                       flags |= ADD_FILES_VERBOSE;
+               if (ignore_add_errors)
+                       flags |= ADD_FILES_IGNORE_ERRORS;
+
+               exit_status = add_files_to_cache(prefix, pathspec, flags);
                goto finish;
        }
 
@@ -254,7 +287,11 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        }
 
        for (i = 0; i < dir.nr; i++)
-               add_file_to_cache(dir.entries[i]->name, verbose);
+               if (add_file_to_cache(dir.entries[i]->name, verbose)) {
+                       if (!ignore_add_errors)
+                               die("adding files failed");
+                       exit_status = 1;
+               }
 
  finish:
        if (active_cache_changed) {
@@ -263,5 +300,5 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        die("Unable to write new index file");
        }
 
-       return 0;
+       return exit_status;
 }
index 10ec137ccec7d7f281562817d81a1035c4aa6124..05c06421b6c3a6d75b598e3c58a40c8461febd75 100644 (file)
@@ -282,7 +282,7 @@ static int merge_working_tree(struct checkout_opts *opts,
                         * entries in the index.
                         */
 
-                       add_files_to_cache(0, NULL, NULL);
+                       add_files_to_cache(NULL, NULL, 0);
                        work = write_tree_from_memory();
 
                        ret = reset_to_new(new->commit->tree, opts->quiet);
index 0baec6db6ac76de81b93f4c990f8347814618be7..d75224381b7ad88d6bbb67db3a85bbb881cea941 100644 (file)
@@ -179,9 +179,10 @@ static void add_remove_files(struct path_list *list)
                struct stat st;
                struct path_list_item *p = &(list->items[i]);
 
-               if (!lstat(p->path, &st))
-                       add_to_cache(p->path, &st, 0);
-               else
+               if (!lstat(p->path, &st)) {
+                       if (add_to_cache(p->path, &st, 0))
+                               die("updating files failed");
+               } else
                        remove_file_from_cache(p->path);
        }
 }
@@ -246,7 +247,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
         */
        if (all || (also && pathspec && *pathspec)) {
                int fd = hold_locked_index(&index_lock, 1);
-               add_files_to_cache(0, also ? prefix : NULL, pathspec);
+               add_files_to_cache(also ? prefix : NULL, pathspec, 0);
                refresh_cache(REFRESH_QUIET);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
index f99ebc7926b95b04f1140d257da7a52bfc33ee50..48f7d95f9784d27f023b22343929f5ad188caa4c 100644 (file)
@@ -219,7 +219,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
        char buf[80];
 
        struct option builtin_gc_options[] = {
-               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects"),
+               OPT_BOOLEAN(0, "prune", &prune, "prune unreferenced objects (deprecated)"),
                OPT_BOOLEAN(0, "aggressive", &aggressive, "be more thorough (increased runtime)"),
                OPT_BOOLEAN(0, "auto", &auto_gc, "enable auto-gc mode"),
                OPT_BOOLEAN('q', "quiet", &quiet, "suppress progress reports"),
@@ -249,24 +249,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                /*
                 * Auto-gc should be least intrusive as possible.
                 */
-               prune = 0;
                if (!need_to_gc())
                        return 0;
                fprintf(stderr, "Auto packing your repository for optimum "
                        "performance. You may also\n"
                        "run \"git gc\" manually. See "
                        "\"git help gc\" for more information.\n");
-       } else {
-               /*
-                * Use safer (for shared repos) "-A" option to
-                * repack when not pruning. Auto-gc makes its
-                * own decision.
-                */
-               if (prune)
-                       append_option(argv_repack, "-a", MAX_ADD);
-               else
-                       append_option(argv_repack, "-A", MAX_ADD);
-       }
+       } else
+               append_option(argv_repack, "-A", MAX_ADD);
 
        if (pack_refs && run_command_v_opt(argv_pack_refs, RUN_GIT_CMD))
                return error(FAILED_RUN, argv_pack_refs[0]);
index a76f5d3474f9d1e16bfc988ad6fd1d971876fe91..b061317275792ccb33eea80a5071b72ed6f8d6a7 100644 (file)
@@ -255,8 +255,8 @@ static int create_default_files(const char *git_dir, const char *template_path)
                        git_config_set("core.worktree", work_tree);
        }
 
-       /* Check if symlink is supported in the work tree */
        if (!reinit) {
+               /* Check if symlink is supported in the work tree */
                path[len] = 0;
                strcpy(path + len, "tXXXXXX");
                if (!close(xmkstemp(path)) &&
@@ -267,6 +267,12 @@ static int create_default_files(const char *git_dir, const char *template_path)
                        unlink(path); /* good */
                else
                        git_config_set("core.symlinks", "false");
+
+               /* Check if the filesystem is case-insensitive */
+               path[len] = 0;
+               strcpy(path + len, "CoNfIg");
+               if (!access(path, F_OK))
+                       git_config_set("core.ignorecase", "true");
        }
 
        return reinit;
index 9d046b2e035bfa6ae63d6d004e5a92072a202d70..543855b7ad11bd0355d88bc1502030509875e0c7 100644 (file)
@@ -18,6 +18,9 @@
 #include "run-command.h"
 #include "shortlog.h"
 
+/* Set a default date-time format for git log ("log.date" config variable) */
+static const char *default_date_mode = NULL;
+
 static int default_show_root = 1;
 static const char *fmt_patch_subject_prefix = "PATCH";
 static const char *fmt_pretty;
@@ -61,7 +64,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
        rev->show_root_diff = default_show_root;
        rev->subject_prefix = fmt_patch_subject_prefix;
+
+       if (default_date_mode)
+               rev->date_mode = parse_date_format(default_date_mode);
+
        argc = setup_revisions(argc, argv, rev, "HEAD");
+
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
        if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
@@ -232,6 +240,8 @@ static int git_log_config(const char *var, const char *value)
                fmt_patch_subject_prefix = xstrdup(value);
                return 0;
        }
+       if (!strcmp(var, "log.date"))
+               return git_config_string(&default_date_mode, var, value);
        if (!strcmp(var, "log.showroot")) {
                default_show_root = git_config_bool(var, value);
                return 0;
index 94f6dd2aad2da512bab0be2e84859eced6553cc9..fb8ffb41aaba5f6e2a8cc13207a2f2c407d6c91d 100644 (file)
@@ -256,7 +256,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
 
                for (i = 0; i < added.nr; i++) {
                        const char *path = added.items[i].path;
-                       add_file_to_cache(path, verbose);
+                       if (add_file_to_cache(path, verbose))
+                               die("updating index entries failed");
                }
 
                for (i = 0; i < deleted.nr; i++)
index 777f272668c39931b330be555f41399adfbce397..f43eb67016ee719d34d2e2f4ae8d77e1ec74b396 100644 (file)
@@ -28,7 +28,8 @@ git-pack-objects [{ -q | --progress | --all-progress }] \n\
        [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
        [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
-       [--stdout | base-name] [--include-tag] [--keep-unreachable] \n\
+       [--stdout | base-name] [--include-tag] \n\
+       [--keep-unreachable | --unpack-unreachable] \n\
        [<ref-list | <object-list]";
 
 struct object_entry {
@@ -43,6 +44,7 @@ struct object_entry {
                                             */
        void *delta_data;       /* cached delta (uncompressed) */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+       unsigned long z_delta_size;     /* delta data size (compressed) */
        unsigned int hash;      /* name hint hash */
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
@@ -65,7 +67,8 @@ static struct pack_idx_entry **written_list;
 static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
-static int no_reuse_delta, no_reuse_object, keep_unreachable, include_tag;
+static int reuse_delta = 1, reuse_object = 1;
+static int keep_unreachable, unpack_unreachable, include_tag;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
@@ -102,24 +105,53 @@ static uint32_t written, written_delta;
 static uint32_t reused, reused_delta;
 
 
-static void *delta_against(void *buf, unsigned long size, struct object_entry *entry)
+static void *get_delta(struct object_entry *entry)
 {
-       unsigned long othersize, delta_size;
+       unsigned long size, base_size, delta_size;
+       void *buf, *base_buf, *delta_buf;
        enum object_type type;
-       void *otherbuf = read_sha1_file(entry->delta->idx.sha1, &type, &othersize);
-       void *delta_buf;
 
-       if (!otherbuf)
+       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+       if (!buf)
+               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+       base_buf = read_sha1_file(entry->delta->idx.sha1, &type, &base_size);
+       if (!base_buf)
                die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
-        delta_buf = diff_delta(otherbuf, othersize,
+       delta_buf = diff_delta(base_buf, base_size,
                               buf, size, &delta_size, 0);
-        if (!delta_buf || delta_size != entry->delta_size)
+       if (!delta_buf || delta_size != entry->delta_size)
                die("delta size changed");
-        free(buf);
-        free(otherbuf);
+       free(buf);
+       free(base_buf);
        return delta_buf;
 }
 
+static unsigned long do_compress(void **pptr, unsigned long size)
+{
+       z_stream stream;
+       void *in, *out;
+       unsigned long maxsize;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, pack_compression_level);
+       maxsize = deflateBound(&stream, size);
+
+       in = *pptr;
+       out = xmalloc(maxsize);
+       *pptr = out;
+
+       stream.next_in = in;
+       stream.avail_in = size;
+       stream.next_out = out;
+       stream.avail_out = maxsize;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       deflateEnd(&stream);
+
+       free(in);
+       return stream.total_out;
+}
+
 /*
  * The per-object header is a pretty dense thing, which is
  *  - first byte: low four bits are "size", then three bits of "type",
@@ -222,42 +254,42 @@ static unsigned long write_object(struct sha1file *f,
                                  struct object_entry *entry,
                                  off_t write_offset)
 {
-       unsigned long size;
-       enum object_type type;
+       unsigned long size, limit, datalen;
        void *buf;
-       unsigned char header[10];
-       unsigned char dheader[10];
+       unsigned char header[10], dheader[10];
        unsigned hdrlen;
-       off_t datalen;
-       enum object_type obj_type;
-       int to_reuse = 0;
-       /* write limit if limited packsize and not first object */
-       unsigned long limit = pack_size_limit && nr_written ?
-                               pack_size_limit - write_offset : 0;
-                               /* no if no delta */
-       int usable_delta =      !entry->delta ? 0 :
-                               /* yes if unlimited packfile */
-                               !pack_size_limit ? 1 :
-                               /* no if base written to previous pack */
-                               entry->delta->idx.offset == (off_t)-1 ? 0 :
-                               /* otherwise double-check written to this
-                                * pack,  like we do below
-                                */
-                               entry->delta->idx.offset ? 1 : 0;
+       enum object_type type;
+       int usable_delta, to_reuse;
 
        if (!pack_to_stdout)
                crc32_begin(f);
 
-       obj_type = entry->type;
-       if (no_reuse_object)
+       type = entry->type;
+
+       /* write limit if limited packsize and not first object */
+       limit = pack_size_limit && nr_written ?
+                       pack_size_limit - write_offset : 0;
+
+       if (!entry->delta)
+               usable_delta = 0;       /* no delta */
+       else if (!pack_size_limit)
+              usable_delta = 1;        /* unlimited packfile */
+       else if (entry->delta->idx.offset == (off_t)-1)
+               usable_delta = 0;       /* base was written to another pack */
+       else if (entry->delta->idx.offset)
+               usable_delta = 1;       /* base already exists in this pack */
+       else
+               usable_delta = 0;       /* base could end up in another pack */
+
+       if (!reuse_object)
                to_reuse = 0;   /* explicit */
        else if (!entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
-       else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
+       else if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA)
                                /* check_object() decided it for us ... */
                to_reuse = usable_delta;
                                /* ... but pack split may override that */
-       else if (obj_type != entry->in_pack_type)
+       else if (type != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
        else if (entry->delta)
                to_reuse = 0;   /* we want to pack afresh */
@@ -267,50 +299,42 @@ static unsigned long write_object(struct sha1file *f,
                                 */
 
        if (!to_reuse) {
-               z_stream stream;
-               unsigned long maxsize;
-               void *out;
                if (!usable_delta) {
-                       buf = read_sha1_file(entry->idx.sha1, &obj_type, &size);
+                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
                        if (!buf)
                                die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+                       /*
+                        * make sure no cached delta data remains from a
+                        * previous attempt before a pack split occured.
+                        */
+                       free(entry->delta_data);
+                       entry->delta_data = NULL;
+                       entry->z_delta_size = 0;
                } else if (entry->delta_data) {
                        size = entry->delta_size;
                        buf = entry->delta_data;
                        entry->delta_data = NULL;
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                } else {
-                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
-                       if (!buf)
-                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
-                       buf = delta_against(buf, size, entry);
+                       buf = get_delta(entry);
                        size = entry->delta_size;
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                }
-               /* compress the data to store and put compressed length in datalen */
-               memset(&stream, 0, sizeof(stream));
-               deflateInit(&stream, pack_compression_level);
-               maxsize = deflateBound(&stream, size);
-               out = xmalloc(maxsize);
-               /* Compress it */
-               stream.next_in = buf;
-               stream.avail_in = size;
-               stream.next_out = out;
-               stream.avail_out = maxsize;
-               while (deflate(&stream, Z_FINISH) == Z_OK)
-                       /* nothing */;
-               deflateEnd(&stream);
-               datalen = stream.total_out;
+
+               if (entry->z_delta_size)
+                       datalen = entry->z_delta_size;
+               else
+                       datalen = do_compress(&buf, size);
 
                /*
                 * The object header is a byte of 'type' followed by zero or
                 * more bytes of length.
                 */
-               hdrlen = encode_header(obj_type, size, header);
+               hdrlen = encode_header(type, size, header);
 
-               if (obj_type == OBJ_OFS_DELTA) {
+               if (type == OBJ_OFS_DELTA) {
                        /*
                         * Deltas with relative base contain an additional
                         * encoding of the relative offset for the delta
@@ -322,20 +346,18 @@ static unsigned long write_object(struct sha1file *f,
                        while (ofs >>= 7)
                                dheader[--pos] = 128 | (--ofs & 127);
                        if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
                        sha1write(f, header, hdrlen);
                        sha1write(f, dheader + pos, sizeof(dheader) - pos);
                        hdrlen += sizeof(dheader) - pos;
-               } else if (obj_type == OBJ_REF_DELTA) {
+               } else if (type == OBJ_REF_DELTA) {
                        /*
                         * Deltas with a base reference contain
                         * an additional 20 bytes for the base sha1.
                         */
                        if (limit && hdrlen + 20 + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
@@ -344,14 +366,12 @@ static unsigned long write_object(struct sha1file *f,
                        hdrlen += 20;
                } else {
                        if (limit && hdrlen + datalen + 20 >= limit) {
-                               free(out);
                                free(buf);
                                return 0;
                        }
                        sha1write(f, header, hdrlen);
                }
-               sha1write(f, out, datalen);
-               free(out);
+               sha1write(f, buf, datalen);
                free(buf);
        }
        else {
@@ -361,11 +381,11 @@ static unsigned long write_object(struct sha1file *f,
                off_t offset;
 
                if (entry->delta) {
-                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                       type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                        reused_delta++;
                }
-               hdrlen = encode_header(obj_type, entry->size, header);
+               hdrlen = encode_header(type, entry->size, header);
                offset = entry->in_pack_offset;
                revidx = find_pack_revindex(p, offset);
                datalen = revidx[1].offset - offset;
@@ -374,7 +394,7 @@ static unsigned long write_object(struct sha1file *f,
                        die("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
                offset += entry->in_pack_header_size;
                datalen -= entry->in_pack_header_size;
-               if (obj_type == OBJ_OFS_DELTA) {
+               if (type == OBJ_OFS_DELTA) {
                        off_t ofs = entry->idx.offset - entry->delta->idx.offset;
                        unsigned pos = sizeof(dheader) - 1;
                        dheader[pos] = ofs & 127;
@@ -385,7 +405,7 @@ static unsigned long write_object(struct sha1file *f,
                        sha1write(f, header, hdrlen);
                        sha1write(f, dheader + pos, sizeof(dheader) - pos);
                        hdrlen += sizeof(dheader) - pos;
-               } else if (obj_type == OBJ_REF_DELTA) {
+               } else if (type == OBJ_REF_DELTA) {
                        if (limit && hdrlen + 20 + datalen + 20 >= limit)
                                return 0;
                        sha1write(f, header, hdrlen);
@@ -452,11 +472,10 @@ static void write_pack_file(void)
        struct sha1file *f;
        off_t offset, offset_one, last_obj_offset = 0;
        struct pack_header hdr;
-       int do_progress = progress >> pack_to_stdout;
        uint32_t nr_remaining = nr_result;
        time_t last_mtime = 0;
 
-       if (do_progress)
+       if (progress > pack_to_stdout)
                progress_state = start_progress("Writing objects", nr_result);
        written_list = xmalloc(nr_objects * sizeof(*written_list));
 
@@ -1022,7 +1041,7 @@ static void check_object(struct object_entry *entry)
                        unuse_pack(&w_curs);
                        return;
                case OBJ_REF_DELTA:
-                       if (!no_reuse_delta && !entry->preferred_base)
+                       if (reuse_delta && !entry->preferred_base)
                                base_ref = use_pack(p, &w_curs,
                                                entry->in_pack_offset + used, NULL);
                        entry->in_pack_header_size = used + 20;
@@ -1045,7 +1064,7 @@ static void check_object(struct object_entry *entry)
                                die("delta base offset out of bound for %s",
                                    sha1_to_hex(entry->idx.sha1));
                        ofs = entry->in_pack_offset - ofs;
-                       if (!no_reuse_delta && !entry->preferred_base) {
+                       if (reuse_delta && !entry->preferred_base) {
                                struct revindex_entry *revidx;
                                revidx = find_pack_revindex(p, ofs);
                                base_ref = nth_packed_object_sha1(p, revidx->nr);
@@ -1233,7 +1252,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * We do not bother to try a delta that we discarded
         * on an earlier try, but only when reusing delta data.
         */
-       if (!no_reuse_delta && trg_entry->in_pack &&
+       if (reuse_delta && trg_entry->in_pack &&
            trg_entry->in_pack == src_entry->in_pack &&
            trg_entry->in_pack_type != OBJ_REF_DELTA &&
            trg_entry->in_pack_type != OBJ_OFS_DELTA)
@@ -1441,11 +1460,34 @@ static void find_deltas(struct object_entry **list, unsigned *list_size,
                                best_base = other_idx;
                }
 
+               /*
+                * If we decided to cache the delta data, then it is best
+                * to compress it right away.  First because we have to do
+                * it anyway, and doing it here while we're threaded will
+                * save a lot of time in the non threaded write phase,
+                * as well as allow for caching more deltas within
+                * the same cache size limit.
+                * ...
+                * But only if not writing to stdout, since in that case
+                * the network is most likely throttling writes anyway,
+                * and therefore it is best to go to the write phase ASAP
+                * instead, as we can afford spending more time compressing
+                * between writes at that moment.
+                */
+               if (entry->delta_data && !pack_to_stdout) {
+                       entry->z_delta_size = do_compress(&entry->delta_data,
+                                                         entry->delta_size);
+                       cache_lock();
+                       delta_cache_size -= entry->delta_size;
+                       delta_cache_size += entry->z_delta_size;
+                       cache_unlock();
+               }
+
                /* if we made n a delta, and if n is already at max
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
                 */
-               if (entry->delta && depth <= n->depth)
+               if (entry->delta && max_depth <= n->depth)
                        continue;
 
                /*
@@ -1688,7 +1730,7 @@ static void prepare_pack(int window, int depth)
 
                if (entry->delta)
                        /* This happens if we decided to reuse existing
-                        * delta from a pack.  "!no_reuse_delta &&" is implied.
+                        * delta from a pack.  "reuse_delta &&" is implied.
                         */
                        continue;
 
@@ -1905,6 +1947,32 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs)
        free(in_pack.array);
 }
 
+static void loosen_unused_packed_objects(struct rev_info *revs)
+{
+       struct packed_git *p;
+       uint32_t i;
+       const unsigned char *sha1;
+
+       for (p = packed_git; p; p = p->next) {
+               for (i = 0; i < revs->num_ignore_packed; i++) {
+                       if (matches_pack_name(p, revs->ignore_packed[i]))
+                               break;
+               }
+               if (revs->num_ignore_packed <= i)
+                       continue;
+
+               if (open_pack_index(p))
+                       die("cannot open pack index");
+
+               for (i = 0; i < p->num_objects; i++) {
+                       sha1 = nth_packed_object_sha1(p, i);
+                       if (!locate_object_entry(sha1))
+                               if (force_object_loose(sha1, p->mtime))
+                                       die("unable to force loose object");
+               }
+       }
+}
+
 static void get_object_list(int ac, const char **av)
 {
        struct rev_info revs;
@@ -1939,6 +2007,8 @@ static void get_object_list(int ac, const char **av)
 
        if (keep_unreachable)
                add_objects_in_unpacked_packs(&revs);
+       if (unpack_unreachable)
+               loosen_unused_packed_objects(&revs);
 }
 
 static int adjust_perm(const char *path, mode_t mode)
@@ -2050,11 +2120,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp("--no-reuse-delta", arg)) {
-                       no_reuse_delta = 1;
+                       reuse_delta = 0;
                        continue;
                }
                if (!strcmp("--no-reuse-object", arg)) {
-                       no_reuse_object = no_reuse_delta = 1;
+                       reuse_object = reuse_delta = 0;
                        continue;
                }
                if (!strcmp("--delta-base-offset", arg)) {
@@ -2073,6 +2143,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        keep_unreachable = 1;
                        continue;
                }
+               if (!strcmp("--unpack-unreachable", arg)) {
+                       unpack_unreachable = 1;
+                       continue;
+               }
                if (!strcmp("--include-tag", arg)) {
                        include_tag = 1;
                        continue;
@@ -2138,6 +2212,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
 
+       if (keep_unreachable && unpack_unreachable)
+               die("--keep-unreachable and --unpack-unreachable are incompatible.");
+
 #ifdef THREADED_DELTA_SEARCH
        if (!delta_search_threads)      /* --threads=0 means autodetect */
                delta_search_threads = online_cpus();
index edc0bd35bb0e96a230284250dc4c40776cf7caac..54d55cc3a33e55d5c3021eae14b7a771b4e1c23f 100644 (file)
@@ -10,6 +10,7 @@
 #include "list-objects.h"
 #include "builtin.h"
 #include "log-tree.h"
+#include "graph.h"
 
 /* bits #0-15 in revision.h */
 
@@ -58,6 +59,8 @@ static const char *header_prefix;
 static void finish_commit(struct commit *commit);
 static void show_commit(struct commit *commit)
 {
+       graph_show_commit(revs.graph);
+
        if (show_timestamp)
                printf("%lu ", commit->date);
        if (header_prefix)
@@ -77,7 +80,7 @@ static void show_commit(struct commit *commit)
                      stdout);
        else
                fputs(sha1_to_hex(commit->object.sha1), stdout);
-       if (revs.parents) {
+       if (revs.print_parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
                        printf(" %s", sha1_to_hex(parents->item->object.sha1));
@@ -96,9 +99,48 @@ static void show_commit(struct commit *commit)
                pretty_print_commit(revs.commit_format, commit,
                                    &buf, revs.abbrev, NULL, NULL,
                                    revs.date_mode, 0);
-               if (buf.len)
-                       printf("%s%c", buf.buf, hdr_termination);
+               if (revs.graph) {
+                       if (buf.len) {
+                               if (revs.commit_format != CMIT_FMT_ONELINE)
+                                       graph_show_oneline(revs.graph);
+
+                               graph_show_commit_msg(revs.graph, &buf);
+
+                               /*
+                                * Add a newline after the commit message.
+                                *
+                                * Usually, this newline produces a blank
+                                * padding line between entries, in which case
+                                * we need to add graph padding on this line.
+                                *
+                                * However, the commit message may not end in a
+                                * newline.  In this case the newline simply
+                                * ends the last line of the commit message,
+                                * and we don't need any graph output.  (This
+                                * always happens with CMIT_FMT_ONELINE, and it
+                                * happens with CMIT_FMT_USERFORMAT when the
+                                * format doesn't explicitly end in a newline.)
+                                */
+                               if (buf.len && buf.buf[buf.len - 1] == '\n')
+                                       graph_show_padding(revs.graph);
+                               putchar('\n');
+                       } else {
+                               /*
+                                * If the message buffer is empty, just show
+                                * the rest of the graph output for this
+                                * commit.
+                                */
+                               if (graph_show_remainder(revs.graph))
+                                       putchar('\n');
+                       }
+               } else {
+                       if (buf.len)
+                               printf("%s%c", buf.buf, hdr_termination);
+               }
                strbuf_release(&buf);
+       } else {
+               if (graph_show_remainder(revs.graph))
+                       putchar('\n');
        }
        maybe_flush_or_die(stdout, "stdout");
        finish_commit(commit);
index f8d8548e9cddd7d2d123fcf0fdee513754df86c1..ab3e85054e174d2d328c625f1db744854b62f5f7 100644 (file)
@@ -94,6 +94,14 @@ static void show(const char *arg)
                puts(arg);
 }
 
+/* Like show(), but with a negation prefix according to type */
+static void show_with_type(int type, const char *arg)
+{
+       if (type != show_type)
+               putchar('^');
+       show(arg);
+}
+
 /* Output a revision, only if filter allows it */
 static void show_rev(int type, const unsigned char *sha1, const char *name)
 {
@@ -101,8 +109,6 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                return;
        def = NULL;
 
-       if (type != show_type)
-               putchar('^');
        if (symbolic && name) {
                if (symbolic == SHOW_SYMBOLIC_FULL) {
                        unsigned char discard[20];
@@ -119,20 +125,20 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                                 */
                                break;
                        case 1: /* happy */
-                               show(full);
+                               show_with_type(type, full);
                                break;
                        default: /* ambiguous */
                                error("refname '%s' is ambiguous", name);
                                break;
                        }
                } else {
-                       show(name);
+                       show_with_type(type, name);
                }
        }
        else if (abbrev)
-               show(find_unique_abbrev(sha1, abbrev));
+               show_with_type(type, find_unique_abbrev(sha1, abbrev));
        else
-               show(sha1_to_hex(sha1));
+               show_with_type(type, sha1_to_hex(sha1));
 }
 
 /* Output a flag, only if filter allows it. */
index bb9c33a6506290edad096acaa52330dd48df6135..d76260c09eef064d8a2862b5d6f0e826d968d643 100644 (file)
@@ -537,9 +537,17 @@ static void verify_remote_names(int nr_heads, const char **heads)
        int i;
 
        for (i = 0; i < nr_heads; i++) {
+               const char *local = heads[i];
                const char *remote = strrchr(heads[i], ':');
 
-               remote = remote ? (remote + 1) : heads[i];
+               if (*local == '+')
+                       local++;
+
+               /* A matching refspec is okay.  */
+               if (remote == local && remote[1] == '\0')
+                       continue;
+
+               remote = remote ? (remote + 1) : local;
                switch (check_ref_format(remote)) {
                case 0: /* ok */
                case CHECK_REF_FORMAT_ONELEVEL:
diff --git a/cache.h b/cache.h
index 093f04cec0459e67c54f3fc5b536f59fb0f343dc..8db19cc253dda9b63401f7b55ffbc0d333e216a8 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -521,6 +521,7 @@ extern void * read_sha1_file(const unsigned char *sha1, enum object_type *type,
 extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
 extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
+extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
@@ -782,7 +783,13 @@ extern int convert_to_git(const char *path, const char *src, size_t len,
 extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
 
 /* add */
-void add_files_to_cache(int verbose, const char *prefix, const char **pathspec);
+#define ADD_FILES_VERBOSE      01
+#define ADD_FILES_IGNORE_ERRORS        02
+/*
+ * return 0 if success, 1 - if addition of a file failed and
+ * ADD_FILES_IGNORE_ERRORS was specified in flags
+ */
+int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
 
 /* diff.c */
 extern int diff_auto_refresh_index;
index 73e5439d97481a0e4dcaa17d025b27ec3fdcf41b..caea684338031431dc3177716654a1a289faa9a3 100644 (file)
@@ -1690,7 +1690,7 @@ static void skip_optional_lf(void)
                ungetc(term_char, stdin);
 }
 
-static void cmd_mark(void)
+static void parse_mark(void)
 {
        if (!prefixcmp(command_buf.buf, "mark :")) {
                next_mark = strtoumax(command_buf.buf + 6, NULL, 10);
@@ -1700,7 +1700,7 @@ static void cmd_mark(void)
                next_mark = 0;
 }
 
-static void cmd_data(struct strbuf *sb)
+static void parse_data(struct strbuf *sb)
 {
        strbuf_reset(sb);
 
@@ -1798,13 +1798,13 @@ static char *parse_ident(const char *buf)
        return ident;
 }
 
-static void cmd_new_blob(void)
+static void parse_new_blob(void)
 {
        static struct strbuf buf = STRBUF_INIT;
 
        read_next_command();
-       cmd_mark();
-       cmd_data(&buf);
+       parse_mark();
+       parse_data(&buf);
        store_object(OBJ_BLOB, &buf, &last_blob, NULL, next_mark);
 }
 
@@ -1908,7 +1908,7 @@ static void file_change_m(struct branch *b)
                        p = uq.buf;
                }
                read_next_command();
-               cmd_data(&buf);
+               parse_data(&buf);
                store_object(OBJ_BLOB, &buf, &last_blob, sha1, 0);
        } else if (oe) {
                if (oe->type != OBJ_BLOB)
@@ -1995,7 +1995,7 @@ static void file_change_deleteall(struct branch *b)
        load_tree(&b->branch_tree);
 }
 
-static void cmd_from_commit(struct branch *b, char *buf, unsigned long size)
+static void parse_from_commit(struct branch *b, char *buf, unsigned long size)
 {
        if (!buf || size < 46)
                die("Not a valid commit: %s", sha1_to_hex(b->sha1));
@@ -2006,7 +2006,7 @@ static void cmd_from_commit(struct branch *b, char *buf, unsigned long size)
                b->branch_tree.versions[1].sha1);
 }
 
-static void cmd_from_existing(struct branch *b)
+static void parse_from_existing(struct branch *b)
 {
        if (is_null_sha1(b->sha1)) {
                hashclr(b->branch_tree.versions[0].sha1);
@@ -2017,12 +2017,12 @@ static void cmd_from_existing(struct branch *b)
 
                buf = read_object_with_reference(b->sha1,
                        commit_type, &size, b->sha1);
-               cmd_from_commit(b, buf, size);
+               parse_from_commit(b, buf, size);
                free(buf);
        }
 }
 
-static int cmd_from(struct branch *b)
+static int parse_from(struct branch *b)
 {
        const char *from;
        struct branch *s;
@@ -2053,12 +2053,12 @@ static int cmd_from(struct branch *b)
                if (oe->pack_id != MAX_PACK_ID) {
                        unsigned long size;
                        char *buf = gfi_unpack_entry(oe, &size);
-                       cmd_from_commit(b, buf, size);
+                       parse_from_commit(b, buf, size);
                        free(buf);
                } else
-                       cmd_from_existing(b);
+                       parse_from_existing(b);
        } else if (!get_sha1(from, b->sha1))
-               cmd_from_existing(b);
+               parse_from_existing(b);
        else
                die("Invalid ref name or SHA1 expression: %s", from);
 
@@ -2066,7 +2066,7 @@ static int cmd_from(struct branch *b)
        return 1;
 }
 
-static struct hash_list *cmd_merge(unsigned int *count)
+static struct hash_list *parse_merge(unsigned int *count)
 {
        struct hash_list *list = NULL, *n, *e = e;
        const char *from;
@@ -2107,7 +2107,7 @@ static struct hash_list *cmd_merge(unsigned int *count)
        return list;
 }
 
-static void cmd_new_commit(void)
+static void parse_new_commit(void)
 {
        static struct strbuf msg = STRBUF_INIT;
        struct branch *b;
@@ -2124,7 +2124,7 @@ static void cmd_new_commit(void)
                b = new_branch(sp);
 
        read_next_command();
-       cmd_mark();
+       parse_mark();
        if (!prefixcmp(command_buf.buf, "author ")) {
                author = parse_ident(command_buf.buf + 7);
                read_next_command();
@@ -2135,10 +2135,10 @@ static void cmd_new_commit(void)
        }
        if (!committer)
                die("Expected committer but didn't get one");
-       cmd_data(&msg);
+       parse_data(&msg);
        read_next_command();
-       cmd_from(b);
-       merge_list = cmd_merge(&merge_count);
+       parse_from(b);
+       merge_list = parse_merge(&merge_count);
 
        /* ensure the branch is active/loaded */
        if (!b->branch_tree.tree || !max_active_branches) {
@@ -2196,7 +2196,7 @@ static void cmd_new_commit(void)
        b->last_commit = object_count_by_type[OBJ_COMMIT];
 }
 
-static void cmd_new_tag(void)
+static void parse_new_tag(void)
 {
        static struct strbuf msg = STRBUF_INIT;
        char *sp;
@@ -2253,7 +2253,7 @@ static void cmd_new_tag(void)
 
        /* tag payload/message */
        read_next_command();
-       cmd_data(&msg);
+       parse_data(&msg);
 
        /* build the tag object */
        strbuf_reset(&new_data);
@@ -2273,7 +2273,7 @@ static void cmd_new_tag(void)
                t->pack_id = pack_id;
 }
 
-static void cmd_reset_branch(void)
+static void parse_reset_branch(void)
 {
        struct branch *b;
        char *sp;
@@ -2293,12 +2293,12 @@ static void cmd_reset_branch(void)
        else
                b = new_branch(sp);
        read_next_command();
-       cmd_from(b);
+       parse_from(b);
        if (command_buf.len > 0)
                unread_command_buf = 1;
 }
 
-static void cmd_checkpoint(void)
+static void parse_checkpoint(void)
 {
        if (object_count) {
                cycle_packfile();
@@ -2309,7 +2309,7 @@ static void cmd_checkpoint(void)
        skip_optional_lf();
 }
 
-static void cmd_progress(void)
+static void parse_progress(void)
 {
        fwrite(command_buf.buf, 1, command_buf.len, stdout);
        fputc('\n', stdout);
@@ -2449,17 +2449,17 @@ int main(int argc, const char **argv)
        set_die_routine(die_nicely);
        while (read_next_command() != EOF) {
                if (!strcmp("blob", command_buf.buf))
-                       cmd_new_blob();
+                       parse_new_blob();
                else if (!prefixcmp(command_buf.buf, "commit "))
-                       cmd_new_commit();
+                       parse_new_commit();
                else if (!prefixcmp(command_buf.buf, "tag "))
-                       cmd_new_tag();
+                       parse_new_tag();
                else if (!prefixcmp(command_buf.buf, "reset "))
-                       cmd_reset_branch();
+                       parse_reset_branch();
                else if (!strcmp("checkpoint", command_buf.buf))
-                       cmd_checkpoint();
+                       parse_checkpoint();
                else if (!prefixcmp(command_buf.buf, "progress "))
-                       cmd_progress();
+                       parse_progress();
                else
                        die("Unsupported command: %s", command_buf.buf);
        }
index 75886a8f2fb0c2244ba921398ae2339dc9ffea26..b48096ec2037e3f14d4bdf89a0afe4623aff090e 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -11,7 +11,7 @@ git-am [options] --skip
 --
 d,dotest=       (removed -- do not use)
 i,interactive   run interactively
-b,binary        pass --allo-binary-replacement to git-apply
+b,binary        pass --allow-binary-replacement to git-apply
 3,3way          allow fall back on 3way merging if needed
 s,signoff       add a Signed-off-by line to the commit message
 u,utf8          recode into utf8 (default)
index b6036bd4d305215b4a70b6fd0fe54d7607dbe068..c93bd9c9b553f18b29e72a08176588d889e7acde 100755 (executable)
@@ -6,6 +6,7 @@ use File::Temp qw(tempdir);
 use Data::Dumper;
 use File::Basename qw(basename dirname);
 use File::Spec;
+use Git;
 
 our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u, $opt_w);
 
@@ -15,6 +16,10 @@ $opt_h && usage();
 
 die "Need at least one commit identifier!" unless @ARGV;
 
+# Get git-config settings
+my $repo = Git->repository();
+$opt_w = $repo->config('cvsexportcommit.cvsdir') unless defined $opt_w;
+
 if ($opt_w) {
        # Remember where GIT_DIR is before changing to CVS checkout
        unless ($ENV{GIT_DIR}) {
index bdac5d51b6dfb721bb648e455cb8f3c5078217e4..5a0255052c9360fdc4cbbdd79008a206ab3e4623 100755 (executable)
@@ -780,6 +780,7 @@ sub commit {
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
                $xtag =~ tr/_/\./ if ( $opt_u );
                $xtag =~ s/[\/]/$opt_s/g;
+               $xtag =~ s/\[//g;
 
                system('git-tag', '-f', $xtag, $cid) == 0
                        or die "Cannot create tag $xtag: $!\n";
index 7c25bb98089ec4c0a57993020a30d2d1afad8dae..9df49710e1b378af1f49c5ce3c8c8c48731d7d38 100755 (executable)
@@ -601,6 +601,7 @@ proc apply_config {} {
        }
 }
 
+set default_config(branch.autosetupmerge) true
 set default_config(merge.diffstat) true
 set default_config(merge.summary) false
 set default_config(merge.verbosity) 2
index 53dfb4ce6bb053349fbed39af8ffaf5a143b6567..3817771b944cc363205b86c91f7b4801c1d568f9 100644 (file)
@@ -183,6 +183,9 @@ method _create {} {
        if {$spec ne {} && $opt_fetch} {
                $co enable_fetch $spec
        }
+       if {$spec ne {}} {
+               $co remote_source $spec
+       }
 
        if {[$co run]} {
                destroy $w
index 86c4f73370a76ffa0196be0c58f11092b101cf0b..ef1930b4911591566be4561b8c17c24e1cfbfaad 100644 (file)
@@ -127,7 +127,7 @@ method _delete {} {
        foreach i $to_delete {
                set b [lindex $i 0]
                set o [lindex $i 1]
-               if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
+               if {[catch {git branch -D $b} err]} {
                        append failed " - $b: $err\n"
                }
        }
index 6e1411711bd46df08c09fd2bc371b017050a8368..caca88831b7066c573917542d24a52e832dcff34 100644 (file)
@@ -16,6 +16,7 @@ field merge_base   {}; # merge base if we have another ref involved
 field fetch_spec   {}; # refetch tracking branch if used?
 field checkout      1; # actually checkout the branch?
 field create        0; # create the branch if it doesn't exist?
+field remote_source {}; # same as fetch_spec, to setup tracking
 
 field reset_ok      0; # did the user agree to reset?
 field fetch_ok      0; # did the fetch succeed?
@@ -44,6 +45,10 @@ method enable_fetch {spec} {
        set fetch_spec $spec
 }
 
+method remote_source {spec} {
+       set remote_source $spec
+}
+
 method enable_checkout {co} {
        set checkout $co
 }
@@ -145,7 +150,7 @@ method _finish_fetch {ok} {
 }
 
 method _update_ref {} {
-       global null_sha1 current_branch
+       global null_sha1 current_branch repo_config
 
        set ref $new_ref
        set new $new_hash
@@ -172,6 +177,23 @@ method _update_ref {} {
 
                set reflog_msg "branch: Created from $new_expr"
                set cur $null_sha1
+
+               if {($repo_config(branch.autosetupmerge) eq {true}
+                       || $repo_config(branch.autosetupmerge) eq {always})
+                       && $remote_source ne {}
+                       && "refs/heads/$newbranch" eq $ref} {
+
+                       set c_remote [lindex $remote_source 1]
+                       set c_merge [lindex $remote_source 2]
+                       if {[catch {
+                                       git config branch.$newbranch.remote $c_remote
+                                       git config branch.$newbranch.merge  $c_merge
+                               } err]} {
+                               _error $this [strcat \
+                               [mc "Failed to configure simplified git-pull for '%s'." $newbranch] \
+                               "\n\n$err"]
+                       }
+               }
        } elseif {$create && $merge_type eq {none}} {
                # We were told to create it, but not do a merge.
                # Bad.  Name shouldn't have existed.
index d66aa3fe3367e0a8db0ee5c90925352a3a143198..a18ac8b4308d8263a0688058524282b72bafe77a 100644 (file)
@@ -102,8 +102,8 @@ proc hint_gc {} {
                *]]
 
        if {$objects_current >= $object_limit} {
-               set objects_current [expr {$objects_current * 256}]
-               set object_limit    [expr {$object_limit    * 256}]
+               set objects_current [expr {$objects_current * 250}]
+               set object_limit    [expr {$object_limit    * 250}]
                if {[ask_popup \
                        [mc "This repository currently has approximately %i loose objects.
 
index 9be748683ce101821a91bf9461ccadfd58c8adf1..78f344f08f34842c134b6b0e22b6eb8c49b1dbbd 100644 (file)
@@ -84,13 +84,19 @@ method _connect {pipe_fd} {
        regexp \
                {International Ispell Version .* \(but really (Aspell .*?)\)$} \
                $s_version _junk s_version
+       regexp {^Aspell (\d)+\.(\d+)} $s_version _junk major minor
 
        puts $pipe_fd !             ; # enable terse mode
-       puts $pipe_fd {$$cr master} ; # fetch the language
-       flush $pipe_fd
 
-       gets $pipe_fd s_lang
-       regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang
+       # fetch the language
+       if {$major > 0 || ($major == 0 && $minor >= 60)} {
+               puts $pipe_fd {$$cr master}
+               flush $pipe_fd
+               gets $pipe_fd s_lang
+               regexp {[/\\]([^/\\]+)\.[^\.]+$} $s_lang _ s_lang
+       } else {
+               set s_lang {}
+       }
 
        if {$::default_config(gui.spellingdictionary) eq {}
         && [get_config gui.spellingdictionary] eq {}} {
index 022b816ae1bf5591467218a310ed2a42aa634809..f20955c18520bf2b0cc8826da9e1dc93a5624ac3 100644 (file)
@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
 "POT-Creation-Date: 2008-03-14 07:18+0100\n"
-"PO-Revision-Date: 2008-02-16 21:52+0100\n"
+"PO-Revision-Date: 2008-05-01 11:51+0200\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
@@ -1754,9 +1754,8 @@ msgid "Number of Diff Context Lines"
 msgstr "Anzahl der Kontextzeilen beim Vergleich"
 
 #: lib/option.tcl:127
-#, fuzzy
 msgid "Commit Message Text Width"
-msgstr "Versionsbeschreibung:"
+msgstr "Textbreite der Versionsbeschreibung"
 
 #: lib/option.tcl:128
 msgid "New Branch Name Template"
@@ -1895,40 +1894,36 @@ msgstr "Fehler beim Erstellen des Icons:"
 
 #: lib/spellcheck.tcl:57
 msgid "Unsupported spell checker"
-msgstr ""
+msgstr "Rechtschreibprüfungsprogramm nicht unterstützt"
 
 #: lib/spellcheck.tcl:65
-#, fuzzy
 msgid "Spell checking is unavailable"
-msgstr "Rechtschreibprüfung fehlgeschlagen"
+msgstr "Rechtschreibprüfung nicht verfügbar"
 
 #: lib/spellcheck.tcl:68
 msgid "Invalid spell checking configuration"
-msgstr ""
+msgstr "Unbenutzbare Konfiguration der Rechtschreibprüfung"
 
 #: lib/spellcheck.tcl:70
 #, tcl-format
 msgid "Reverting dictionary to %s."
-msgstr ""
+msgstr "Wörterbuch auf %s zurückgesetzt."
 
 #: lib/spellcheck.tcl:73
-#, fuzzy
 msgid "Spell checker silently failed on startup"
-msgstr "Rechtschreibprüfung fehlgeschlagen"
+msgstr "Rechtschreibprüfungsprogramm mit Fehler abgebrochen"
 
 #: lib/spellcheck.tcl:80
-#, fuzzy
 msgid "Unrecognized spell checker"
-msgstr "Unbekannte Version von »aspell«"
+msgstr "Unbekanntes Rechtschreibprüfungsprogramm"
 
 #: lib/spellcheck.tcl:180
 msgid "No Suggestions"
 msgstr "Keine Vorschläge"
 
 #: lib/spellcheck.tcl:381
-#, fuzzy
 msgid "Unexpected EOF from spell checker"
-msgstr "Unerwartetes EOF von »aspell«"
+msgstr "Unerwartetes EOF vom Rechtschreibprüfungsprogramm"
 
 #: lib/spellcheck.tcl:385
 msgid "Spell Checker Failed"
@@ -2002,6 +1997,3 @@ msgstr "Kompaktes Datenformat benutzen (für langsame Netzverbindungen)"
 #: lib/transport.tcl:168
 msgid "Include tags"
 msgstr "Mit Markierungen übertragen"
-
-#~ msgid "Not connected to aspell"
-#~ msgstr "Keine Verbindung zu »aspell«"
index 69b35d87e62b628d45f8e934f163b7ef64e2dda6..5fc5f5201f8ea8e155a25795b93bfea86fc12d12 100755 (executable)
@@ -9,11 +9,9 @@ git-merge [options] <remote>...
 git-merge [options] <msg> HEAD <remote>
 --
 stat                 show a diffstat at the end of the merge
-n,no-stat            don't show a diffstat at the end of the merge
+n                    don't show a diffstat at the end of the merge
 summary              (synonym to --stat)
-no-summary           (synonym to --no-stat)
 log                  add list of one-line log to merge commit message
-no-log               don't add list of one-line log to merge commit message
 squash               create a single commit instead of doing a merge
 commit               perform a commit if the merge sucesses (default)
 ff                   allow fast forward (default)
index bf0c2985af875cdb7b2c64998390dbee908ff14c..809e537a4d81233966aad23df11c13931d6250c1 100755 (executable)
@@ -107,6 +107,11 @@ error_on_no_merge_candidates () {
 }
 
 test true = "$rebase" && {
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD -- ||
+       die "refusing to pull with rebase: your working tree is not up-to-date"
+
        . git-parse-remote &&
        origin="$1"
        test -z "$origin" && origin=$(get_default_remote)
index 501519ab6897c2463c054e3f7310efc6721c432f..10f735cff561c6606f5c2bd3f9feec1d6f6ae52e 100755 (executable)
@@ -8,7 +8,7 @@ OPTIONS_SPEC="\
 git-repack [options]
 --
 a               pack everything in a single pack
-A               same as -a, and keep unreachable objects too
+A               same as -a, and turn unreachable objects loose
 d               remove redundant packs, and run git-prune-packed
 f               pass --no-reuse-delta to git-pack-objects
 n               do not run git-update-server-info
@@ -23,7 +23,7 @@ max-pack-size=  maximum size of each packfile
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
-no_update_info= all_into_one= remove_redundant= keep_unreachable=
+no_update_info= all_into_one= remove_redundant= unpack_unreachable=
 local= quiet= no_reuse= extra=
 while test $# != 0
 do
@@ -31,7 +31,7 @@ do
        -n)     no_update_info=t ;;
        -a)     all_into_one=t ;;
        -A)     all_into_one=t
-               keep_unreachable=--keep-unreachable ;;
+               unpack_unreachable=--unpack-unreachable ;;
        -d)     remove_redundant=t ;;
        -q)     quiet=-q ;;
        -f)     no_reuse=--no-reuse-object ;;
@@ -79,9 +79,9 @@ case ",$all_into_one," in
        if test -z "$args"
        then
                args='--unpacked --incremental'
-       elif test -n "$keep_unreachable"
+       elif test -n "$unpack_unreachable"
        then
-               args="$args $keep_unreachable"
+               args="$args $unpack_unreachable"
        fi
        ;;
 esac
index 1e1d98656d645371612cac02abad4ea33757d40e..a598fdc890c817875766c29aa714d622af770f4b 100755 (executable)
@@ -521,8 +521,30 @@ EOT
        open(C,"<",$compose_filename)
                or die "Failed to open $compose_filename : " . $!;
 
+       my $need_8bit_cte = file_has_nonascii($compose_filename);
+       my $in_body = 0;
        while(<C>) {
                next if m/^GIT: /;
+               if (!$in_body && /^\n$/) {
+                       $in_body = 1;
+                       if ($need_8bit_cte) {
+                               print C2 "MIME-Version: 1.0\n",
+                                        "Content-Type: text/plain; ",
+                                          "charset=utf-8\n",
+                                        "Content-Transfer-Encoding: 8bit\n";
+                       }
+               }
+               if (!$in_body && /^MIME-Version:/i) {
+                       $need_8bit_cte = 0;
+               }
+               if (!$in_body && /^Subject: ?(.*)/i) {
+                       my $subject = $1;
+                       $_ = "Subject: " .
+                               ($subject =~ /[^[:ascii:]]/ ?
+                                quote_rfc2047($subject) :
+                                $subject) .
+                               "\n";
+               }
                print C2 $_;
        }
        close(C);
@@ -613,6 +635,14 @@ sub unquote_rfc2047 {
        return wantarray ? ($_, $encoding) : $_;
 }
 
+sub quote_rfc2047 {
+       local $_ = shift;
+       my $encoding = shift || 'utf-8';
+       s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
+       s/(.*)/=\?$encoding\?q\?$1\?=/;
+       return $_;
+}
+
 # use the simplest quoting being able to handle the recipient
 sub sanitize_address
 {
@@ -630,13 +660,12 @@ sub sanitize_address
 
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
-               $recipient_name =~ s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
-               $recipient_name =~ s/(.*)/=\?utf-8\?q\?$1\?=/;
+               $recipient_name = quote_rfc2047($recipient_name);
        }
 
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
-               $recipient_name =~ s/(["\\\r])/\\$1/;
+               $recipient_name =~ s/(["\\\r])/\\$1/g;
                $recipient_name = "\"$recipient_name\"";
        }
 
@@ -959,3 +988,13 @@ sub validate_patch {
        }
        return undef;
 }
+
+sub file_has_nonascii {
+       my $fn = shift;
+       open(my $fh, '<', $fn)
+               or die "unable to open $fn: $!\n";
+       while (my $line = <$fh>) {
+               return 1 if $line =~ /[^[:ascii:]]/;
+       }
+       return 0;
+}
index 67f7a28cb30ae5cb16e5b5f7d947e00621267ff5..100737210de3c76682ab3803626a36328fb27ca1 100755 (executable)
@@ -5,7 +5,7 @@
 # Copyright (c) 2007 Lars Hjemli
 
 USAGE="[--quiet] [--cached] \
-[add <repo> [-b branch]|status|init|update|summary [-n|--summary-limit <n>] [<commit>]] \
+[add <repo> [-b branch]|status|init|update [-i|--init]|summary [-n|--summary-limit <n>] [<commit>]] \
 [--] [<path>...]"
 OPTIONS_SPEC=
 . git-sh-setup
@@ -74,8 +74,7 @@ module_name()
 {
        # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
        re=$(printf '%s' "$1" | sed -e 's/[].[^$\\*]/\\&/g')
-       name=$( GIT_CONFIG=.gitmodules \
-               git config --get-regexp '^submodule\..*\.path$' |
+       name=$( git config -f .gitmodules --get-regexp '^submodule\..*\.path$' |
                sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
        test -z "$name" &&
        die "No submodule mapping found in .gitmodules for path '$path'"
@@ -198,8 +197,8 @@ cmd_add()
        git add "$path" ||
        die "Failed to add submodule '$path'"
 
-       GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" &&
-       GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" &&
+       git config -f .gitmodules submodule."$path".path "$path" &&
+       git config -f .gitmodules submodule."$path".url "$repo" &&
        git add .gitmodules ||
        die "Failed to register submodule '$path'"
 }
@@ -240,7 +239,7 @@ cmd_init()
                url=$(git config submodule."$name".url)
                test -z "$url" || continue
 
-               url=$(GIT_CONFIG=.gitmodules git config submodule."$name".url)
+               url=$(git config -f .gitmodules submodule."$name".url)
                test -z "$url" &&
                die "No url found for submodule path '$path' in .gitmodules"
 
@@ -272,6 +271,10 @@ cmd_update()
                -q|--quiet)
                        quiet=1
                        ;;
+               -i|--init)
+                       shift
+                       cmd_init "$@" || return
+                       ;;
                --)
                        shift
                        break
@@ -297,6 +300,7 @@ cmd_update()
                        # path have been specified
                        test "$#" != "0" &&
                        say "Submodule path '$path' not initialized"
+                       say "Maybe you want to use 'update --init'?"
                        continue
                fi
 
index f90dfabffa894741f500e513d5380c7752d0195c..e1b6045605865cbfc4ae0d57039111d5df825649 100644 (file)
@@ -40,9 +40,9 @@ endif
 all:: gitk-wish $(ALL_MSGFILES)
 
 install:: all
-       $(INSTALL) gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
-       $(INSTALL) -d '$(DESTDIR_SQ)$(msgsdir_SQ)'
-       $(foreach p,$(ALL_MSGFILES), $(INSTALL) $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true
+       $(INSTALL) -m 755 gitk-wish '$(DESTDIR_SQ)$(bindir_SQ)'/gitk
+       $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(msgsdir_SQ)'
+       $(foreach p,$(ALL_MSGFILES), $(INSTALL) -m 644 $p '$(DESTDIR_SQ)$(msgsdir_SQ)' &&) true
 
 uninstall::
        $(foreach p,$(ALL_MSGFILES), $(RM) '$(DESTDIR_SQ)$(msgsdir_SQ)'/$(notdir $p) &&) true
index 9a4d9c40cf6ec1bed01a2c2b950efb2d9887a8a5..22bcd18a4671b3bdd78f065c65bdaf042ba4a77e 100644 (file)
@@ -2,7 +2,7 @@
 # Tcl ignores the next line -*- tcl -*- \
 exec wish "$0" -- "$@"
 
-# Copyright (C) 2005-2006 Paul Mackerras.  All rights reserved.
+# Copyright © 2005-2008 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.
@@ -47,12 +47,24 @@ proc filereadable {fd script} {
     lappend runq [list $fd $script]
 }
 
+proc nukefile {fd} {
+    global runq
+
+    for {set i 0} {$i < [llength $runq]} {} {
+       if {[lindex $runq $i 0] eq $fd} {
+           set runq [lreplace $runq $i $i]
+       } else {
+           incr i
+       }
+    }
+}
+
 proc dorunq {} {
     global isonrunq runq
 
     set tstart [clock clicks -milliseconds]
     set t0 $tstart
-    while {$runq ne {}} {
+    while {[llength $runq] > 0} {
        set fd [lindex $runq 0 0]
        set script [lindex $runq 0 1]
        set repeat [eval $script]
@@ -78,41 +90,274 @@ proc dorunq {} {
     }
 }
 
-# Start off a git rev-list process and arrange to read its output
+proc unmerged_files {files} {
+    global nr_unmerged
+
+    # 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 {} . "[mc "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 {$files eq {} || [path_filter $files $fname]} {
+           lappend mlist $fname
+       }
+    }
+    catch {close $fd}
+    return $mlist
+}
+
+proc parseviewargs {n arglist} {
+    global vdatemode vmergeonly vflags vdflags vrevs vfiltered vorigargs
+
+    set vdatemode($n) 0
+    set vmergeonly($n) 0
+    set glflags {}
+    set diffargs {}
+    set nextisval 0
+    set revargs {}
+    set origargs $arglist
+    set allknown 1
+    set filtered 0
+    set i -1
+    foreach arg $arglist {
+       incr i
+       if {$nextisval} {
+           lappend glflags $arg
+           set nextisval 0
+           continue
+       }
+       switch -glob -- $arg {
+           "-d" -
+           "--date-order" {
+               set vdatemode($n) 1
+               # remove from origargs in case we hit an unknown option
+               set origargs [lreplace $origargs $i $i]
+               incr i -1
+           }
+           # These request or affect diff output, which we don't want.
+           # Some could be used to set our defaults for diff display.
+           "-[puabwcrRBMC]" -
+           "--no-renames" - "--full-index" - "--binary" - "--abbrev=*" -
+           "--find-copies-harder" - "-l*" - "--ext-diff" - "--no-ext-diff" -
+           "--src-prefix=*" - "--dst-prefix=*" - "--no-prefix" -
+           "-O*" - "--text" - "--full-diff" - "--ignore-space-at-eol" -
+           "--ignore-space-change" - "-U*" - "--unified=*" {
+               lappend diffargs $arg
+           }
+           # These cause our parsing of git log's output to fail, or else
+           # they're options we want to set ourselves, so ignore them.
+           "--raw" - "--patch-with-raw" - "--patch-with-stat" -
+           "--name-only" - "--name-status" - "--color" - "--color-words" -
+           "--log-size" - "--pretty=*" - "--decorate" - "--abbrev-commit" -
+           "--cc" - "-z" - "--header" - "--parents" - "--boundary" -
+           "--no-color" - "-g" - "--walk-reflogs" - "--no-walk" -
+           "--timestamp" - "relative-date" - "--date=*" - "--stdin" -
+           "--objects" - "--objects-edge" - "--reverse" {
+           }
+           # These are harmless, and some are even useful
+           "--stat=*" - "--numstat" - "--shortstat" - "--summary" -
+           "--check" - "--exit-code" - "--quiet" - "--topo-order" -
+           "--full-history" - "--dense" - "--sparse" -
+           "--follow" - "--left-right" - "--encoding=*" {
+               lappend glflags $arg
+           }
+           # These mean that we get a subset of the commits
+           "--diff-filter=*" - "--no-merges" - "--unpacked" -
+           "--max-count=*" - "--skip=*" - "--since=*" - "--after=*" -
+           "--until=*" - "--before=*" - "--max-age=*" - "--min-age=*" -
+           "--author=*" - "--committer=*" - "--grep=*" - "-[iE]" -
+           "--remove-empty" - "--first-parent" - "--cherry-pick" -
+           "-S*" - "--pickaxe-all" - "--pickaxe-regex" - {
+               set filtered 1
+               lappend glflags $arg
+           }
+           # This appears to be the only one that has a value as a
+           # separate word following it
+           "-n" {
+               set filtered 1
+               set nextisval 1
+               lappend glflags $arg
+           }
+           "--not" {
+               set notflag [expr {!$notflag}]
+               lappend revargs $arg
+           }
+           "--all" {
+               lappend revargs $arg
+           }
+           "--merge" {
+               set vmergeonly($n) 1
+               # git rev-parse doesn't understand --merge
+               lappend revargs --gitk-symmetric-diff-marker MERGE_HEAD...HEAD
+           }
+           # Other flag arguments including -<n>
+           "-*" {
+               if {[string is digit -strict [string range $arg 1 end]]} {
+                   set filtered 1
+               } else {
+                   # a flag argument that we don't recognize;
+                   # that means we can't optimize
+                   set allknown 0
+               }
+               lappend glflags $arg
+           }
+           # Non-flag arguments specify commits or ranges of commits
+           default {
+               if {[string match "*...*" $arg]} {
+                   lappend revargs --gitk-symmetric-diff-marker
+               }
+               lappend revargs $arg
+           }
+       }
+    }
+    set vdflags($n) $diffargs
+    set vflags($n) $glflags
+    set vrevs($n) $revargs
+    set vfiltered($n) $filtered
+    set vorigargs($n) $origargs
+    return $allknown
+}
+
+proc parseviewrevs {view revs} {
+    global vposids vnegids
+
+    if {$revs eq {}} {
+       set revs HEAD
+    }
+    if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
+       # we get stdout followed by stderr in $err
+       # for an unknown rev, git rev-parse echoes it and then errors out
+       set errlines [split $err "\n"]
+       set badrev {}
+       for {set l 0} {$l < [llength $errlines]} {incr l} {
+           set line [lindex $errlines $l]
+           if {!([string length $line] == 40 && [string is xdigit $line])} {
+               if {[string match "fatal:*" $line]} {
+                   if {[string match "fatal: ambiguous argument*" $line]
+                       && $badrev ne {}} {
+                       if {[llength $badrev] == 1} {
+                           set err "unknown revision $badrev"
+                       } else {
+                           set err "unknown revisions: [join $badrev ", "]"
+                       }
+                   } else {
+                       set err [join [lrange $errlines $l end] "\n"]
+                   }
+                   break
+               }
+               lappend badrev $line
+           }
+       }                   
+       error_popup "Error parsing revisions: $err"
+       return {}
+    }
+    set ret {}
+    set pos {}
+    set neg {}
+    set sdm 0
+    foreach id [split $ids "\n"] {
+       if {$id eq "--gitk-symmetric-diff-marker"} {
+           set sdm 4
+       } elseif {[string match "^*" $id]} {
+           if {$sdm != 1} {
+               lappend ret $id
+               if {$sdm == 3} {
+                   set sdm 0
+               }
+           }
+           lappend neg [string range $id 1 end]
+       } else {
+           if {$sdm != 2} {
+               lappend ret $id
+           } else {
+               lset ret end [lindex $ret end]...$id
+           }
+           lappend pos $id
+       }
+       incr sdm -1
+    }
+    set vposids($view) $pos
+    set vnegids($view) $neg
+    return $ret
+}
+
+# Start off a git log process and arrange to read its output
 proc start_rev_list {view} {
-    global startmsecs
-    global commfd leftover tclencoding datemode
-    global viewargs viewargscmd viewfiles commitidx viewcomplete vnextroot
+    global startmsecs commitidx viewcomplete curview
+    global commfd leftover tclencoding
+    global viewargs viewargscmd viewfiles vfilelimit
     global showlocalchanges commitinterest mainheadid
-    global progressdirn progresscoords proglastnc curview
+    global viewactive loginstance viewinstances vmergeonly
+    global pending_select mainheadid
+    global vcanopt vflags vrevs vorigargs
 
     set startmsecs [clock clicks -milliseconds]
     set commitidx($view) 0
-    set viewcomplete($view) 0
-    set vnextroot($view) 0
+    # these are set this way for the error exits
+    set viewcomplete($view) 1
+    set viewactive($view) 0
+    varcinit $view
+
     set args $viewargs($view)
     if {$viewargscmd($view) ne {}} {
        if {[catch {
            set str [exec sh -c $viewargscmd($view)]
        } err]} {
            error_popup "Error executing --argscmd command: $err"
-           exit 1
+           return 0
        }
        set args [concat $args [split $str "\n"]]
     }
-    set order "--topo-order"
-    if {$datemode} {
-       set order "--date-order"
+    set vcanopt($view) [parseviewargs $view $args]
+
+    set files $viewfiles($view)
+    if {$vmergeonly($view)} {
+       set files [unmerged_files $files]
+       if {$files eq {}} {
+           global nr_unmerged
+           if {$nr_unmerged == 0} {
+               error_popup [mc "No files selected: --merge specified but\
+                            no files are unmerged."]
+           } else {
+               error_popup [mc "No files selected: --merge specified but\
+                            no unmerged files are within file limit."]
+           }
+           return 0
+       }
     }
+    set vfilelimit($view) $files
+
+    if {$vcanopt($view)} {
+       set revs [parseviewrevs $view $vrevs($view)]
+       if {$revs eq {}} {
+           return 0
+       }
+       set args [concat $vflags($view) $revs]
+    } else {
+       set args $vorigargs($view)
+    }
+
     if {[catch {
-       set fd [open [concat | git log --no-color -z --pretty=raw $order --parents \
-                        --boundary $args "--" $viewfiles($view)] r]
+       set fd [open [concat | git log --no-color -z --pretty=raw --parents \
+                        --boundary $args "--" $files] r]
     } err]} {
-       error_popup "[mc "Error executing git rev-list:"] $err"
-       exit 1
+       error_popup "[mc "Error executing git log:"] $err"
+       return 0
     }
-    set commfd($view) $fd
-    set leftover($view) {}
+    set i [incr loginstance]
+    set viewinstances($view) [list $i]
+    set commfd($i) $fd
+    set leftover($i) {}
     if {$showlocalchanges} {
        lappend commitinterest($mainheadid) {dodiffindex}
     }
@@ -120,35 +365,152 @@ proc start_rev_list {view} {
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
-    filerun $fd [list getcommitlines $fd $view]
+    filerun $fd [list getcommitlines $fd $i $view 0]
     nowbusy $view [mc "Reading"]
     if {$view == $curview} {
-       set progressdirn 1
-       set progresscoords {0 0}
-       set proglastnc 0
+       set pending_select $mainheadid
     }
+    set viewcomplete($view) 0
+    set viewactive($view) 1
+    return 1
 }
 
-proc stop_rev_list {} {
-    global commfd curview
+proc stop_rev_list {view} {
+    global commfd viewinstances leftover
 
-    if {![info exists commfd($curview)]} return
-    set fd $commfd($curview)
-    catch {
-       set pid [pid $fd]
-       exec kill $pid
+    foreach inst $viewinstances($view) {
+       set fd $commfd($inst)
+       catch {
+           set pid [pid $fd]
+           exec kill $pid
+       }
+       catch {close $fd}
+       nukefile $fd
+       unset commfd($inst)
+       unset leftover($inst)
     }
-    catch {close $fd}
-    unset commfd($curview)
+    set viewinstances($view) {}
 }
 
 proc getcommits {} {
-    global phase canv curview
+    global canv curview need_redisplay viewactive
 
-    set phase getcommits
     initlayout
-    start_rev_list $curview
-    show_status [mc "Reading commits..."]
+    if {[start_rev_list $curview]} {
+       show_status [mc "Reading commits..."]
+       set need_redisplay 1
+    } else {
+       show_status [mc "No commits selected"]
+    }
+}
+
+proc updatecommits {} {
+    global curview vcanopt vorigargs vfilelimit viewinstances
+    global viewactive viewcomplete loginstance tclencoding mainheadid
+    global startmsecs commfd showneartags showlocalchanges leftover
+    global mainheadid pending_select
+    global isworktree
+    global varcid vposids vnegids vflags vrevs
+
+    set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
+    set oldmainid $mainheadid
+    rereadrefs
+    if {$showlocalchanges} {
+       if {$mainheadid ne $oldmainid} {
+           dohidelocalchanges
+       }
+       if {[commitinview $mainheadid $curview]} {
+           dodiffindex
+       }
+    }
+    set view $curview
+    if {$vcanopt($view)} {
+       set oldpos $vposids($view)
+       set oldneg $vnegids($view)
+       set revs [parseviewrevs $view $vrevs($view)]
+       if {$revs eq {}} {
+           return
+       }
+       # note: getting the delta when negative refs change is hard,
+       # and could require multiple git log invocations, so in that
+       # case we ask git log for all the commits (not just the delta)
+       if {$oldneg eq $vnegids($view)} {
+           set newrevs {}
+           set npos 0
+           # take out positive refs that we asked for before or
+           # that we have already seen
+           foreach rev $revs {
+               if {[string length $rev] == 40} {
+                   if {[lsearch -exact $oldpos $rev] < 0
+                       && ![info exists varcid($view,$rev)]} {
+                       lappend newrevs $rev
+                       incr npos
+                   }
+               } else {
+                   lappend $newrevs $rev
+               }
+           }
+           if {$npos == 0} return
+           set revs $newrevs
+           set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
+       }
+       set args [concat $vflags($view) $revs --not $oldpos]
+    } else {
+       set args $vorigargs($view)
+    }
+    if {[catch {
+       set fd [open [concat | git log --no-color -z --pretty=raw --parents \
+                         --boundary $args "--" $vfilelimit($view)] r]
+    } err]} {
+       error_popup "Error executing git log: $err"
+       return
+    }
+    if {$viewactive($view) == 0} {
+       set startmsecs [clock clicks -milliseconds]
+    }
+    set i [incr loginstance]
+    lappend viewinstances($view) $i
+    set commfd($i) $fd
+    set leftover($i) {}
+    fconfigure $fd -blocking 0 -translation lf -eofchar {}
+    if {$tclencoding != {}} {
+       fconfigure $fd -encoding $tclencoding
+    }
+    filerun $fd [list getcommitlines $fd $i $view 1]
+    incr viewactive($view)
+    set viewcomplete($view) 0
+    set pending_select $mainheadid
+    nowbusy $view "Reading"
+    if {$showneartags} {
+       getallcommits
+    }
+}
+
+proc reloadcommits {} {
+    global curview viewcomplete selectedline currentid thickerline
+    global showneartags treediffs commitinterest cached_commitrow
+    global targetid
+
+    if {!$viewcomplete($curview)} {
+       stop_rev_list $curview
+    }
+    resetvarcs $curview
+    set selectedline {}
+    catch {unset currentid}
+    catch {unset thickerline}
+    catch {unset treediffs}
+    readrefs
+    changedrefs
+    if {$showneartags} {
+       getallcommits
+    }
+    clear_display
+    catch {unset commitinterest}
+    catch {unset cached_commitrow}
+    catch {unset targetid}
+    setcanvscroll
+    getcommits
+    return 0
 }
 
 # This makes a string representation of a positive integer which
@@ -164,46 +526,759 @@ proc strrep {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
+# Procedures used in reordering commits from git log (without
+# --topo-order) into the order for display.
+
+proc varcinit {view} {
+    global varcstart vupptr vdownptr vleftptr vbackptr varctok varcrow
+    global vtokmod varcmod vrowmod varcix vlastins
+
+    set varcstart($view) {{}}
+    set vupptr($view) {0}
+    set vdownptr($view) {0}
+    set vleftptr($view) {0}
+    set vbackptr($view) {0}
+    set varctok($view) {{}}
+    set varcrow($view) {{}}
+    set vtokmod($view) {}
+    set varcmod($view) 0
+    set vrowmod($view) 0
+    set varcix($view) {{}}
+    set vlastins($view) {0}
+}
+
+proc resetvarcs {view} {
+    global varcid varccommits parents children vseedcount ordertok
+
+    foreach vid [array names varcid $view,*] {
+       unset varcid($vid)
+       unset children($vid)
+       unset parents($vid)
+    }
+    # some commits might have children but haven't been seen yet
+    foreach vid [array names children $view,*] {
+       unset children($vid)
+    }
+    foreach va [array names varccommits $view,*] {
+       unset varccommits($va)
+    }
+    foreach vd [array names vseedcount $view,*] {
+       unset vseedcount($vd)
+    }
+    catch {unset ordertok}
+}
+
+# returns a list of the commits with no children
+proc seeds {v} {
+    global vdownptr vleftptr varcstart
+
+    set ret {}
+    set a [lindex $vdownptr($v) 0]
+    while {$a != 0} {
+       lappend ret [lindex $varcstart($v) $a]
+       set a [lindex $vleftptr($v) $a]
+    }
+    return $ret
+}
+
+proc newvarc {view id} {
+    global varcid varctok parents children vdatemode
+    global vupptr vdownptr vleftptr vbackptr varcrow varcix varcstart
+    global commitdata commitinfo vseedcount varccommits vlastins
+
+    set a [llength $varctok($view)]
+    set vid $view,$id
+    if {[llength $children($vid)] == 0 || $vdatemode($view)} {
+       if {![info exists commitinfo($id)]} {
+           parsecommit $id $commitdata($id) 1
+       }
+       set cdate [lindex $commitinfo($id) 4]
+       if {![string is integer -strict $cdate]} {
+           set cdate 0
+       }
+       if {![info exists vseedcount($view,$cdate)]} {
+           set vseedcount($view,$cdate) -1
+       }
+       set c [incr vseedcount($view,$cdate)]
+       set cdate [expr {$cdate ^ 0xffffffff}]
+       set tok "s[strrep $cdate][strrep $c]"
+    } else {
+       set tok {}
+    }
+    set ka 0
+    if {[llength $children($vid)] > 0} {
+       set kid [lindex $children($vid) end]
+       set k $varcid($view,$kid)
+       if {[string compare [lindex $varctok($view) $k] $tok] > 0} {
+           set ki $kid
+           set ka $k
+           set tok [lindex $varctok($view) $k]
+       }
+    }
+    if {$ka != 0} {
+       set i [lsearch -exact $parents($view,$ki) $id]
+       set j [expr {[llength $parents($view,$ki)] - 1 - $i}]
+       append tok [strrep $j]
+    }
+    set c [lindex $vlastins($view) $ka]
+    if {$c == 0 || [string compare $tok [lindex $varctok($view) $c]] < 0} {
+       set c $ka
+       set b [lindex $vdownptr($view) $ka]
+    } else {
+       set b [lindex $vleftptr($view) $c]
+    }
+    while {$b != 0 && [string compare $tok [lindex $varctok($view) $b]] >= 0} {
+       set c $b
+       set b [lindex $vleftptr($view) $c]
+    }
+    if {$c == $ka} {
+       lset vdownptr($view) $ka $a
+       lappend vbackptr($view) 0
+    } else {
+       lset vleftptr($view) $c $a
+       lappend vbackptr($view) $c
+    }
+    lset vlastins($view) $ka $a
+    lappend vupptr($view) $ka
+    lappend vleftptr($view) $b
+    if {$b != 0} {
+       lset vbackptr($view) $b $a
+    }
+    lappend varctok($view) $tok
+    lappend varcstart($view) $id
+    lappend vdownptr($view) 0
+    lappend varcrow($view) {}
+    lappend varcix($view) {}
+    set varccommits($view,$a) {}
+    lappend vlastins($view) 0
+    return $a
+}
+
+proc splitvarc {p v} {
+    global varcid varcstart varccommits varctok
+    global vupptr vdownptr vleftptr vbackptr varcix varcrow vlastins
+
+    set oa $varcid($v,$p)
+    set ac $varccommits($v,$oa)
+    set i [lsearch -exact $varccommits($v,$oa) $p]
+    if {$i <= 0} return
+    set na [llength $varctok($v)]
+    # "%" sorts before "0"...
+    set tok "[lindex $varctok($v) $oa]%[strrep $i]"
+    lappend varctok($v) $tok
+    lappend varcrow($v) {}
+    lappend varcix($v) {}
+    set varccommits($v,$oa) [lrange $ac 0 [expr {$i - 1}]]
+    set varccommits($v,$na) [lrange $ac $i end]
+    lappend varcstart($v) $p
+    foreach id $varccommits($v,$na) {
+       set varcid($v,$id) $na
+    }
+    lappend vdownptr($v) [lindex $vdownptr($v) $oa]
+    lappend vlastins($v) [lindex $vlastins($v) $oa]
+    lset vdownptr($v) $oa $na
+    lset vlastins($v) $oa 0
+    lappend vupptr($v) $oa
+    lappend vleftptr($v) 0
+    lappend vbackptr($v) 0
+    for {set b [lindex $vdownptr($v) $na]} {$b != 0} {set b [lindex $vleftptr($v) $b]} {
+       lset vupptr($v) $b $na
+    }
+}
+
+proc renumbervarc {a v} {
+    global parents children varctok varcstart varccommits
+    global vupptr vdownptr vleftptr vbackptr vlastins varcid vtokmod vdatemode
+
+    set t1 [clock clicks -milliseconds]
+    set todo {}
+    set isrelated($a) 1
+    set kidchanged($a) 1
+    set ntot 0
+    while {$a != 0} {
+       if {[info exists isrelated($a)]} {
+           lappend todo $a
+           set id [lindex $varccommits($v,$a) end]
+           foreach p $parents($v,$id) {
+               if {[info exists varcid($v,$p)]} {
+                   set isrelated($varcid($v,$p)) 1
+               }
+           }
+       }
+       incr ntot
+       set b [lindex $vdownptr($v) $a]
+       if {$b == 0} {
+           while {$a != 0} {
+               set b [lindex $vleftptr($v) $a]
+               if {$b != 0} break
+               set a [lindex $vupptr($v) $a]
+           }
+       }
+       set a $b
+    }
+    foreach a $todo {
+       if {![info exists kidchanged($a)]} continue
+       set id [lindex $varcstart($v) $a]
+       if {[llength $children($v,$id)] > 1} {
+           set children($v,$id) [lsort -command [list vtokcmp $v] \
+                                     $children($v,$id)]
+       }
+       set oldtok [lindex $varctok($v) $a]
+       if {!$vdatemode($v)} {
+           set tok {}
+       } else {
+           set tok $oldtok
+       }
+       set ka 0
+       set kid [last_real_child $v,$id]
+       if {$kid ne {}} {
+           set k $varcid($v,$kid)
+           if {[string compare [lindex $varctok($v) $k] $tok] > 0} {
+               set ki $kid
+               set ka $k
+               set tok [lindex $varctok($v) $k]
+           }
+       }
+       if {$ka != 0} {
+           set i [lsearch -exact $parents($v,$ki) $id]
+           set j [expr {[llength $parents($v,$ki)] - 1 - $i}]
+           append tok [strrep $j]
+       }
+       if {$tok eq $oldtok} {
+           continue
+       }
+       set id [lindex $varccommits($v,$a) end]
+       foreach p $parents($v,$id) {
+           if {[info exists varcid($v,$p)]} {
+               set kidchanged($varcid($v,$p)) 1
+           } else {
+               set sortkids($p) 1
+           }
+       }
+       lset varctok($v) $a $tok
+       set b [lindex $vupptr($v) $a]
+       if {$b != $ka} {
+           if {[string compare [lindex $varctok($v) $ka] $vtokmod($v)] < 0} {
+               modify_arc $v $ka
+           }
+           if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
+               modify_arc $v $b
+           }
+           set c [lindex $vbackptr($v) $a]
+           set d [lindex $vleftptr($v) $a]
+           if {$c == 0} {
+               lset vdownptr($v) $b $d
+           } else {
+               lset vleftptr($v) $c $d
+           }
+           if {$d != 0} {
+               lset vbackptr($v) $d $c
+           }
+           if {[lindex $vlastins($v) $b] == $a} {
+               lset vlastins($v) $b $c
+           }
+           lset vupptr($v) $a $ka
+           set c [lindex $vlastins($v) $ka]
+           if {$c == 0 || \
+                   [string compare $tok [lindex $varctok($v) $c]] < 0} {
+               set c $ka
+               set b [lindex $vdownptr($v) $ka]
+           } else {
+               set b [lindex $vleftptr($v) $c]
+           }
+           while {$b != 0 && \
+                     [string compare $tok [lindex $varctok($v) $b]] >= 0} {
+               set c $b
+               set b [lindex $vleftptr($v) $c]
+           }
+           if {$c == $ka} {
+               lset vdownptr($v) $ka $a
+               lset vbackptr($v) $a 0
+           } else {
+               lset vleftptr($v) $c $a
+               lset vbackptr($v) $a $c
+           }
+           lset vleftptr($v) $a $b
+           if {$b != 0} {
+               lset vbackptr($v) $b $a
+           }
+           lset vlastins($v) $ka $a
+       }
+    }
+    foreach id [array names sortkids] {
+       if {[llength $children($v,$id)] > 1} {
+           set children($v,$id) [lsort -command [list vtokcmp $v] \
+                                     $children($v,$id)]
+       }
+    }
+    set t2 [clock clicks -milliseconds]
+    #puts "renumbervarc did [llength $todo] of $ntot arcs in [expr {$t2-$t1}]ms"
+}
+
+# Fix up the graph after we have found out that in view $v,
+# $p (a commit that we have already seen) is actually the parent
+# of the last commit in arc $a.
+proc fix_reversal {p a v} {
+    global varcid varcstart varctok vupptr
+
+    set pa $varcid($v,$p)
+    if {$p ne [lindex $varcstart($v) $pa]} {
+       splitvarc $p $v
+       set pa $varcid($v,$p)
+    }
+    # seeds always need to be renumbered
+    if {[lindex $vupptr($v) $pa] == 0 ||
+       [string compare [lindex $varctok($v) $a] \
+            [lindex $varctok($v) $pa]] > 0} {
+       renumbervarc $pa $v
+    }
+}
+
+proc insertrow {id p v} {
+    global cmitlisted children parents varcid varctok vtokmod
+    global varccommits ordertok commitidx numcommits curview
+    global targetid targetrow
+
+    readcommit $id
+    set vid $v,$id
+    set cmitlisted($vid) 1
+    set children($vid) {}
+    set parents($vid) [list $p]
+    set a [newvarc $v $id]
+    set varcid($vid) $a
+    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] < 0} {
+       modify_arc $v $a
+    }
+    lappend varccommits($v,$a) $id
+    set vp $v,$p
+    if {[llength [lappend children($vp) $id]] > 1} {
+       set children($vp) [lsort -command [list vtokcmp $v] $children($vp)]
+       catch {unset ordertok}
+    }
+    fix_reversal $p $a $v
+    incr commitidx($v)
+    if {$v == $curview} {
+       set numcommits $commitidx($v)
+       setcanvscroll
+       if {[info exists targetid]} {
+           if {![comes_before $targetid $p]} {
+               incr targetrow
+           }
+       }
+    }
+}
+
+proc insertfakerow {id p} {
+    global varcid varccommits parents children cmitlisted
+    global commitidx varctok vtokmod targetid targetrow curview numcommits
+
+    set v $curview
+    set a $varcid($v,$p)
+    set i [lsearch -exact $varccommits($v,$a) $p]
+    if {$i < 0} {
+       puts "oops: insertfakerow can't find [shortids $p] on arc $a"
+       return
+    }
+    set children($v,$id) {}
+    set parents($v,$id) [list $p]
+    set varcid($v,$id) $a
+    lappend children($v,$p) $id
+    set cmitlisted($v,$id) 1
+    set numcommits [incr commitidx($v)]
+    # note we deliberately don't update varcstart($v) even if $i == 0
+    set varccommits($v,$a) [linsert $varccommits($v,$a) $i $id]
+    modify_arc $v $a $i
+    if {[info exists targetid]} {
+       if {![comes_before $targetid $p]} {
+           incr targetrow
+       }
+    }
+    setcanvscroll
+    drawvisible
+}
+
+proc removefakerow {id} {
+    global varcid varccommits parents children commitidx
+    global varctok vtokmod cmitlisted currentid selectedline
+    global targetid curview numcommits
+
+    set v $curview
+    if {[llength $parents($v,$id)] != 1} {
+       puts "oops: removefakerow [shortids $id] has [llength $parents($v,$id)] parents"
+       return
+    }
+    set p [lindex $parents($v,$id) 0]
+    set a $varcid($v,$id)
+    set i [lsearch -exact $varccommits($v,$a) $id]
+    if {$i < 0} {
+       puts "oops: removefakerow can't find [shortids $id] on arc $a"
+       return
+    }
+    unset varcid($v,$id)
+    set varccommits($v,$a) [lreplace $varccommits($v,$a) $i $i]
+    unset parents($v,$id)
+    unset children($v,$id)
+    unset cmitlisted($v,$id)
+    set numcommits [incr commitidx($v) -1]
+    set j [lsearch -exact $children($v,$p) $id]
+    if {$j >= 0} {
+       set children($v,$p) [lreplace $children($v,$p) $j $j]
+    }
+    modify_arc $v $a $i
+    if {[info exist currentid] && $id eq $currentid} {
+       unset currentid
+       set selectedline {}
+    }
+    if {[info exists targetid] && $targetid eq $id} {
+       set targetid $p
+    }
+    setcanvscroll
+    drawvisible
+}
+
+proc first_real_child {vp} {
+    global children nullid nullid2
+
+    foreach id $children($vp) {
+       if {$id ne $nullid && $id ne $nullid2} {
+           return $id
+       }
+    }
+    return {}
+}
+
+proc last_real_child {vp} {
+    global children nullid nullid2
+
+    set kids $children($vp)
+    for {set i [llength $kids]} {[incr i -1] >= 0} {} {
+       set id [lindex $kids $i]
+       if {$id ne $nullid && $id ne $nullid2} {
+           return $id
+       }
+    }
+    return {}
+}
+
+proc vtokcmp {v a b} {
+    global varctok varcid
+
+    return [string compare [lindex $varctok($v) $varcid($v,$a)] \
+               [lindex $varctok($v) $varcid($v,$b)]]
+}
+
+# This assumes that if lim is not given, the caller has checked that
+# arc a's token is less than $vtokmod($v)
+proc modify_arc {v a {lim {}}} {
+    global varctok vtokmod varcmod varcrow vupptr curview vrowmod varccommits
+
+    if {$lim ne {}} {
+       set c [string compare [lindex $varctok($v) $a] $vtokmod($v)]
+       if {$c > 0} return
+       if {$c == 0} {
+           set r [lindex $varcrow($v) $a]
+           if {$r ne {} && $vrowmod($v) <= $r + $lim} return
+       }
+    }
+    set vtokmod($v) [lindex $varctok($v) $a]
+    set varcmod($v) $a
+    if {$v == $curview} {
+       while {$a != 0 && [lindex $varcrow($v) $a] eq {}} {
+           set a [lindex $vupptr($v) $a]
+           set lim {}
+       }
+       set r 0
+       if {$a != 0} {
+           if {$lim eq {}} {
+               set lim [llength $varccommits($v,$a)]
+           }
+           set r [expr {[lindex $varcrow($v) $a] + $lim}]
+       }
+       set vrowmod($v) $r
+       undolayout $r
+    }
+}
+
+proc update_arcrows {v} {
+    global vtokmod varcmod vrowmod varcrow commitidx currentid selectedline
+    global varcid vrownum varcorder varcix varccommits
+    global vupptr vdownptr vleftptr varctok
+    global displayorder parentlist curview cached_commitrow
+
+    if {$vrowmod($v) == $commitidx($v)} return
+    if {$v == $curview} {
+       if {[llength $displayorder] > $vrowmod($v)} {
+           set displayorder [lrange $displayorder 0 [expr {$vrowmod($v) - 1}]]
+           set parentlist [lrange $parentlist 0 [expr {$vrowmod($v) - 1}]]
+       }
+       catch {unset cached_commitrow}
+    }
+    set narctot [expr {[llength $varctok($v)] - 1}]
+    set a $varcmod($v)
+    while {$a != 0 && [lindex $varcix($v) $a] eq {}} {
+       # go up the tree until we find something that has a row number,
+       # or we get to a seed
+       set a [lindex $vupptr($v) $a]
+    }
+    if {$a == 0} {
+       set a [lindex $vdownptr($v) 0]
+       if {$a == 0} return
+       set vrownum($v) {0}
+       set varcorder($v) [list $a]
+       lset varcix($v) $a 0
+       lset varcrow($v) $a 0
+       set arcn 0
+       set row 0
+    } else {
+       set arcn [lindex $varcix($v) $a]
+       if {[llength $vrownum($v)] > $arcn + 1} {
+           set vrownum($v) [lrange $vrownum($v) 0 $arcn]
+           set varcorder($v) [lrange $varcorder($v) 0 $arcn]
+       }
+       set row [lindex $varcrow($v) $a]
+    }
+    while {1} {
+       set p $a
+       incr row [llength $varccommits($v,$a)]
+       # go down if possible
+       set b [lindex $vdownptr($v) $a]
+       if {$b == 0} {
+           # if not, go left, or go up until we can go left
+           while {$a != 0} {
+               set b [lindex $vleftptr($v) $a]
+               if {$b != 0} break
+               set a [lindex $vupptr($v) $a]
+           }
+           if {$a == 0} break
+       }
+       set a $b
+       incr arcn
+       lappend vrownum($v) $row
+       lappend varcorder($v) $a
+       lset varcix($v) $a $arcn
+       lset varcrow($v) $a $row
+    }
+    set vtokmod($v) [lindex $varctok($v) $p]
+    set varcmod($v) $p
+    set vrowmod($v) $row
+    if {[info exists currentid]} {
+       set selectedline [rowofcommit $currentid]
+    }
+}
+
+# Test whether view $v contains commit $id
+proc commitinview {id v} {
+    global varcid
+
+    return [info exists varcid($v,$id)]
+}
+
+# Return the row number for commit $id in the current view
+proc rowofcommit {id} {
+    global varcid varccommits varcrow curview cached_commitrow
+    global varctok vtokmod
+
+    set v $curview
+    if {![info exists varcid($v,$id)]} {
+       puts "oops rowofcommit no arc for [shortids $id]"
+       return {}
+    }
+    set a $varcid($v,$id)
+    if {[string compare [lindex $varctok($v) $a] $vtokmod($v)] >= 0} {
+       update_arcrows $v
+    }
+    if {[info exists cached_commitrow($id)]} {
+       return $cached_commitrow($id)
+    }
+    set i [lsearch -exact $varccommits($v,$a) $id]
+    if {$i < 0} {
+       puts "oops didn't find commit [shortids $id] in arc $a"
+       return {}
+    }
+    incr i [lindex $varcrow($v) $a]
+    set cached_commitrow($id) $i
+    return $i
+}
+
+# Returns 1 if a is on an earlier row than b, otherwise 0
+proc comes_before {a b} {
+    global varcid varctok curview
+
+    set v $curview
+    if {$a eq $b || ![info exists varcid($v,$a)] || \
+           ![info exists varcid($v,$b)]} {
+       return 0
+    }
+    if {$varcid($v,$a) != $varcid($v,$b)} {
+       return [expr {[string compare [lindex $varctok($v) $varcid($v,$a)] \
+                          [lindex $varctok($v) $varcid($v,$b)]] < 0}]
+    }
+    return [expr {[rowofcommit $a] < [rowofcommit $b]}]
+}
+
+proc bsearch {l elt} {
+    if {[llength $l] == 0 || $elt <= [lindex $l 0]} {
+       return 0
+    }
+    set lo 0
+    set hi [llength $l]
+    while {$hi - $lo > 1} {
+       set mid [expr {int(($lo + $hi) / 2)}]
+       set t [lindex $l $mid]
+       if {$elt < $t} {
+           set hi $mid
+       } elseif {$elt > $t} {
+           set lo $mid
+       } else {
+           return $mid
+       }
+    }
+    return $lo
+}
+
+# Make sure rows $start..$end-1 are valid in displayorder and parentlist
+proc make_disporder {start end} {
+    global vrownum curview commitidx displayorder parentlist
+    global varccommits varcorder parents vrowmod varcrow
+    global d_valid_start d_valid_end
+
+    if {$end > $vrowmod($curview)} {
+       update_arcrows $curview
+    }
+    set ai [bsearch $vrownum($curview) $start]
+    set start [lindex $vrownum($curview) $ai]
+    set narc [llength $vrownum($curview)]
+    for {set r $start} {$ai < $narc && $r < $end} {incr ai} {
+       set a [lindex $varcorder($curview) $ai]
+       set l [llength $displayorder]
+       set al [llength $varccommits($curview,$a)]
+       if {$l < $r + $al} {
+           if {$l < $r} {
+               set pad [ntimes [expr {$r - $l}] {}]
+               set displayorder [concat $displayorder $pad]
+               set parentlist [concat $parentlist $pad]
+           } elseif {$l > $r} {
+               set displayorder [lrange $displayorder 0 [expr {$r - 1}]]
+               set parentlist [lrange $parentlist 0 [expr {$r - 1}]]
+           }
+           foreach id $varccommits($curview,$a) {
+               lappend displayorder $id
+               lappend parentlist $parents($curview,$id)
+           }
+       } elseif {[lindex $displayorder [expr {$r + $al - 1}]] eq {}} {
+           set i $r
+           foreach id $varccommits($curview,$a) {
+               lset displayorder $i $id
+               lset parentlist $i $parents($curview,$id)
+               incr i
+           }
+       }
+       incr r $al
+    }
+}
+
+proc commitonrow {row} {
+    global displayorder
+
+    set id [lindex $displayorder $row]
+    if {$id eq {}} {
+       make_disporder $row [expr {$row + 1}]
+       set id [lindex $displayorder $row]
+    }
+    return $id
+}
+
+proc closevarcs {v} {
+    global varctok varccommits varcid parents children
+    global cmitlisted commitidx commitinterest vtokmod
+
+    set missing_parents 0
+    set scripts {}
+    set narcs [llength $varctok($v)]
+    for {set a 1} {$a < $narcs} {incr a} {
+       set id [lindex $varccommits($v,$a) end]
+       foreach p $parents($v,$id) {
+           if {[info exists varcid($v,$p)]} continue
+           # add p as a new commit
+           incr missing_parents
+           set cmitlisted($v,$p) 0
+           set parents($v,$p) {}
+           if {[llength $children($v,$p)] == 1 &&
+               [llength $parents($v,$id)] == 1} {
+               set b $a
+           } else {
+               set b [newvarc $v $p]
+           }
+           set varcid($v,$p) $b
+           if {[string compare [lindex $varctok($v) $b] $vtokmod($v)] < 0} {
+               modify_arc $v $b
+           }
+           lappend varccommits($v,$b) $p
+           incr commitidx($v)
+           if {[info exists commitinterest($p)]} {
+               foreach script $commitinterest($p) {
+                   lappend scripts [string map [list "%I" $p] $script]
+               }
+               unset commitinterest($id)
+           }
+       }
+    }
+    if {$missing_parents > 0} {
+       foreach s $scripts {
+           eval $s
+       }
+    }
+}
+
+# Use $rwid as a substitute for $id, i.e. reparent $id's children to $rwid
+# Assumes we already have an arc for $rwid.
+proc rewrite_commit {v id rwid} {
+    global children parents varcid varctok vtokmod varccommits
+
+    foreach ch $children($v,$id) {
+       # make $rwid be $ch's parent in place of $id
+       set i [lsearch -exact $parents($v,$ch) $id]
+       if {$i < 0} {
+           puts "oops rewrite_commit didn't find $id in parent list for $ch"
+       }
+       set parents($v,$ch) [lreplace $parents($v,$ch) $i $i $rwid]
+       # add $ch to $rwid's children and sort the list if necessary
+       if {[llength [lappend children($v,$rwid) $ch]] > 1} {
+           set children($v,$rwid) [lsort -command [list vtokcmp $v] \
+                                       $children($v,$rwid)]
+       }
+       # fix the graph after joining $id to $rwid
+       set a $varcid($v,$ch)
+       fix_reversal $rwid $a $v
+       # parentlist is wrong for the last element of arc $a
+       # even if displayorder is right, hence the 3rd arg here
+       modify_arc $v $a [expr {[llength $varccommits($v,$a)] - 1}]
+    }
+}
+
+proc getcommitlines {fd inst view updating}  {
+    global cmitlisted commitinterest leftover
+    global commitidx commitdata vdatemode
+    global parents children curview hlview
+    global idpending ordertok
+    global varccommits varcid varctok vtokmod vfilelimit
 
     set stuff [read $fd 500000]
     # git log doesn't terminate the last commit with a null...
-    if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
+    if {$stuff == {} && $leftover($inst) 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
-           }
+       global commfd viewcomplete viewactive viewname
+       global viewinstances
+       unset commfd($inst)
+       set i [lsearch -exact $viewinstances($view) $inst]
+       if {$i >= 0} {
+           set viewinstances($view) [lreplace $viewinstances($view) $i $i]
        }
-       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]} {
@@ -213,10 +1288,10 @@ proc getcommitlines {fd view}  {
            }
            if {[string range $err 0 4] == "usage"} {
                set err "Gitk: error reading commits$fv:\
-                       bad arguments to git rev-list."
+                       bad arguments to git log."
                if {$viewname($view) eq "Command line"} {
                    append err \
-                       "  (Note: arguments to gitk are passed to git rev-list\
+                       "  (Note: arguments to gitk are passed to git log\
                         to allow selection of commits to be displayed.)"
                }
            } else {
@@ -224,23 +1299,31 @@ proc getcommitlines {fd view}  {
            }
            error_popup $err
        }
+       if {[incr viewactive($view) -1] <= 0} {
+           set viewcomplete($view) 1
+           # Check if we have seen any ids listed as parents that haven't
+           # appeared in the list
+           closevarcs $view
+           notbusy $view
+       }
        if {$view == $curview} {
-           run chewcommits $view
+           run chewcommits
        }
        return 0
     }
     set start 0
     set gotsome 0
+    set scripts {}
     while 1 {
        set i [string first "\0" $stuff $start]
        if {$i < 0} {
-           append leftover($view) [string range $stuff $start end]
+           append leftover($inst) [string range $stuff $start end]
            break
        }
        if {$start == 0} {
-           set cmit $leftover($view)
+           set cmit $leftover($inst)
            append cmit [string range $stuff 0 [expr {$i - 1}]]
-           set leftover($view) {}
+           set leftover($inst) {}
        } else {
            set cmit [string range $stuff $start [expr {$i - 1}]]
        }
@@ -276,121 +1359,128 @@ proc getcommitlines {fd view}  {
            exit 1
        }
        set id [lindex $ids 0]
-       if {![info exists ordertok($view,$id)]} {
-           set otok "o[strrep $vnextroot($view)]"
-           incr vnextroot($view)
-           set ordertok($view,$id) $otok
-       } else {
-           set otok $ordertok($view,$id)
-           unset idpending($view,$id)
+       set vid $view,$id
+
+       if {!$listed && $updating && ![info exists varcid($vid)] &&
+           $vfilelimit($view) ne {}} {
+           # git log doesn't rewrite parents for unlisted commits
+           # when doing path limiting, so work around that here
+           # by working out the rewritten parent with git rev-list
+           # and if we already know about it, using the rewritten
+           # parent as a substitute parent for $id's children.
+           if {![catch {
+               set rwid [exec git rev-list --first-parent --max-count=1 \
+                             $id -- $vfilelimit($view)]
+           }]} {
+               if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
+                   # use $rwid in place of $id
+                   rewrite_commit $view $id $rwid
+                   continue
+               }
+           }
+       }
+
+       set a 0
+       if {[info exists varcid($vid)]} {
+           if {$cmitlisted($vid) || !$listed} continue
+           set a $varcid($vid)
        }
        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
+       set cmitlisted($vid) $listed
+       set parents($vid) $olds
+       if {![info exists children($vid)]} {
+           set children($vid) {}
+       } elseif {$a == 0 && [llength $children($vid)] == 1} {
+           set k [lindex $children($vid) 0]
+           if {[llength $parents($view,$k)] == 1 &&
+               (!$vdatemode($view) ||
+                $varcid($view,$k) == [llength $varctok($view)] - 1)} {
+               set a $varcid($view,$k)
+           }
+       }
+       if {$a == 0} {
+           # new arc
+           set a [newvarc $view $id]
+       }
+       if {[string compare [lindex $varctok($view) $a] $vtokmod($view)] < 0} {
+           modify_arc $view $a
+       }
+       if {![info exists varcid($vid)]} {
+           set varcid($vid) $a
+           lappend varccommits($view,$a) $id
+           incr commitidx($view)
+       }
+
+       set i 0
+       foreach p $olds {
+           if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
+               set vp $view,$p
+               if {[llength [lappend children($vp) $id]] > 1 &&
+                   [vtokcmp $view [lindex $children($vp) end-1] $id] > 0} {
+                   set children($vp) [lsort -command [list vtokcmp $view] \
+                                          $children($vp)]
+                   catch {unset ordertok}
+               }
+               if {[info exists varcid($view,$p)]} {
+                   fix_reversal $p $a $view
+               }
+           }
+           incr i
        }
+
        if {[info exists commitinterest($id)]} {
            foreach script $commitinterest($id) {
-               eval [string map [list "%I" $id] $script]
+               lappend scripts [string map [list "%I" $id] $script]
            }
            unset commitinterest($id)
        }
        set gotsome 1
     }
     if {$gotsome} {
-       run chewcommits $view
+       global numcommits hlview
+
        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
+           set numcommits $commitidx($view)
+           run chewcommits
+       }
+       if {[info exists hlview] && $view == $hlview} {
+           # we never actually get here...
+           run vhighlightmore
+       }
+       foreach s $scripts {
+           eval $s
        }
     }
     return 2
 }
 
-proc chewcommits {view} {
+proc chewcommits {} {
     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 [mc "No commits selected"]
-           }
-           notbusy layout
-           set phase {}
+    global pending_select
+
+    layoutmore
+    if {$viewcomplete($curview)} {
+       global commitidx varctok
+       global numcommits startmsecs
+       global mainheadid nullid
+
+       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"
+           #puts "[llength $varctok($view)] arcs, $commitidx($view) commits"
+       } else {
+           show_status [mc "No commits selected"]
        }
-    }
-    if {[info exists hlview] && $view == $hlview} {
-       vhighlightmore
+       notbusy layout
     }
     return 0
 }
@@ -400,38 +1490,6 @@ proc readcommit {id} {
     parsecommit $id $contents 0
 }
 
-proc updatecommits {} {
-    global viewdata curview phase displayorder ordertok idpending
-    global children commitrow selectedline thickerline showneartags
-    global isworktree
-
-    set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
-
-    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
 
@@ -472,7 +1530,7 @@ proc parsecommit {id contents listed} {
        set headline [string trimright [string range $headline 0 $i]]
     }
     if {!$listed} {
-       # git rev-list indents the comment by 4 spaces;
+       # git log indents the comment by 4 spaces;
        # if we got this via git cat-file, add the indentation
        set newcomment {}
        foreach line [split $comment "\n"] {
@@ -558,10 +1616,10 @@ proc readrefs {} {
 
 # skip over fake commits
 proc first_real_row {} {
-    global nullid nullid2 displayorder numcommits
+    global nullid nullid2 numcommits
 
     for {set row 0} {$row < $numcommits} {incr row} {
-       set id [lindex $displayorder $row]
+       set id [commitonrow $row]
        if {$id ne $nullid && $id ne $nullid2} {
            break
        }
@@ -641,7 +1699,7 @@ proc setoptions {} {
 }
 
 proc makewindow {} {
-    global canv canv2 canv3 linespc charspc ctext cflist
+    global canv canv2 canv3 linespc charspc ctext cflist cscroll
     global tabstop
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
@@ -654,13 +1712,14 @@ proc makewindow {} {
     global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
     global headctxmenu progresscanv progressitem progresscoords statusw
     global fprogitem fprogcoord lastprogupdate progupdatepending
-    global rprogitem rprogcoord
+    global rprogitem rprogcoord rownumsel numcommits
     global have_tk85
 
     menu .bar
     .bar add cascade -label [mc "File"] -menu .bar.file
     menu .bar.file
     .bar.file add command -label [mc "Update"] -command updatecommits
+    .bar.file add command -label [mc "Reload"] -command reloadcommits
     .bar.file add command -label [mc "Reread references"] -command rereadrefs
     .bar.file add command -label [mc "List references"] -command showrefs
     .bar.file add command -label [mc "Quit"] -command doquit
@@ -769,6 +1828,18 @@ proc makewindow {} {
        -state disabled -width 26
     pack .tf.bar.rightbut -side left -fill y
 
+    label .tf.bar.rowlabel -text [mc "Row"]
+    set rownumsel {}
+    label .tf.bar.rownum -width 7 -font textfont -textvariable rownumsel \
+       -relief sunken -anchor e
+    label .tf.bar.rowlabel2 -text "/"
+    label .tf.bar.numcommits -width 7 -font textfont -textvariable numcommits \
+       -relief sunken -anchor e
+    pack .tf.bar.rowlabel .tf.bar.rownum .tf.bar.rowlabel2 .tf.bar.numcommits \
+       -side left
+    global selectedline
+    trace add variable selectedline write selectedline_change
+
     # Status label and progress bar
     set statusw .tf.bar.status
     label $statusw -width 15 -relief sunken
@@ -1016,7 +2087,7 @@ proc makewindow {} {
     bindkey k "selnextline 1"
     bindkey j "goback"
     bindkey l "goforw"
-    bindkey b "$ctext yview scroll -1 pages"
+    bindkey b prevfile
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
     bindkey / {dofind 1 1}
@@ -1088,6 +2159,8 @@ proc makewindow {} {
        -command {flist_hl 0}
     $flist_menu add command -label [mc "Highlight this only"] \
        -command {flist_hl 1}
+    $flist_menu add command -label [mc "External diff"] \
+        -command {external_diff}
 }
 
 # Windows sends all mouse wheel events to the current focused window, not
@@ -1108,6 +2181,17 @@ proc windows_mousewheel_redirector {W X Y D} {
     }
 }
 
+# Update row number label when selectedline changes
+proc selectedline_change {n1 n2 op} {
+    global selectedline rownumsel
+
+    if {$selectedline eq {}} {
+       set rownumsel {}
+    } else {
+       set rownumsel [expr {$selectedline + 1}]
+    }
+}
+
 # mouse-2 makes all windows scan vertically, but only the one
 # the cursor is in scans horizontally
 proc canvscan {op w x y} {
@@ -1123,7 +2207,7 @@ proc canvscan {op w x y} {
 
 proc scrollcanv {cscroll f0 f1} {
     $cscroll set $f0 $f1
-    drawfrac $f0 $f1
+    drawvisible
     flushhighlights
 }
 
@@ -1192,7 +2276,7 @@ proc savestuff {w} {
     global viewname viewfiles viewargs viewargscmd viewperm nextviewnum
     global cmitmode wrapcomment datetimeformat limitdiffs
     global colors bgcolor fgcolor diffcolors diffcontext selectbgcolor
-    global autoselect
+    global autoselect extdifftool
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -1218,6 +2302,7 @@ proc savestuff {w} {
        puts $f [list set diffcolors $diffcolors]
        puts $f [list set diffcontext $diffcontext]
        puts $f [list set selectbgcolor $selectbgcolor]
+       puts $f [list set extdifftool $extdifftool]
 
        puts $f "set geometry(main) [wm geometry .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
@@ -1318,7 +2403,7 @@ proc about {} {
     message $w.m -text [mc "
 Gitk - a commit viewer for git
 
-Copyright © 2005-2006 Paul Mackerras
+Copyright © 2005-2008 Paul Mackerras
 
 Use and redistribute under the terms of the GNU General Public License"] \
            -justify center -aspect 400 -border 2 -bg white -relief groove
@@ -1675,7 +2760,7 @@ image create bitmap reficon-o -background black -foreground "#ddddff" \
     -data $rectdata -maskdata $rectmask
 
 proc init_flist {first} {
-    global cflist cflist_top selectedline difffilestart
+    global cflist cflist_top difffilestart
 
     $cflist conf -state normal
     $cflist delete 0.0 end
@@ -1768,6 +2853,12 @@ proc pop_flist_menu {w X Y x y} {
        set e [lindex $treediffs($diffids) [expr {$l-2}]]
     }
     set flist_menu_file $e
+    set xdiffstate "normal"
+    if {$cmitmode eq "tree"} {
+       set xdiffstate "disabled"
+    }
+    # Disable "External diff" item in tree mode
+    $flist_menu entryconf 2 -state $xdiffstate
     tk_popup $flist_menu $X $Y
 }
 
@@ -1780,7 +2871,114 @@ proc flist_hl {only} {
     } else {
        append findstring " " $x
     }
-    set gdttype [mc "touching paths:"]
+    set gdttype [mc "touching paths:"]
+}
+
+proc save_file_from_commit {filename output what} {
+    global nullfile
+
+    if {[catch {exec git show $filename -- > $output} err]} {
+       if {[string match "fatal: bad revision *" $err]} {
+           return $nullfile
+       }
+       error_popup "Error getting \"$filename\" from $what: $err"
+       return {}
+    }
+    return $output
+}
+
+proc external_diff_get_one_file {diffid filename diffdir} {
+    global nullid nullid2 nullfile
+    global gitdir
+
+    if {$diffid == $nullid} {
+        set difffile [file join [file dirname $gitdir] $filename]
+       if {[file exists $difffile]} {
+           return $difffile
+       }
+       return $nullfile
+    }
+    if {$diffid == $nullid2} {
+        set difffile [file join $diffdir "\[index\] [file tail $filename]"]
+        return [save_file_from_commit :$filename $difffile index]
+    }
+    set difffile [file join $diffdir "\[$diffid\] [file tail $filename]"]
+    return [save_file_from_commit $diffid:$filename $difffile \
+              "revision $diffid"]
+}
+
+proc external_diff {} {
+    global gitktmpdir nullid nullid2
+    global flist_menu_file
+    global diffids
+    global diffnum
+    global gitdir extdifftool
+
+    if {[llength $diffids] == 1} {
+        # no reference commit given
+        set diffidto [lindex $diffids 0]
+        if {$diffidto eq $nullid} {
+            # diffing working copy with index
+            set diffidfrom $nullid2
+        } elseif {$diffidto eq $nullid2} {
+            # diffing index with HEAD
+            set diffidfrom "HEAD"
+        } else {
+            # use first parent commit
+            global parentlist selectedline
+            set diffidfrom [lindex $parentlist $selectedline 0]
+        }
+    } else {
+        set diffidfrom [lindex $diffids 0]
+        set diffidto [lindex $diffids 1]
+    }
+
+    # make sure that several diffs wont collide
+    if {![info exists gitktmpdir]} {
+       set gitktmpdir [file join [file dirname $gitdir] \
+                           [format ".gitk-tmp.%s" [pid]]]
+       if {[catch {file mkdir $gitktmpdir} err]} {
+           error_popup "Error creating temporary directory $gitktmpdir: $err"
+           unset gitktmpdir
+           return
+       }
+       set diffnum 0
+    }
+    incr diffnum
+    set diffdir [file join $gitktmpdir $diffnum]
+    if {[catch {file mkdir $diffdir} err]} {
+       error_popup "Error creating temporary directory $diffdir: $err"
+       return
+    }
+
+    # gather files to diff
+    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]
+    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
+
+    if {$difffromfile ne {} && $difftofile ne {}} {
+        set cmd [concat | [shellsplit $extdifftool] \
+                    [list $difffromfile $difftofile]]
+        if {[catch {set fl [open $cmd r]} err]} {
+            file delete -force $diffdir
+            error_popup [mc "$extdifftool: command failed: $err"]
+        } else {
+            fconfigure $fl -blocking 0
+            filerun $fl [list delete_at_eof $fl $diffdir]
+        }
+    }
+}
+
+# delete $dir when we see eof on $f (presumably because the child has exited)
+proc delete_at_eof {f dir} {
+    while {[gets $f line] >= 0} {}
+    if {[eof $f]} {
+       if {[catch {close $f} err]} {
+           error_popup "External diff viewer failed: $err"
+       }
+       file delete -force $dir
+       return 0
+    }
+    return 1
 }
 
 # Functions for adding and removing shell-type quoting
@@ -1925,7 +3123,7 @@ proc vieweditor {top n title} {
        -variable newviewperm($n)
     grid $top.perm - -pady 5 -sticky w
     message $top.al -aspect 1000 \
-       -text [mc "Commits to include (arguments to git rev-list):"]
+       -text [mc "Commits to include (arguments to git log):"]
     grid $top.al - -sticky w -pady 5
     entry $top.args -width 50 -textvariable newviewargs($n) \
        -background $bgcolor
@@ -2028,7 +3226,7 @@ proc newviewok {top n} {
            set viewargs($n) $newargs
            set viewargscmd($n) $newviewargscmd($n)
            if {$curview == $n} {
-               run updatecommits
+               run reloadcommits
            }
        }
     }
@@ -2036,7 +3234,7 @@ proc newviewok {top n} {
 }
 
 proc delview {} {
-    global curview viewdata viewperm hlview selectedhlview
+    global curview viewperm hlview selectedhlview
 
     if {$curview == 0} return
     if {[info exists hlview] && $hlview == $curview} {
@@ -2044,7 +3242,6 @@ proc delview {} {
        unset hlview
     }
     allviewmenus $curview delete
-    set viewdata($curview) {}
     set viewperm($curview) 0
     showview 0
 }
@@ -2058,48 +3255,28 @@ proc addviewmenu {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 curview cached_commitrow ordertok
     global displayorder parentlist rowidlist rowisopt rowfinal
-    global colormap rowtextx commitrow nextcolor canvxmax
-    global numcommits commitlisted
+    global colormap rowtextx nextcolor canvxmax
+    global numcommits viewcomplete
     global selectedline currentid canv canvy0
     global treediffs
-    global pending_select phase
+    global pending_select mainheadid
     global commitidx
-    global commfd
-    global selectedview selectfirst
-    global vparentlist vdisporder vcmitlisted
+    global selectedview
     global hlview selectedhlview commitinterest
 
     if {$n == $curview} return
     set selid {}
-    if {[info exists 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}]
+    set yscreen [expr {($ybot - $ytop) / 2}]
+    if {$selectedline ne {}} {
        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}]
        }
@@ -2109,17 +3286,6 @@ proc showview {n} {
     }
     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} {
@@ -2127,6 +3293,8 @@ proc showview {n} {
        set selectedhlview [mc "None"]
     }
     catch {unset commitinterest}
+    catch {unset cached_commitrow}
+    catch {unset ordertok}
 
     set curview $n
     set selectedview $n
@@ -2134,7 +3302,7 @@ proc showview {n} {
     .bar.view entryconf [mc "Delete view"] -state [expr {$n == 0? "disabled": "normal"}]
 
     run refill_reflist
-    if {![info exists viewdata($n)]} {
+    if {![info exists viewcomplete($n)]} {
        if {$selid ne {}} {
            set pending_select $selid
        }
@@ -2142,14 +3310,11 @@ proc showview {n} {
        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 displayorder {}
+    set parentlist {}
+    set rowidlist {}
+    set rowisopt {}
+    set rowfinal {}
     set numcommits $commitidx($n)
 
     catch {unset colormap}
@@ -2161,9 +3326,8 @@ proc showview {n} {
     setcanvscroll
     set yf 0
     set row {}
-    set selectfirst 0
-    if {[info exists yscreen] && [info exists commitrow($n,$selid)]} {
-       set row $commitrow($n,$selid)
+    if {$selid ne {} && [commitinview $selid $n]} {
+       set row [rowofcommit $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}]
@@ -2176,21 +3340,24 @@ proc showview {n} {
     drawvisible
     if {$row ne {}} {
        selectline $row 0
-    } elseif {$selid ne {}} {
-       set pending_select $selid
+    } elseif {$mainheadid ne {} && [commitinview $mainheadid $curview]} {
+       selectline [rowofcommit $mainheadid] 1
+    } elseif {!$viewcomplete($n)} {
+       if {$selid ne {}} {
+           set pending_select $selid
+       } else {
+           set pending_select $mainheadid
+       }
     } else {
        set row [first_real_row]
        if {$row < $numcommits} {
            selectline $row 0
-       } else {
-           set selectfirst 1
        }
     }
-    if {$phase ne {}} {
-       if {$phase eq "getcommits"} {
+    if {!$viewcomplete($n)} {
+       if {$numcommits == 0} {
            show_status [mc "Reading commits..."]
        }
-       run chewcommits $n
     } elseif {$numcommits == 0} {
        show_status [mc "No commits selected"]
     }
@@ -2198,20 +3365,20 @@ proc showview {n} {
 
 # Stuff relating to the highlighting facility
 
-proc ishighlighted {row} {
+proc ishighlighted {id} {
     global vhighlights fhighlights nhighlights rhighlights
 
-    if {[info exists nhighlights($row)] && $nhighlights($row) > 0} {
-       return $nhighlights($row)
+    if {[info exists nhighlights($id)] && $nhighlights($id) > 0} {
+       return $nhighlights($id)
     }
-    if {[info exists vhighlights($row)] && $vhighlights($row) > 0} {
-       return $vhighlights($row)
+    if {[info exists vhighlights($id)] && $vhighlights($id) > 0} {
+       return $vhighlights($id)
     }
-    if {[info exists fhighlights($row)] && $fhighlights($row) > 0} {
-       return $fhighlights($row)
+    if {[info exists fhighlights($id)] && $fhighlights($id) > 0} {
+       return $fhighlights($id)
     }
-    if {[info exists rhighlights($row)] && $rhighlights($row) > 0} {
-       return $rhighlights($row)
+    if {[info exists rhighlights($id)] && $rhighlights($id) > 0} {
+       return $rhighlights($id)
     }
     return 0
 }
@@ -2221,7 +3388,7 @@ proc bolden {row font} {
 
     lappend boldrows $row
     $canv itemconf $linehtag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv delete secsel
        set t [eval $canv create rect [$canv bbox $linehtag($row)] \
                   -outline {{}} -tags secsel \
@@ -2235,7 +3402,7 @@ proc bolden_name {row font} {
 
     lappend boldnamerows $row
     $canv2 itemconf $linentag($row) -font $font
-    if {[info exists selectedline] && $row == $selectedline} {
+    if {$row == $selectedline} {
        $canv2 delete secsel
        set t [eval $canv2 create rect [$canv2 bbox $linentag($row)] \
                   -outline {{}} -tags secsel \
@@ -2249,7 +3416,7 @@ proc unbolden {} {
 
     set stillbold {}
     foreach row $boldrows {
-       if {![ishighlighted $row]} {
+       if {![ishighlighted [commitonrow $row]]} {
            bolden $row mainfont
        } else {
            lappend stillbold $row
@@ -2259,17 +3426,13 @@ proc unbolden {} {
 }
 
 proc addvhighlight {n} {
-    global hlview curview viewdata vhl_done vhighlights commitidx
+    global hlview viewcomplete curview vhl_done 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) {}
+    if {$n != $curview && ![info exists viewcomplete($n)]} {
        start_rev_list $n
     }
     set vhl_done $commitidx($hlview)
@@ -2288,43 +3451,38 @@ proc delvhighlight {} {
 }
 
 proc vhighlightmore {} {
-    global hlview vhl_done commitidx vhighlights
-    global displayorder vdisporder curview
+    global hlview vhl_done commitidx vhighlights 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)
+       set id [commitonrow $i $hlview]
+       if {[commitinview $id $curview]} {
+           set row [rowofcommit $id]
            if {$r0 <= $row && $row <= $r1} {
                if {![highlighted $row]} {
                    bolden $row mainfontbold
                }
-               set vhighlights($row) 1
+               set vhighlights($id) 1
            }
        }
     }
     set vhl_done $max
+    return 0
 }
 
 proc askvhighlight {row id} {
-    global hlview vhighlights commitrow iddrawn
+    global hlview vhighlights iddrawn
 
-    if {[info exists commitrow($hlview,$id)]} {
-       if {[info exists iddrawn($id)] && ![ishighlighted $row]} {
+    if {[commitinview $id $hlview]} {
+       if {[info exists iddrawn($id)] && ![ishighlighted $id]} {
            bolden $row mainfontbold
        }
-       set vhighlights($row) 1
+       set vhighlights($id) 1
     } else {
-       set vhighlights($row) 0
+       set vhighlights($id) 0
     }
 }
 
@@ -2462,12 +3620,12 @@ proc askfilehighlight {row id} {
     global filehighlight fhighlights fhl_list
 
     lappend fhl_list $id
-    set fhighlights($row) -1
+    set fhighlights($id) -1
     puts $filehighlight $id
 }
 
 proc readfhighlight {} {
-    global filehighlight fhighlights commitrow curview iddrawn
+    global filehighlight fhighlights curview iddrawn
     global fhl_list find_dirn
 
     if {![info exists filehighlight]} {
@@ -2480,18 +3638,16 @@ proc readfhighlight {} {
        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 fhighlights($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]} {
+       if {![commitinview $line $curview]} continue
+       set row [rowofcommit $line]
+       if {[info exists iddrawn($line)] && ![ishighlighted $line]} {
            bolden $row mainfontbold
        }
-       set fhighlights($row) 1
+       set fhighlights($line) 1
     }
     if {[eof $filehighlight]} {
        # strange...
@@ -2540,7 +3696,7 @@ proc askfindhighlight {row id} {
        }
     }
     if {$isbold && [info exists iddrawn($id)]} {
-       if {![ishighlighted $row]} {
+       if {![ishighlighted $id]} {
            bolden $row mainfontbold
            if {$isbold > 1} {
                bolden_name $row mainfontbold
@@ -2550,7 +3706,7 @@ proc askfindhighlight {row id} {
            markrowmatches $row $id
        }
     }
-    set nhighlights($row) $isbold
+    set nhighlights($id) $isbold
 }
 
 proc markrowmatches {row id} {
@@ -2588,7 +3744,7 @@ proc vrel_change {name ix op} {
 # 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
+    global highlight_related
 
     catch {unset descendent}
     set desc_todo [list $a]
@@ -2608,16 +3764,16 @@ proc rhighlight_none {} {
 }
 
 proc is_descendent {a} {
-    global curview children commitrow descendent desc_todo
+    global curview children descendent desc_todo
 
     set v $curview
-    set la $commitrow($v,$a)
+    set la [rowofcommit $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} {
+       if {[rowofcommit $do] < $la} {
            lappend leftover $do
            continue
        }
@@ -2640,20 +3796,20 @@ proc is_descendent {a} {
 }
 
 proc is_ancestor {a} {
-    global curview parentlist commitrow ancestor anc_todo
+    global curview parents ancestor anc_todo
 
     set v $curview
-    set la $commitrow($v,$a)
+    set la [rowofcommit $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} {
+       if {![commitinview $do $v] || [rowofcommit $do] > $la} {
            lappend leftover $do
            continue
        }
-       foreach np [lindex $parentlist $commitrow($v,$do)] {
+       foreach np $parents($v,$do) {
            if {![info exists ancestor($np)]} {
                set ancestor($np) 1
                lappend todo $np
@@ -2675,7 +3831,7 @@ proc askrelhighlight {row id} {
     global descendent highlight_related iddrawn rhighlights
     global selectedline ancestor
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set isbold 0
     if {$highlight_related eq [mc "Descendant"] ||
        $highlight_related eq [mc "Not descendant"]} {
@@ -2695,11 +3851,11 @@ proc askrelhighlight {row id} {
        }
     }
     if {[info exists iddrawn($id)]} {
-       if {$isbold && ![ishighlighted $row]} {
+       if {$isbold && ![ishighlighted $id]} {
            bolden $row mainfontbold
        }
     }
-    set rhighlights($row) $isbold
+    set rhighlights($id) $isbold
 }
 
 # Graph layout functions
@@ -2730,40 +3886,81 @@ proc ntimes {n o} {
     return $ret
 }
 
+proc ordertoken {id} {
+    global ordertok curview varcid varcstart varctok curview parents children
+    global nullid nullid2
+
+    if {[info exists ordertok($id)]} {
+       return $ordertok($id)
+    }
+    set origid $id
+    set todo {}
+    while {1} {
+       if {[info exists varcid($curview,$id)]} {
+           set a $varcid($curview,$id)
+           set p [lindex $varcstart($curview) $a]
+       } else {
+           set p [lindex $children($curview,$id) 0]
+       }
+       if {[info exists ordertok($p)]} {
+           set tok $ordertok($p)
+           break
+       }
+       set id [first_real_child $curview,$p]
+       if {$id eq {}} {
+           # it's a root
+           set tok [lindex $varctok($curview) $varcid($curview,$p)]
+           break
+       }
+       if {[llength $parents($curview,$id)] == 1} {
+           lappend todo [list $p {}]
+       } else {
+           set j [lsearch -exact $parents($curview,$id) $p]
+           if {$j < 0} {
+               puts "oops didn't find [shortids $p] in parents of [shortids $id]"
+           }
+           lappend todo [list $p [strrep $j]]
+       }
+    }
+    for {set i [llength $todo]} {[incr i -1] >= 0} {} {
+       set p [lindex $todo $i 0]
+       append tok [lindex $todo $i 1]
+       set ordertok($p) $tok
+    }
+    set ordertok($origid) $tok
+    return $tok
+}
+
 # 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])} {
+    set t [ordertoken $id]
+    if {$i < 0} {
+       set i 0
+    }
+    if {$i >= [llength $idlist] || $t < [ordertoken [lindex $idlist $i]]} {
        if {$i > [llength $idlist]} {
            set i [llength $idlist]
        }
-       while {[incr i -1] >= 0 &&
-              $t < $ordertok($curview,[lindex $idlist $i])} {}
+       while {[incr i -1] >= 0 && $t < [ordertoken [lindex $idlist $i]]} {}
        incr i
     } else {
-       if {$t > $ordertok($curview,[lindex $idlist $i])} {
+       if {$t > [ordertoken [lindex $idlist $i]]} {
            while {[incr i] < [llength $idlist] &&
-                  $t >= $ordertok($curview,[lindex $idlist $i])} {}
+                  $t >= [ordertoken [lindex $idlist $i]]} {}
        }
     }
     return $i
 }
 
 proc initlayout {} {
-    global rowidlist rowisopt rowfinal displayorder commitlisted
+    global rowidlist rowisopt rowfinal displayorder parentlist
     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 {}
@@ -2772,16 +3969,19 @@ proc initlayout {} {
     set canvxmax [$canv cget -width]
     catch {unset colormap}
     catch {unset rowtextx}
-    set selectfirst 1
+    setcanvscroll
 }
 
 proc setcanvscroll {} {
     global canv canv2 canv3 numcommits linespc canvxmax canvy0
+    global lastscrollset lastscrollrows
 
     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]
+    set lastscrollset [clock clicks -milliseconds]
+    set lastscrollrows $numcommits
 }
 
 proc visiblerows {} {
@@ -2804,102 +4004,57 @@ proc visiblerows {} {
 }
 
 proc layoutmore {} {
-    global commitidx viewcomplete numcommits
-    global uparrowlen downarrowlen mingaplen curview
-
-    set show $commitidx($curview)
-    if {$show > $numcommits || $viewcomplete($curview)} {
-       showstuff $show $viewcomplete($curview)
-    }
-}
+    global commitidx viewcomplete curview
+    global numcommits pending_select curview
+    global lastscrollset lastscrollrows commitinterest
 
-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
+    if {$lastscrollrows < 100 || $viewcomplete($curview) ||
+       [clock clicks -milliseconds] - $lastscrollset > 500} {
        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
-       }
+       [commitinview $pending_select $curview]} {
+       selectline [rowofcommit $pending_select] 1
     }
+    drawvisible
 }
 
 proc doshowlocalchanges {} {
-    global curview mainheadid phase commitrow
+    global curview mainheadid
 
-    if {[info exists commitrow($curview,$mainheadid)] &&
-       ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+    if {[commitinview $mainheadid $curview]} {
        dodiffindex
-    } elseif {$phase ne {}} {
-       lappend commitinterest($mainheadid) {}
+    } else {
+       lappend commitinterest($mainheadid) {dodiffindex}
     }
 }
 
 proc dohidelocalchanges {} {
-    global localfrow localirow lserial
+    global nullid nullid2 lserial curview
 
-    if {$localfrow >= 0} {
-       removerow $localfrow
-       set localfrow -1
-       if {$localirow > 0} {
-           incr localirow -1
-       }
+    if {[commitinview $nullid $curview]} {
+       removefakerow $nullid
     }
-    if {$localirow >= 0} {
-       removerow $localirow
-       set localirow -1
+    if {[commitinview $nullid2 $curview]} {
+       removefakerow $nullid2
     }
     incr lserial
 }
 
 # spawn off a process to do git diff-index --cached HEAD
 proc dodiffindex {} {
-    global localirow localfrow lserial showlocalchanges
+    global lserial showlocalchanges
     global isworktree
 
     if {!$showlocalchanges || !$isworktree} 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
+    global mainheadid nullid nullid2 curview commitinfo commitdata lserial
 
     set isdiff 1
     if {[gets $fd line] < 0} {
@@ -2911,26 +4066,32 @@ proc readdiffindex {fd serial} {
     # 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 {$serial != $lserial} {
+       return 0
     }
 
-    if {$isdiff && $serial == $lserial && $localirow == -1} {
+    # now see if there are any local changes not checked in to the index
+    set fd [open "|git diff-files" r]
+    fconfigure $fd -blocking 0
+    filerun $fd [list readdifffiles $fd $serial]
+
+    if {$isdiff && ![commitinview $nullid2 $curview]} {
        # add the line for the changes in the index to the graph
-       set localirow $commitrow($curview,$mainheadid)
        set hl [mc "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
+       if {[commitinview $nullid $curview]} {
+           removefakerow $nullid
+       }
+       insertfakerow $nullid2 $mainheadid
+    } elseif {!$isdiff && [commitinview $nullid2 $curview]} {
+       removefakerow $nullid2
     }
     return 0
 }
 
 proc readdifffiles {fd serial} {
-    global localirow localfrow commitrow mainheadid nullid curview
+    global mainheadid nullid nullid2 curview
     global commitinfo commitdata lserial
 
     set isdiff 1
@@ -2943,50 +4104,55 @@ proc readdifffiles {fd serial} {
     # we only need to see one line and we don't really care what it says...
     close $fd
 
-    if {$isdiff && $serial == $lserial && $localfrow == -1} {
+    if {$serial != $lserial} {
+       return 0
+    }
+
+    if {$isdiff && ![commitinview $nullid $curview]} {
        # 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 [mc "Local uncommitted changes, not checked in to index"]
        set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
        set commitdata($nullid) "\n    $hl\n"
-       insertrow $localfrow $nullid
+       if {[commitinview $nullid2 $curview]} {
+           set p $nullid2
+       } else {
+           set p $mainheadid
+       }
+       insertfakerow $nullid $p
+    } elseif {!$isdiff && [commitinview $nullid $curview]} {
+       removefakerow $nullid
     }
     return 0
 }
 
 proc nextuse {id row} {
-    global commitrow curview children
+    global curview children
 
     if {[info exists children($curview,$id)]} {
        foreach kid $children($curview,$id) {
-           if {![info exists commitrow($curview,$kid)]} {
+           if {![commitinview $kid $curview]} {
                return -1
            }
-           if {$commitrow($curview,$kid) > $row} {
-               return $commitrow($curview,$kid)
+           if {[rowofcommit $kid] > $row} {
+               return [rowofcommit $kid]
            }
        }
     }
-    if {[info exists commitrow($curview,$id)]} {
-       return $commitrow($curview,$id)
+    if {[commitinview $id $curview]} {
+       return [rowofcommit $id]
     }
     return -1
 }
 
 proc prevuse {id row} {
-    global commitrow curview children
+    global 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)
+           if {![commitinview $kid $curview]} break
+           if {[rowofcommit $kid] < $row} {
+               set ret [rowofcommit $kid]
            }
        }
     }
@@ -2995,7 +4161,7 @@ proc prevuse {id row} {
 
 proc make_idlist {row} {
     global displayorder parentlist uparrowlen downarrowlen mingaplen
-    global commitidx curview ordertok children commitrow
+    global commitidx curview children
 
     set r [expr {$row - $mingaplen - $downarrowlen - 1}]
     if {$r < 0} {
@@ -3009,6 +4175,7 @@ proc make_idlist {row} {
     if {$rb > $commitidx($curview)} {
        set rb $commitidx($curview)
     }
+    make_disporder $r [expr {$rb + 1}]
     set ids {}
     for {} {$r < $ra} {incr r} {
        set nextid [lindex $displayorder [expr {$r + 1}]]
@@ -3017,7 +4184,7 @@ proc make_idlist {row} {
            set rn [nextuse $p $r]
            if {$rn >= $row &&
                $rn <= $r + $downarrowlen + $mingaplen + $uparrowlen} {
-               lappend ids [list $ordertok($curview,$p) $p]
+               lappend ids [list [ordertoken $p] $p]
            }
        }
     }
@@ -3027,25 +4194,25 @@ proc make_idlist {row} {
            if {$p eq $nextid} continue
            set rn [nextuse $p $r]
            if {$rn < 0 || $rn >= $row} {
-               lappend ids [list $ordertok($curview,$p) $p]
+               lappend ids [list [ordertoken $p] $p]
            }
        }
     }
     set id [lindex $displayorder $row]
-    lappend ids [list $ordertok($curview,$id) $id]
+    lappend ids [list [ordertoken $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]
+           if {[rowofcommit $firstkid] < $row} {
+               lappend ids [list [ordertoken $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]
+           if {$firstkid ne {} && [rowofcommit $firstkid] < $row} {
+               lappend ids [list [ordertoken $id] $id]
            }
        }
     }
@@ -3091,8 +4258,9 @@ proc layoutrows {row endrow} {
     global rowidlist rowisopt rowfinal displayorder
     global uparrowlen downarrowlen maxwidth mingaplen
     global children parentlist
-    global commitidx viewcomplete curview commitrow
+    global commitidx viewcomplete curview
 
+    make_disporder [expr {$row - 1}] [expr {$endrow + $uparrowlen}]
     set idlist {}
     if {$row > 0} {
        set rm1 [expr {$row - 1}]
@@ -3148,7 +4316,7 @@ proc layoutrows {row endrow} {
                foreach p [lindex $parentlist $r] {
                    if {[lsearch -exact $idlist $p] >= 0} continue
                    set fk [lindex $children($curview,$p) 0]
-                   if {$commitrow($curview,$fk) < $row} {
+                   if {[rowofcommit $fk] < $row} {
                        set x [idcol $idlist $p $x]
                        set idlist [linsert $idlist $x $p]
                    }
@@ -3157,7 +4325,7 @@ proc layoutrows {row endrow} {
                    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} {
+                       if {$fk ne {} && [rowofcommit $fk] < $row} {
                            set x [idcol $idlist $p $x]
                            set idlist [linsert $idlist $x $p]
                        }
@@ -3372,7 +4540,7 @@ proc linewidth {id} {
 }
 
 proc rowranges {id} {
-    global commitrow curview children uparrowlen downarrowlen
+    global curview children uparrowlen downarrowlen
     global rowidlist
 
     set kids $children($curview,$id)
@@ -3382,13 +4550,13 @@ proc rowranges {id} {
     set ret {}
     lappend kids $id
     foreach child $kids {
-       if {![info exists commitrow($curview,$child)]} break
-       set row $commitrow($curview,$child)
+       if {![commitinview $child $curview]} break
+       set row [rowofcommit $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"
+               puts "oops children of [shortids $id] out of order [shortids $child] $row <= [shortids $prev] $prevrow"
            }
            # see if the line extends the whole way from prevrow to row
            if {$row > $prevrow + $uparrowlen + $downarrowlen &&
@@ -3421,7 +4589,7 @@ proc rowranges {id} {
        if {$child eq $id} {
            lappend ret $row
        }
-       set prev $id
+       set prev $child
        set prevrow $row
     }
     return $ret
@@ -3669,14 +4837,14 @@ proc drawlines {id} {
 }
 
 proc drawcmittext {id row col} {
-    global linespc canv canv2 canv3 canvy0 fgcolor curview
-    global commitlisted commitinfo rowidlist parentlist
+    global linespc canv canv2 canv3 fgcolor curview
+    global cmitlisted 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 negative, 3 for left, 4 for right
-    set listed [lindex $commitlisted $row]
+    set listed $cmitlisted($curview,$id)
     if {$id eq $nullid} {
        set ofill red
     } elseif {$id eq $nullid2} {
@@ -3732,7 +4900,7 @@ proc drawcmittext {id row col} {
     set date [formatdate $date]
     set font mainfont
     set nfont mainfont
-    set isbold [ishighlighted $row]
+    set isbold [ishighlighted $id]
     if {$isbold > 0} {
        lappend boldrows $row
        set font mainfontbold
@@ -3748,7 +4916,7 @@ proc drawcmittext {id row col} {
                            -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} {
+    if {$selectedline == $row} {
        make_secsel $row
     }
     set xr [expr {$xt + [font measure $font $headline]}]
@@ -3761,7 +4929,7 @@ proc drawcmittext {id row col} {
 proc drawcmitrow {row} {
     global displayorder rowidlist nrows_drawn
     global iddrawn markingmatches
-    global commitinfo parentlist numcommits
+    global commitinfo numcommits
     global filehighlight fhighlights findpattern nhighlights
     global hlview vhighlights
     global highlight_related rhighlights
@@ -3769,16 +4937,16 @@ proc drawcmitrow {row} {
     if {$row >= $numcommits} return
 
     set id [lindex $displayorder $row]
-    if {[info exists hlview] && ![info exists vhighlights($row)]} {
+    if {[info exists hlview] && ![info exists vhighlights($id)]} {
        askvhighlight $row $id
     }
-    if {[info exists filehighlight] && ![info exists fhighlights($row)]} {
+    if {[info exists filehighlight] && ![info exists fhighlights($id)]} {
        askfilehighlight $row $id
     }
-    if {$findpattern ne {} && ![info exists nhighlights($row)]} {
+    if {$findpattern ne {} && ![info exists nhighlights($id)]} {
        askfindhighlight $row $id
     }
-    if {$highlight_related ne [mc "None"] && ![info exists rhighlights($row)]} {
+    if {$highlight_related ne [mc "None"] && ![info exists rhighlights($id)]} {
        askrelhighlight $row $id
     }
     if {![info exists iddrawn($id)]} {
@@ -3881,30 +5049,92 @@ proc drawcommits {row {endrow {}}} {
     }
 }
 
-proc drawfrac {f0 f1} {
-    global canv linespc
+proc undolayout {row} {
+    global uparrowlen mingaplen downarrowlen
+    global rowidlist rowisopt rowfinal need_redisplay
+
+    set r [expr {$row - ($uparrowlen + $mingaplen + $downarrowlen)}]
+    if {$r < 0} {
+       set r 0
+    }
+    if {[llength $rowidlist] > $r} {
+       incr r -1
+       set rowidlist [lrange $rowidlist 0 $r]
+       set rowfinal [lrange $rowfinal 0 $r]
+       set rowisopt [lrange $rowisopt 0 $r]
+       set need_redisplay 1
+       run drawvisible
+    }
+}
+
+proc drawvisible {} {
+    global canv linespc curview vrowmod selectedline targetrow targetid
+    global need_redisplay cscroll numcommits
 
+    set fs [$canv yview]
     set ymax [lindex [$canv cget -scrollregion] 3]
-    if {$ymax eq {} || $ymax == 0} return
+    if {$ymax eq {} || $ymax == 0 || $numcommits == 0} return
+    set f0 [lindex $fs 0]
+    set f1 [lindex $fs 1]
     set y0 [expr {int($f0 * $ymax)}]
-    set row [expr {int(($y0 - 3) / $linespc) - 1}]
     set y1 [expr {int($f1 * $ymax)}]
+
+    if {[info exists targetid]} {
+       if {[commitinview $targetid $curview]} {
+           set r [rowofcommit $targetid]
+           if {$r != $targetrow} {
+               # Fix up the scrollregion and change the scrolling position
+               # now that our target row has moved.
+               set diff [expr {($r - $targetrow) * $linespc}]
+               set targetrow $r
+               setcanvscroll
+               set ymax [lindex [$canv cget -scrollregion] 3]
+               incr y0 $diff
+               incr y1 $diff
+               set f0 [expr {$y0 / $ymax}]
+               set f1 [expr {$y1 / $ymax}]
+               allcanvs yview moveto $f0
+               $cscroll set $f0 $f1
+               set need_redisplay 1
+           }
+       } else {
+           unset targetid
+       }
+    }
+
+    set row [expr {int(($y0 - 3) / $linespc) - 1}]
     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
+    if {$endrow >= $vrowmod($curview)} {
+       update_arcrows $curview
+    }
+    if {$selectedline ne {} &&
+       $row <= $selectedline && $selectedline <= $endrow} {
+       set targetrow $selectedline
+    } elseif {[info exists targetid]} {
+       set targetrow [expr {int(($row + $endrow) / 2)}]
+    }
+    if {[info exists targetrow]} {
+       if {$targetrow >= $numcommits} {
+           set targetrow [expr {$numcommits - 1}]
+       }
+       set targetid [commitonrow $targetrow]
+    }
     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
+    global linehtag linentag linedtag boldrows boldnamerows
 
     allcanvs delete all
     catch {unset iddrawn}
     catch {unset linesegs}
+    catch {unset linehtag}
+    catch {unset linentag}
+    catch {unset linedtag}
+    set boldrows {}
+    set boldnamerows {}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
@@ -3950,7 +5180,7 @@ proc findcrossings {id} {
 
 proc assigncolor {id} {
     global colormap colors nextcolor
-    global commitrow parentlist children children curview
+    global parents children children curview
 
     if {[info exists colormap($id)]} return
     set ncolors [llength $colors]
@@ -3962,7 +5192,7 @@ proc assigncolor {id} {
     if {[llength $kids] == 1} {
        set child [lindex $kids 0]
        if {[info exists colormap($child)]
-           && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
+           && [llength $parents($curview,$child)] == 1} {
            set colormap($id) $colormap($child)
            return
        }
@@ -3990,7 +5220,7 @@ proc assigncolor {id} {
                && [lsearch -exact $badcolors $colormap($child)] < 0} {
                lappend badcolors $colormap($child)
            }
-           foreach p [lindex $parentlist $commitrow($curview,$child)] {
+           foreach p $parents($curview,$child) {
                if {[info exists colormap($p)]
                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
                    lappend badcolors $colormap($p)
@@ -4023,7 +5253,7 @@ proc bindline {t id} {
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs mainhead
     global linespc lthickness
-    global canv commitrow rowtextx curview fgcolor bgcolor
+    global canv rowtextx curview fgcolor bgcolor
 
     set marks {}
     set ntags 0
@@ -4073,7 +5303,7 @@ proc drawtags {id x xt y1} {
                       $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}]
+           set rowtextx([rowofcommit $id]) [expr {$xr + $linespc}]
        } else {
            # draw a head or other ref
            if {[incr nheads -1] >= 0} {
@@ -4127,103 +5357,6 @@ proc show_status {msg} {
        -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} {
@@ -4296,7 +5429,7 @@ proc dofind {{dirn 1} {wrap 1}} {
     }
     focus .
     if {$findstring eq {} || $numcommits == 0} return
-    if {![info exists selectedline]} {
+    if {$selectedline eq {}} {
        set findstartline [lindex [visiblerows] [expr {$dirn < 0}]]
     } else {
        set findstartline $selectedline
@@ -4326,9 +5459,9 @@ proc stopfinding {} {
 
 proc findmore {} {
     global commitdata commitinfo numcommits findpattern findloc
-    global findstartline findcurline displayorder
+    global findstartline findcurline findallowwrap
     global find_dirn gdttype fhighlights fprogcoord
-    global findallowwrap
+    global curview varcorder vrownum varccommits vrowmod
 
     if {![info exists find_dirn]} {
        return 0
@@ -4364,14 +5497,31 @@ proc findmore {} {
        set n 500
        set moretodo 1
     }
+    if {$l + ($find_dirn > 0? $n: 1) > $vrowmod($curview)} {
+       update_arcrows $curview
+    }
     set found 0
     set domore 1
+    set ai [bsearch $vrownum($curview) $l]
+    set a [lindex $varcorder($curview) $ai]
+    set arow [lindex $vrownum($curview) $ai]
+    set ids [lindex $varccommits($curview,$a)]
+    set arowend [expr {$arow + [llength $ids]}]
     if {$gdttype eq [mc "containing:"]} {
        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
-           set id [lindex $displayorder $l]
+           if {$l < $arow || $l >= $arowend} {
+               incr ai $find_dirn
+               set a [lindex $varcorder($curview) $ai]
+               set arow [lindex $vrownum($curview) $ai]
+               set ids [lindex $varccommits($curview,$a)]
+               set arowend [expr {$arow + [llength $ids]}]
+           }
+           set id [lindex $ids [expr {$l - $arow}]]
            # 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 commitdata($id)] ||
+               ![doesmatch $commitdata($id)]} {
+               continue
+           }
            if {![info exists commitinfo($id)]} {
                getcommit $id
            }
@@ -4387,16 +5537,27 @@ proc findmore {} {
        }
     } else {
        for {} {$n > 0} {incr n -1; incr l $find_dirn} {
-           set id [lindex $displayorder $l]
-           if {![info exists fhighlights($l)]} {
+           if {$l < $arow || $l >= $arowend} {
+               incr ai $find_dirn
+               set a [lindex $varcorder($curview) $ai]
+               set arow [lindex $vrownum($curview) $ai]
+               set ids [lindex $varccommits($curview,$a)]
+               set arowend [expr {$arow + [llength $ids]}]
+           }
+           set id [lindex $ids [expr {$l - $arow}]]
+           if {![info exists fhighlights($id)]} {
+               # this sets fhighlights($id) to -1
                askfilehighlight $l $id
+           }
+           if {$fhighlights($id) > 0} {
+               set found $domore
+               break
+           }
+           if {$fhighlights($id) < 0} {
                if {$domore} {
                    set domore 0
                    set findcurline [expr {$l - $find_dirn}]
                }
-           } elseif {$fhighlights($l)} {
-               set found $domore
-               break
            }
        }
     }
@@ -4464,7 +5625,7 @@ proc markmatches {canv l str tag matches font row} {
                   [expr {$x0+$xlen+2}] $y1 \
                   -outline {} -tags [list match$l matches] -fill yellow]
        $canv lower $t
-       if {[info exists selectedline] && $row == $selectedline} {
+       if {$row == $selectedline} {
            $canv raise $t secsel
        }
     }
@@ -4490,7 +5651,9 @@ proc selcanvline {w x y} {
        set l 0
     }
     if {$w eq $canv} {
-       if {![info exists rowtextx($l)] || $x < $rowtextx($l)} return
+       set xmax [lindex [$canv cget -scrollregion] 2]
+       set xleft [expr {[lindex [$canv xview] 0] * $xmax}]
+       if {![info exists rowtextx($l)] || $xleft + $x < $rowtextx($l)} return
     }
     unmarkmatches
     selectline $l 1
@@ -4511,7 +5674,7 @@ proc commit_descriptor {p} {
 # append some text to the ctext widget, and make any SHA1 ID
 # that we know about be a clickable link.
 proc appendwithlinks {text tags} {
-    global ctext commitrow linknum curview pendinglinks
+    global ctext linknum curview pendinglinks
 
     set start [$ctext index "end - 1c"]
     $ctext insert end $text $tags
@@ -4529,11 +5692,11 @@ proc appendwithlinks {text tags} {
 }
 
 proc setlink {id lk} {
-    global curview commitrow ctext pendinglinks commitinterest
+    global curview ctext pendinglinks commitinterest
 
-    if {[info exists commitrow($curview,$id)]} {
+    if {[commitinview $id $curview]} {
        $ctext tag conf $lk -foreground blue -underline 1
-       $ctext tag bind $lk <1> [list selectline $commitrow($curview,$id) 1]
+       $ctext tag bind $lk <1> [list selectline [rowofcommit $id] 1]
        $ctext tag bind $lk <Enter> {linkcursor %W 1}
        $ctext tag bind $lk <Leave> {linkcursor %W -1}
     } else {
@@ -4584,7 +5747,7 @@ proc viewnextline {dir} {
 # 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
+    global ctext linknum curview $var maxrefs
 
     if {[catch {$ctext index $pos}]} {
        return 0
@@ -4621,7 +5784,7 @@ proc appendrefs {pos ids var} {
 proc dispneartags {delay} {
     global selectedline currentid showneartags tagphase
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     after cancel dispnexttag
     if {$delay} {
        after 200 dispnexttag
@@ -4635,7 +5798,7 @@ proc dispneartags {delay} {
 proc dispnexttag {} {
     global selectedline currentid showneartags tagphase ctext
 
-    if {![info exists selectedline] || !$showneartags} return
+    if {$selectedline eq {} || !$showneartags} return
     switch -- $tagphase {
        0 {
            set dtags [desctags $currentid]
@@ -4687,12 +5850,12 @@ proc make_secsel {l} {
 
 proc selectline {l isnew} {
     global canv ctext commitinfo selectedline
-    global displayorder
-    global canvy0 linespc parentlist children curview
+    global canvy0 linespc parents children curview
     global currentid sha1entry
     global commentend idtags linknum
     global mergemax numcommits pending_select
     global cmitmode showneartags allcommits
+    global targetrow targetid lastscrollrows
     global autoselect
 
     catch {unset pending_select}
@@ -4701,6 +5864,15 @@ proc selectline {l isnew} {
     unsel_reflist
     stopfinding
     if {$l < 0 || $l >= $numcommits} return
+    set id [commitonrow $l]
+    set targetid $id
+    set targetrow $l
+    set selectedline $l
+    set currentid $id
+    if {$lastscrollrows < $numcommits} {
+       setcanvscroll
+    }
+
     set y [expr {$canvy0 + $l * $linespc}]
     set ymax [lindex [$canv cget -scrollregion] 3]
     set ytop [expr {$y - $linespc - 1}]
@@ -4740,13 +5912,9 @@ proc selectline {l isnew} {
     make_secsel $l
 
     if {$isnew} {
-       addtohistory [list selectline $l 0]
+       addtohistory [list selbyid $id]
     }
 
-    set selectedline $l
-
-    set id [lindex $displayorder $l]
-    set currentid $id
     $sha1entry delete 0 end
     $sha1entry insert 0 $id
     if {$autoselect} {
@@ -4758,6 +5926,9 @@ proc selectline {l isnew} {
     $ctext conf -state normal
     clear_ctext
     set linknum 0
+    if {![info exists commitinfo($id)]} {
+       getcommit $id
+    }
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
     $ctext insert end "[mc "Author"]: [lindex $info 1]  $date\n"
@@ -4772,7 +5943,7 @@ proc selectline {l isnew} {
     }
 
     set headers {}
-    set olds [lindex $parentlist $l]
+    set olds $parents($curview,$id)
     if {[llength $olds] > 1} {
        set np 0
        foreach p $olds {
@@ -4830,7 +6001,7 @@ proc selectline {l isnew} {
     } elseif {[llength $olds] <= 1} {
        startdiff $id
     } else {
-       mergediff $id $l
+       mergediff $id
     }
 }
 
@@ -4849,7 +6020,7 @@ proc sellastline {} {
 proc selnextline {dir} {
     global selectedline
     focus .
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir}]
     unmarkmatches
     selectline $l 1
@@ -4864,7 +6035,7 @@ proc selnextpage {dir} {
     }
     allcanvs yview scroll [expr {$dir * $lpp}] units
     drawvisible
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     set l [expr {$selectedline + $dir * $lpp}]
     if {$l < 0} {
        set l 0
@@ -4878,7 +6049,7 @@ proc selnextpage {dir} {
 proc unselectline {} {
     global selectedline currentid
 
-    catch {unset selectedline}
+    set selectedline {}
     catch {unset currentid}
     allcanvs delete secsel
     rhighlight_none
@@ -4887,7 +6058,7 @@ proc unselectline {} {
 proc reselectline {} {
     global selectedline
 
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
     }
 }
@@ -4992,11 +6163,12 @@ proc gettreeline {gtf id} {
        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]
+           set line [string range $line 0 [expr {$i-1}]]
+           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
+           set sha1 [lindex $line 2]
            if {[string index $fname 0] eq "\""} {
                set fname [lindex $fname 0]
            }
@@ -5075,19 +6247,19 @@ proc getblobline {bf id} {
     return [expr {$nl >= 1000? 2: 1}]
 }
 
-proc mergediff {id l} {
+proc mergediff {id} {
     global diffmergeid mdifffd
     global diffids
+    global parents
     global diffcontext
-    global parentlist
-    global limitdiffs viewfiles curview
+    global limitdiffs vfilelimit 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 -U$diffcontext $id]
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
-       set cmd [concat $cmd -- $viewfiles($curview)]
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
     }
     if {[catch {set mdf [open $cmd r]} err]} {
        error_popup "[mc "Error getting merge diffs:"] $err"
@@ -5095,7 +6267,7 @@ proc mergediff {id l} {
     }
     fconfigure $mdf -blocking 0
     set mdifffd($id) $mdf
-    set np [llength [lindex $parentlist $l]]
+    set np [llength $parents($curview,$id)]
     settabs $np
     filerun $mdf [list getmergediffline $mdf $id $np]
 }
@@ -5265,7 +6437,7 @@ proc gettreediffs {ids} {
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
-    global cmitmode viewfiles curview limitdiffs
+    global cmitmode vfilelimit curview limitdiffs
 
     set nr 0
     while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
@@ -5282,10 +6454,10 @@ proc gettreediffline {gdtf ids} {
        return [expr {$nr >= 1000? 2: 1}]
     }
     close $gdtf
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
        set flist {}
        foreach f $treediff {
-           if {[path_filter $viewfiles($curview) $f]} {
+           if {[path_filter $vfilelimit($curview) $f]} {
                lappend flist $f
            }
        }
@@ -5331,14 +6503,14 @@ proc getblobdiffs {ids} {
     global diffinhdr treediffs
     global diffcontext
     global ignorespace
-    global limitdiffs viewfiles curview
+    global limitdiffs vfilelimit curview
 
     set cmd [diffcmd $ids "-p -C --no-commit-id -U$diffcontext"]
     if {$ignorespace} {
        append cmd " -w"
     }
-    if {$limitdiffs && $viewfiles($curview) ne {}} {
-       set cmd [concat $cmd -- $viewfiles($curview)]
+    if {$limitdiffs && $vfilelimit($curview) ne {}} {
+       set cmd [concat $cmd -- $vfilelimit($curview)]
     }
     if {[catch {set bdf [open $cmd r]} err]} {
        puts "error getting diffs: $err"
@@ -5478,26 +6650,44 @@ proc changediffdisp {} {
     $ctext tag conf d1 -elide [lindex $diffelide 1]
 }
 
+proc highlightfile {loc cline} {
+    global ctext cflist cflist_top
+
+    $ctext yview $loc
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $cline.0 "$cline.0 lineend"
+    $cflist see $cline.0
+    set cflist_top $cline
+}
+
 proc prevfile {} {
-    global difffilestart ctext
-    set prev [lindex $difffilestart 0]
+    global difffilestart ctext cmitmode
+
+    if {$cmitmode eq "tree"} return
+    set prev 0.0
+    set prevline 1
     set here [$ctext index @0,0]
     foreach loc $difffilestart {
        if {[$ctext compare $loc >= $here]} {
-           $ctext yview $prev
+           highlightfile $prev $prevline
            return
        }
        set prev $loc
+       incr prevline
     }
-    $ctext yview $prev
+    highlightfile $prev $prevline
 }
 
 proc nextfile {} {
-    global difffilestart ctext
+    global difffilestart ctext cmitmode
+
+    if {$cmitmode eq "tree"} return
     set here [$ctext index @0,0]
+    set line 1
     foreach loc $difffilestart {
+       incr line
        if {[$ctext compare $loc > $here]} {
-           $ctext yview $loc
+           highlightfile $loc $line
            return
        }
     }
@@ -5680,7 +6870,7 @@ proc redisplay {} {
     setcanvscroll
     allcanvs yview moveto [lindex $span 0]
     drawvisible
-    if {[info exists selectedline]} {
+    if {$selectedline ne {}} {
        selectline $selectedline 0
        allcanvs yview moveto [lindex $span 0]
     }
@@ -5731,7 +6921,7 @@ proc fontname {f} {
 }
 
 proc incrfont {inc} {
-    global mainfont textfont ctext canv phase cflist showrefstop
+    global mainfont textfont ctext canv cflist showrefstop
     global stopped entries fontattr
 
     unmarkmatches
@@ -5782,8 +6972,7 @@ proc sha1change {n1 n2 op} {
 }
 
 proc gotocommit {} {
-    global sha1string currentid commitrow tagids headids
-    global displayorder numcommits curview
+    global sha1string tagids headids curview varcid
 
     if {$sha1string == {}
        || ([info exists currentid] && $sha1string == $currentid)} return
@@ -5794,23 +6983,18 @@ proc gotocommit {} {
     } 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
-               }
-           }
+           set matches [array names varcid "$curview,$id*"]
            if {$matches ne {}} {
                if {[llength $matches] > 1} {
                    error_popup [mc "Short SHA1 id %s is ambiguous" $id]
                    return
                }
-               set id [lindex $matches 0]
+               set id [lindex [split [lindex $matches 0] ","] 1]
            }
        }
     }
-    if {[info exists commitrow($curview,$id)]} {
-       selectline $commitrow($curview,$id) 1
+    if {[commitinview $id $curview]} {
+       selectline [rowofcommit $id] 1
        return
     }
     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
@@ -5919,7 +7103,7 @@ proc arrowjump {id n y} {
 }
 
 proc lineclick {x y id isnew} {
-    global ctext commitinfo children canv thickerline curview commitrow
+    global ctext commitinfo children canv thickerline curview
 
     if {![info exists commitinfo($id)] && ![getcommit $id]} return
     unmarkmatches
@@ -5987,9 +7171,9 @@ proc normalline {} {
 }
 
 proc selbyid {id} {
-    global commitrow curview
-    if {[info exists commitrow($curview,$id)]} {
-       selectline $commitrow($curview,$id) 1
+    global curview
+    if {[commitinview $id $curview]} {
+       selectline [rowofcommit $id] 1
     }
 }
 
@@ -6002,20 +7186,23 @@ proc mstime {} {
 }
 
 proc rowmenu {x y id} {
-    global rowctxmenu commitrow selectedline rowmenuid curview
+    global rowctxmenu selectedline rowmenuid curview
     global nullid nullid2 fakerowmenu mainhead
 
     stopfinding
     set rowmenuid $id
-    if {![info exists selectedline]
-       || $commitrow($curview,$id) eq $selectedline} {
+    if {$selectedline eq {} || [rowofcommit $id] eq $selectedline} {
        set state disabled
     } else {
        set state normal
     }
     if {$id ne $nullid && $id ne $nullid2} {
        set menu $rowctxmenu
-       $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
+       if {$mainhead ne {}} {
+           $menu entryconfigure 7 -label [mc "Reset %s branch to here" $mainhead]
+       } else {
+           $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
+       }
     } else {
        set menu $fakerowmenu
     }
@@ -6026,15 +7213,15 @@ proc rowmenu {x y id} {
 }
 
 proc diffvssel {dirn} {
-    global rowmenuid selectedline displayorder
+    global rowmenuid selectedline
 
-    if {![info exists selectedline]} return
+    if {$selectedline eq {}} return
     if {$dirn} {
-       set oldid [lindex $displayorder $selectedline]
+       set oldid [commitonrow $selectedline]
        set newid $rowmenuid
     } else {
        set oldid $rowmenuid
-       set newid [lindex $displayorder $selectedline]
+       set newid [commitonrow $selectedline]
     }
     addtohistory [list doseldiff $oldid $newid]
     doseldiff $oldid $newid
@@ -6212,24 +7399,24 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag commitrow idpos selectedline curview
+    global canv linehtag idpos currentid curview
     global canvxmax iddrawn
 
-    if {![info exists commitrow($curview,$id)]} return
+    if {![commitinview $id $curview]} return
     if {![info exists iddrawn($id)]} return
-    drawcommits $commitrow($curview,$id)
+    set row [rowofcommit $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]}]
+    $canv coords $linehtag($row) $xt [lindex $idpos($id) 2]
+    set text [$canv itemcget $linehtag($row) -text]
+    set font [$canv itemcget $linehtag($row) -font]
+    set xr [expr {$xt + [font measure $font $text]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
        setcanvscroll
     }
-    if {[info exists selectedline]
-       && $selectedline == $commitrow($curview,$id)} {
-       selectline $selectedline 0
+    if {[info exists currentid] && $currentid == $id} {
+       make_secsel $row
     }
 }
 
@@ -6355,8 +7542,8 @@ proc mkbrgo {top} {
 }
 
 proc cherrypick {} {
-    global rowmenuid curview commitrow
-    global mainhead
+    global rowmenuid curview
+    global mainhead mainheadid
 
     set oldhead [exec git rev-parse HEAD]
     set dheads [descheads $rowmenuid]
@@ -6382,20 +7569,22 @@ proc cherrypick {} {
        return
     }
     addnewchild $newhead $oldhead
-    if {[info exists commitrow($curview,$oldhead)]} {
-       insertrow $commitrow($curview,$oldhead) $newhead
+    if {[commitinview $oldhead $curview]} {
+       insertrow $newhead $oldhead $curview
        if {$mainhead ne {}} {
            movehead $newhead $mainhead
            movedhead $newhead $mainhead
+           set mainheadid $newhead
        }
        redrawtags $oldhead
        redrawtags $newhead
+       selbyid $newhead
     }
     notbusy cherrypick
 }
 
 proc resethead {} {
-    global mainheadid mainhead rowmenuid confirm_ok resettype
+    global mainhead rowmenuid confirm_ok resettype
 
     set confirm_ok 0
     set w ".confirmreset"
@@ -6428,12 +7617,13 @@ proc resethead {} {
     tkwait window $w
     if {!$confirm_ok} return
     if {[catch {set fd [open \
-           [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+           [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
        error_popup $err
     } else {
        dohidelocalchanges
        filerun $fd [list readresetstat $fd]
        nowbusy reset [mc "Resetting"]
+       selbyid $rowmenuid
     }
 }
 
@@ -6489,24 +7679,46 @@ proc cobranch {} {
     global showlocalchanges mainheadid
 
     # check the tree is clean first??
-    set oldmainhead $mainhead
     nowbusy checkout [mc "Checking out"]
     update
     dohidelocalchanges
     if {[catch {
-       exec git checkout -q $headmenuhead
+       set fd [open [list | git checkout $headmenuhead 2>@1] r]
     } err]} {
        notbusy checkout
        error_popup $err
+       if {$showlocalchanges} {
+           dodiffindex
+       }
     } else {
-       notbusy checkout
-       set mainhead $headmenuhead
-       set mainheadid $headmenuid
-       if {[info exists headids($oldmainhead)]} {
-           redrawtags $headids($oldmainhead)
+       filerun $fd [list readcheckoutstat $fd $headmenuhead $headmenuid]
+    }
+}
+
+proc readcheckoutstat {fd newhead newheadid} {
+    global mainhead mainheadid headids showlocalchanges progresscoords
+
+    if {[gets $fd line] >= 0} {
+       if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
+           set progresscoords [list 0 [expr {1.0 * $m / $n}]]
+           adjustprogress
        }
-       redrawtags $headmenuid
+       return 1
+    }
+    set progresscoords {0 0}
+    adjustprogress
+    notbusy checkout
+    if {[catch {close $fd} err]} {
+       error_popup $err
+    }
+    set oldmainhead $mainhead
+    set mainhead $newhead
+    set mainheadid $newheadid
+    if {[info exists headids($oldmainhead)]} {
+       redrawtags $headids($oldmainhead)
     }
+    redrawtags $newheadid
+    selbyid $newheadid
     if {$showlocalchanges} {
        dodiffindex
     }
@@ -6620,13 +7832,13 @@ proc reflistfilter_change {n1 n2 op} {
 
 proc refill_reflist {} {
     global reflist reflistfilter showrefstop headids tagids otherrefids
-    global commitrow curview commitinterest
+    global 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))]} {
+           if {[commitinview $headids($n) $curview]} {
                lappend refs [list $n H]
            } else {
                set commitinterest($headids($n)) {run refill_reflist}
@@ -6635,7 +7847,7 @@ proc refill_reflist {} {
     }
     foreach n [array names tagids] {
        if {[string match $reflistfilter $n]} {
-           if {[info exists commitrow($curview,$tagids($n))]} {
+           if {[commitinview $tagids($n) $curview]} {
                lappend refs [list $n T]
            } else {
                set commitinterest($tagids($n)) {run refill_reflist}
@@ -6644,7 +7856,7 @@ proc refill_reflist {} {
     }
     foreach n [array names otherrefids] {
        if {[string match $reflistfilter $n]} {
-           if {[info exists commitrow($curview,$otherrefids($n))]} {
+           if {[commitinview $otherrefids($n) $curview]} {
                lappend refs [list $n o]
            } else {
                set commitinterest($otherrefids($n)) {run refill_reflist}
@@ -7788,7 +9000,7 @@ proc changedrefs {} {
 }
 
 proc rereadrefs {} {
-    global idtags idheads idotherrefs mainhead
+    global idtags idheads idotherrefs mainheadid
 
     set refids [concat [array names idtags] \
                    [array names idheads] [array names idotherrefs]]
@@ -7797,7 +9009,7 @@ proc rereadrefs {} {
            set ref($id) [listrefs $id]
        }
     }
-    set oldmainhead $mainhead
+    set oldmainhead $mainheadid
     readrefs
     changedrefs
     set refids [lsort -unique [concat $refids [array names idtags] \
@@ -7805,8 +9017,8 @@ proc rereadrefs {} {
     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)} {
+           ($id eq $oldmainhead && $id ne $mainheadid) ||
+           ($id eq $mainheadid && $id ne $oldmainhead)} {
            redrawtags $id
        }
     }
@@ -7858,9 +9070,15 @@ proc showtag {tag isnew} {
 
 proc doquit {} {
     global stopped
+    global gitktmpdir
+
     set stopped 100
     savestuff .
     destroy .
+
+    if {[info exists gitktmpdir]} {
+       catch {file delete -force $gitktmpdir}
+    }
 }
 
 proc mkfontdisp {font top which} {
@@ -7989,7 +9207,7 @@ proc doprefs {} {
     global maxwidth maxgraphpct
     global oldprefs prefstop showneartags showlocalchanges
     global bgcolor fgcolor ctext diffcolors selectbgcolor
-    global tabstop limitdiffs autoselect
+    global tabstop limitdiffs autoselect extdifftool
 
     set top .gitkprefs
     set prefstop $top
@@ -8041,6 +9259,15 @@ proc doprefs {} {
     pack $top.ldiff.b $top.ldiff.l -side left
     grid x $top.ldiff -sticky w
 
+    entry $top.extdifft -textvariable extdifftool
+    frame $top.extdifff
+    label $top.extdifff.l -text [mc "External diff tool" ] -font optionfont \
+       -padx 10
+    button $top.extdifff.b -text [mc "Choose..."] -font optionfont \
+       -command choose_extdiff
+    pack $top.extdifff.l $top.extdifff.b -side left
+    grid x $top.extdifff $top.extdifft -sticky w
+
     label $top.cdisp -text [mc "Colors: press to choose"]
     grid $top.cdisp - -sticky w -pady 10
     label $top.bg -padx 40 -relief sunk -background $bgcolor
@@ -8088,6 +9315,15 @@ proc doprefs {} {
     bind $top <Visibility> "focus $top.buts.ok"
 }
 
+proc choose_extdiff {} {
+    global extdifftool
+
+    set prog [tk_getOpenFile -title "External diff tool" -multiple false]
+    if {$prog ne {}} {
+       set extdifftool $prog
+    }
+}
+
 proc choosecolor {v vi w x cmd} {
     global $v
 
@@ -8479,7 +9715,6 @@ if {[catch {package require Tk 8.4} err]} {
 }
 
 # defaults...
-set datemode 0
 set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
@@ -8516,6 +9751,8 @@ set limitdiffs 1
 set datetimeformat "%Y-%m-%d %H:%M:%S"
 set autoselect 1
 
+set extdifftool "meld"
+
 set colors {green red blue magenta darkgrey brown orange}
 set bgcolor white
 set fgcolor black
@@ -8570,7 +9807,6 @@ if {![file isdirectory $gitdir]} {
     exit 1
 }
 
-set mergeonly 0
 set revtreeargs {}
 set cmdline_files {}
 set i 0
@@ -8578,11 +9814,6 @@ set revtreeargscmd {}
 foreach arg $argv {
     switch -glob -- $arg {
        "" { }
-       "-d" { set datemode 1 }
-       "--merge" {
-           set mergeonly 1
-           lappend revtreeargs $arg
-       }
        "--" {
            set cmdline_files [lrange $argv [expr {$i + 1}] end]
            break
@@ -8598,7 +9829,7 @@ foreach arg $argv {
 }
 
 if {$i >= [llength $argv] && $revtreeargs ne {}} {
-    # no -- on command line, but some arguments (other than -d)
+    # no -- on command line, but some arguments (other than --argscmd)
     if {[catch {
        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
        set cmdline_files [split $f "\n"]
@@ -8626,42 +9857,9 @@ if {$i >= [llength $argv] && $revtreeargs ne {}} {
     }
 }
 
-if {$mergeonly} {
-    # find the list of unmerged files
-    set mlist {}
-    set nr_unmerged 0
-    if {[catch {
-       set fd [open "| git ls-files -u" r]
-    } err]} {
-       show_error {} . "[mc "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 {} . [mc "No files selected: --merge specified but\
-                            no files are unmerged."]
-       } else {
-           show_error {} . [mc "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 nullfile "/dev/null"
 
 set have_tk85 [expr {[package vcompare $tk_version "8.5"] >= 0}]
 
@@ -8693,12 +9891,13 @@ set viewperm(0) 0
 set viewargs(0) {}
 set viewargscmd(0) {}
 
+set selectedline {}
+set numcommits 0
+set loginstance 0
 set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
-set localirow -1
-set localfrow -1
 set lserial 0
 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
 setcoords
@@ -8718,6 +9917,7 @@ if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
     set viewargs(1) $revtreeargs
     set viewargscmd(1) $revtreeargscmd
     set viewperm(1) 0
+    set vdatemode(1) 0
     addviewmenu 1
     .bar.view entryconf [mc "Edit view..."] -state normal
     .bar.view entryconf [mc "Delete view"] -state normal
index 5ee2fca8b264d2b64310cd15bb6c2b36ea215b78..b9867bf8e05d06f7effb9919f00879f77690e185 100644 (file)
@@ -7,249 +7,253 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-01-09 22:20+0100\n"
-"PO-Revision-Date: 2008-01-09 22:21+0100\n"
+"POT-Creation-Date: 2008-05-01 11:54+0200\n"
+"PO-Revision-Date: 2008-05-02 21:12+0200\n"
 "Last-Translator: Christian Stimming <stimming@tuhh.de>\n"
 "Language-Team: German\n"
 "MIME-Version: 1.0\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: gitk:101
+#: gitk:111
 msgid "Error executing git rev-list:"
 msgstr "Fehler beim Ausführen von git-rev-list:"
 
-#: gitk:114
+#: gitk:124
 msgid "Reading"
 msgstr "Lesen"
 
-#: gitk:141 gitk:2143
+#: gitk:151 gitk:2191
 msgid "Reading commits..."
 msgstr "Versionen lesen..."
 
-#: gitk:264
+#: gitk:275
 msgid "Can't parse git log output:"
-msgstr "Git log Ausgabe kann nicht erkannt werden:"
+msgstr "Ausgabe von git-log kann nicht erkannt werden:"
 
-#: gitk:375 gitk:2147
+#: gitk:386 gitk:2195
 msgid "No commits selected"
 msgstr "Keine Versionen ausgewählt."
 
-#: gitk:486
+#: gitk:500
 msgid "No commit information available"
 msgstr "Keine Versionsinformation verfügbar"
 
-#: gitk:585 gitk:607 gitk:1908 gitk:6366 gitk:7866 gitk:8020
+#: gitk:599 gitk:621 gitk:1955 gitk:6424 gitk:7924 gitk:8083
 msgid "OK"
 msgstr "Ok"
 
-#: gitk:609 gitk:1909 gitk:6046 gitk:6117 gitk:6218 gitk:6264 gitk:6368
-#: gitk:7867 gitk:8021
+#: gitk:623 gitk:1956 gitk:6108 gitk:6179 gitk:6276 gitk:6322 gitk:6426
+#: gitk:7925 gitk:8084
 msgid "Cancel"
 msgstr "Abbrechen"
 
-#: gitk:646
+#: gitk:661
 msgid "File"
 msgstr "Datei"
 
-#: gitk:648
+#: gitk:663
 msgid "Update"
 msgstr "Aktualisieren"
 
-#: gitk:649
+#: gitk:664
 msgid "Reread references"
 msgstr "Zweige neu laden"
 
-#: gitk:650
+#: gitk:665
 msgid "List references"
-msgstr "Zweige auflisten"
+msgstr "Zweige/Markierungen auflisten"
 
-#: gitk:651
+#: gitk:666
 msgid "Quit"
 msgstr "Beenden"
 
-#: gitk:653
+#: gitk:668
 msgid "Edit"
 msgstr "Bearbeiten"
 
-#: gitk:654
+#: gitk:669
 msgid "Preferences"
 msgstr "Einstellungen"
 
-#: gitk:657
+#: gitk:672 gitk:1892
 msgid "View"
 msgstr "Ansicht"
 
-#: gitk:658
+#: gitk:673
 msgid "New view..."
 msgstr "Neue Ansicht..."
 
-#: gitk:659 gitk:2085 gitk:8651
+#: gitk:674 gitk:2133 gitk:8723
 msgid "Edit view..."
 msgstr "Ansicht bearbeiten..."
 
-#: gitk:661 gitk:2086 gitk:8652
+#: gitk:676 gitk:2134 gitk:8724
 msgid "Delete view"
 msgstr "Ansicht löschen"
 
-#: gitk:663
+#: gitk:678
 msgid "All files"
 msgstr "Alle Dateien"
 
-#: gitk:667
+#: gitk:682
 msgid "Help"
 msgstr "Hilfe"
 
-#: gitk:668 gitk:1280
+#: gitk:683 gitk:1317
 msgid "About gitk"
 msgstr "Über gitk"
 
-#: gitk:669
+#: gitk:684
 msgid "Key bindings"
 msgstr "Tastenkürzel"
 
-#: gitk:726
+#: gitk:741
 msgid "SHA1 ID: "
 msgstr "SHA1:"
 
-#: gitk:776
+#: gitk:791
 msgid "Find"
 msgstr "Suche"
 
-#: gitk:777
+#: gitk:792
 msgid "next"
 msgstr "nächste"
 
-#: gitk:778
+#: gitk:793
 msgid "prev"
 msgstr "vorige"
 
-#: gitk:779
+#: gitk:794
 msgid "commit"
 msgstr "Version"
 
-#: gitk:782 gitk:784 gitk:2308 gitk:2331 gitk:2355 gitk:4257 gitk:4320
+#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369
 msgid "containing:"
 msgstr "enthaltend:"
 
-#: gitk:785 gitk:1741 gitk:1746 gitk:2383
+#: gitk:800 gitk:1778 gitk:1783 gitk:2431
 msgid "touching paths:"
 msgstr "Pfad betreffend:"
 
-#: gitk:786 gitk:2388
+#: gitk:801 gitk:2436
 msgid "adding/removing string:"
-msgstr "String dazu/löschen:"
+msgstr "Zeichenkette ändernd:"
 
-#: gitk:795 gitk:797
+#: gitk:810 gitk:812
 msgid "Exact"
 msgstr "Exakt"
 
-#: gitk:797 gitk:2466 gitk:4225
+#: gitk:812 gitk:2514 gitk:4274
 msgid "IgnCase"
 msgstr "Kein Groß/Klein"
 
-#: gitk:797 gitk:2357 gitk:2464 gitk:4221
+#: gitk:812 gitk:2405 gitk:2512 gitk:4270
 msgid "Regexp"
 msgstr "Regexp"
 
-#: gitk:799 gitk:800 gitk:2485 gitk:2515 gitk:2522 gitk:4331 gitk:4387
+#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436
 msgid "All fields"
 msgstr "Alle Felder"
 
-#: gitk:800 gitk:2483 gitk:2515 gitk:4287
+#: gitk:815 gitk:2531 gitk:2563 gitk:4336
 msgid "Headline"
 msgstr "Überschrift"
 
-#: gitk:801 gitk:2483 gitk:4287 gitk:4387 gitk:4775
+#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827
 msgid "Comments"
 msgstr "Beschreibung"
 
-#: gitk:801 gitk:2483 gitk:2487 gitk:2522 gitk:4287 gitk:4711 gitk:5895
-#: gitk:5910
+#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5957
+#: gitk:5972
 msgid "Author"
 msgstr "Autor"
 
-#: gitk:801 gitk:2483 gitk:4287 gitk:4713
+#: gitk:816 gitk:2531 gitk:4336 gitk:4765
 msgid "Committer"
 msgstr "Eintragender"
 
-#: gitk:829
+#: gitk:845
 msgid "Search"
 msgstr "Suche"
 
-#: gitk:836
+#: gitk:852
 msgid "Diff"
 msgstr "Vergleich"
 
-#: gitk:838
+#: gitk:854
 msgid "Old version"
 msgstr "Alte Version"
 
-#: gitk:840
+#: gitk:856
 msgid "New version"
 msgstr "Neue Version"
 
-#: gitk:842
+#: gitk:858
 msgid "Lines of context"
 msgstr "Kontextzeilen"
 
-#: gitk:900
+#: gitk:868
+msgid "Ignore space change"
+msgstr "Leerzeichenänderungen ignorieren"
+
+#: gitk:926
 msgid "Patch"
 msgstr "Patch"
 
-#: gitk:902
+#: gitk:928
 msgid "Tree"
 msgstr "Baum"
 
-#: gitk:1018 gitk:1033 gitk:5961
+#: gitk:1053 gitk:1068 gitk:6023
 msgid "Diff this -> selected"
 msgstr "Vergleich diese -> gewählte"
 
-#: gitk:1020 gitk:1035 gitk:5962
+#: gitk:1055 gitk:1070 gitk:6024
 msgid "Diff selected -> this"
 msgstr "Vergleich gewählte -> diese"
 
-#: gitk:1022 gitk:1037 gitk:5963
+#: gitk:1057 gitk:1072 gitk:6025
 msgid "Make patch"
 msgstr "Patch erstellen"
 
-#: gitk:1023 gitk:6101
+#: gitk:1058 gitk:6163
 msgid "Create tag"
 msgstr "Markierung erstellen"
 
-#: gitk:1024 gitk:6198
+#: gitk:1059 gitk:6256
 msgid "Write commit to file"
 msgstr "Version in Datei schreiben"
 
-#: gitk:1025 gitk:6252
+#: gitk:1060 gitk:6310
 msgid "Create new branch"
 msgstr "Neuen Zweig erstellen"
 
-#: gitk:1026
+#: gitk:1061
 msgid "Cherry-pick this commit"
 msgstr "Diese Version pflücken"
 
-#: gitk:1028
+#: gitk:1063
 msgid "Reset HEAD branch to here"
 msgstr "HEAD-Zweig auf diese Version zurücksetzen"
 
-#: gitk:1044
+#: gitk:1079
 msgid "Check out this branch"
 msgstr "Auf diesen Zweig umstellen"
 
-#: gitk:1046
+#: gitk:1081
 msgid "Remove this branch"
 msgstr "Zweig löschen"
 
-#: gitk:1052
+#: gitk:1087
 msgid "Highlight this too"
 msgstr "Diesen auch hervorheben"
 
-#: gitk:1054
+#: gitk:1089
 msgid "Highlight this only"
 msgstr "Nur diesen hervorheben"
 
-#: gitk:1281
+#: gitk:1318
 msgid ""
 "\n"
 "Gitk - a commit viewer for git\n"
@@ -267,262 +271,425 @@ msgstr ""
 "License\n"
 "        "
 
-#: gitk:1289 gitk:1350 gitk:6524
+#: gitk:1326 gitk:1387 gitk:6582
 msgid "Close"
 msgstr "Schließen"
 
-#: gitk:1308
+#: gitk:1345
 msgid "Gitk key bindings"
 msgstr "Gitk Tastaturbelegung"
 
-#: gitk:1858
+#: gitk:1347
+msgid "Gitk key bindings:"
+msgstr "Gitk Tastaturbelegung:"
+
+#: gitk:1349
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tBeenden"
+
+#: gitk:1350
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Pos1>\t\tZur neuesten Version springen"
+
+#: gitk:1351
+msgid "<End>\t\tMove to last commit"
+msgstr "<Ende>\t\tZur ältesten Version springen"
+
+#: gitk:1352
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Hoch>, p, i\tNächste neuere Version"
+
+#: gitk:1353
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Runter>, n, k\tNächste ältere Version"
+
+#: gitk:1354
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Links>, z, j\tEine Version zurückgehen"
+
+#: gitk:1355
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Rechts>, x, l\tEine Version weitergehen"
+
+#: gitk:1356
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<BildHoch>\tEine Seite nach oben blättern"
+
+#: gitk:1357
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<BildRunter>\tEine Seite nach unten blättern"
+
+#: gitk:1358
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern"
+
+#: gitk:1359
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern"
+
+#: gitk:1360
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern"
+
+#: gitk:1361
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern"
+
+#: gitk:1362
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-BildHoch>\tVersionsliste eine Seite hoch blättern"
+
+#: gitk:1363
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern"
+
+#: gitk:1364
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)"
+
+#: gitk:1365
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)"
+
+#: gitk:1366
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern"
+
+#: gitk:1367
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern"
+
+#: gitk:1368
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern"
+
+#: gitk:1369
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tVergleich um 18 Zeilen nach oben (»up«) blättern"
+
+#: gitk:1370
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tVergleich um 18 Zeilen nach unten (»down«) blättern"
+
+#: gitk:1371
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tSuchen"
+
+#: gitk:1372
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tWeitersuchen"
+
+#: gitk:1373
+msgid "<Return>\tMove to next find hit"
+msgstr "<Eingabetaste>\tWeitersuchen"
+
+#: gitk:1374
+msgid "/\t\tMove to next find hit, or redo find"
+msgstr "/\t\tWeitersuchen oder neue Suche beginnen"
+
+#: gitk:1375
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tRückwärts weitersuchen"
+
+#: gitk:1376
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tVergleich zur nächsten Datei (»file«) blättern"
+
+#: gitk:1377
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tWeitersuchen im Vergleich"
+
+#: gitk:1378
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich"
+
+#: gitk:1379
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-Nummerblock-Plus>\tSchriftgröße vergrößern"
+
+#: gitk:1380
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-Plus>\tSchriftgröße vergrößern"
+
+#: gitk:1381
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-Nummernblock-> Schriftgröße verkleinern"
+
+#: gitk:1382
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-Minus>\tSchriftgröße verkleinern"
+
+#: gitk:1383
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tAktualisieren"
+
+#: gitk:1896
 msgid "Gitk view definition"
 msgstr "Gitk Ansichten"
 
-#: gitk:1882
+#: gitk:1921
 msgid "Name"
 msgstr "Name"
 
-#: gitk:1885
+#: gitk:1924
 msgid "Remember this view"
 msgstr "Diese Ansicht speichern"
 
-#: gitk:1889
+#: gitk:1928
 msgid "Commits to include (arguments to git rev-list):"
 msgstr "Versionen anzeigen (Argumente von git-rev-list):"
 
-#: gitk:1895
+#: gitk:1935
+msgid "Command to generate more commits to include:"
+msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:"
+
+#: gitk:1942
 msgid "Enter files and directories to include, one per line:"
 msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):"
 
-#: gitk:1942
+#: gitk:1989
 msgid "Error in commit selection arguments:"
 msgstr "Fehler in den ausgewählten Versionen:"
 
-#: gitk:1993 gitk:2079 gitk:2535 gitk:2549 gitk:3732 gitk:8620 gitk:8621
+#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8689 gitk:8690
 msgid "None"
 msgstr "Keine"
 
-#: gitk:2483 gitk:4287 gitk:5897 gitk:5912
+#: gitk:2531 gitk:4336 gitk:5959 gitk:5974
 msgid "Date"
 msgstr "Datum"
 
-#: gitk:2483 gitk:4287
+#: gitk:2531 gitk:4336
 msgid "CDate"
 msgstr "Eintragedatum"
 
-#: gitk:2632 gitk:2637
+#: gitk:2680 gitk:2685
 msgid "Descendant"
 msgstr "Abkömmling"
 
-#: gitk:2633
+#: gitk:2681
 msgid "Not descendant"
 msgstr "Nicht Abkömmling"
 
-#: gitk:2640 gitk:2645
+#: gitk:2688 gitk:2693
 msgid "Ancestor"
 msgstr "Vorgänger"
 
-#: gitk:2641
+#: gitk:2689
 msgid "Not ancestor"
 msgstr "Nicht Vorgänger"
 
-#: gitk:2875
+#: gitk:2924
 msgid "Local changes checked in to index but not committed"
 msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen"
 
-#: gitk:2905
+#: gitk:2954
 msgid "Local uncommitted changes, not checked in to index"
 msgstr "Lokale Änderungen, nicht bereitgestellt"
 
-#: gitk:4256
+#: gitk:4305
 msgid "Searching"
 msgstr "Suchen"
 
-#: gitk:4715
+#: gitk:4767
 msgid "Tags:"
 msgstr "Markierungen:"
 
-#: gitk:4732 gitk:4738 gitk:5890
+#: gitk:4784 gitk:4790 gitk:5952
 msgid "Parent"
 msgstr "Eltern"
 
-#: gitk:4743
+#: gitk:4795
 msgid "Child"
 msgstr "Kind"
 
-#: gitk:4752
+#: gitk:4804
 msgid "Branch"
 msgstr "Zweig"
 
-#: gitk:4755
+#: gitk:4807
 msgid "Follows"
 msgstr "Folgt auf"
 
-#: gitk:4758
+#: gitk:4810
 msgid "Precedes"
 msgstr "Vorgänger von"
 
-#: gitk:5040
+#: gitk:5094
 msgid "Error getting merge diffs:"
 msgstr "Fehler beim Laden des Vergleichs:"
 
-#: gitk:5717
+#: gitk:5779
 msgid "Goto:"
 msgstr "Gehe zu:"
 
-#: gitk:5719
+#: gitk:5781
 msgid "SHA1 ID:"
-msgstr "SHA1 Kennung:"
+msgstr "SHA1-Hashwert:"
 
-#: gitk:5744
+#: gitk:5806
 #, tcl-format
 msgid "Short SHA1 id %s is ambiguous"
-msgstr "Kurze SHA1-Kennung »%s« ist mehrdeutig"
+msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig"
 
-#: gitk:5756
+#: gitk:5818
 #, tcl-format
 msgid "SHA1 id %s is not known"
-msgstr "SHA1-Kennung »%s« unbekannt"
+msgstr "SHA1-Hashwert »%s« unbekannt"
 
-#: gitk:5758
+#: gitk:5820
 #, tcl-format
 msgid "Tag/Head %s is not known"
 msgstr "Markierung/Zweig »%s« ist unbekannt"
 
-#: gitk:5900
+#: gitk:5962
 msgid "Children"
 msgstr "Kinder"
 
-#: gitk:5957
+#: gitk:6019
 #, tcl-format
 msgid "Reset %s branch to here"
 msgstr "Zweig »%s« hierher zurücksetzen"
 
-#: gitk:5988
+#: gitk:6050
 msgid "Top"
 msgstr "Oben"
 
-#: gitk:5989
+#: gitk:6051
 msgid "From"
 msgstr "Von"
 
-#: gitk:5994
+#: gitk:6056
 msgid "To"
 msgstr "bis"
 
-#: gitk:6017
+#: gitk:6079
 msgid "Generate patch"
 msgstr "Patch erstellen"
 
-#: gitk:6019
+#: gitk:6081
 msgid "From:"
 msgstr "Von:"
 
-#: gitk:6028
+#: gitk:6090
 msgid "To:"
 msgstr "bis:"
 
-#: gitk:6037
+#: gitk:6099
 msgid "Reverse"
 msgstr "Umgekehrt"
 
-#: gitk:6039 gitk:6212
+#: gitk:6101 gitk:6270
 msgid "Output file:"
 msgstr "Ausgabedatei:"
 
-#: gitk:6045
+#: gitk:6107
 msgid "Generate"
 msgstr "Erzeugen"
 
-#: gitk:6081
+#: gitk:6143
 msgid "Error creating patch:"
 msgstr "Fehler beim Patch erzeugen:"
 
-#: gitk:6103 gitk:6200 gitk:6254
+#: gitk:6165 gitk:6258 gitk:6312
 msgid "ID:"
 msgstr "ID:"
 
-#: gitk:6112
+#: gitk:6174
 msgid "Tag name:"
 msgstr "Markierungsname:"
 
-#: gitk:6116 gitk:6263
+#: gitk:6178 gitk:6321
 msgid "Create"
 msgstr "Erstellen"
 
-#: gitk:6131
+#: gitk:6193
 msgid "No tag name specified"
 msgstr "Kein Markierungsname angegeben"
 
-#: gitk:6135
+#: gitk:6197
 #, tcl-format
 msgid "Tag \"%s\" already exists"
 msgstr "Markierung »%s« existiert bereits."
 
-#: gitk:6145
+#: gitk:6203
 msgid "Error creating tag:"
 msgstr "Fehler bei Markierung erstellen:"
 
-#: gitk:6209
+#: gitk:6267
 msgid "Command:"
 msgstr "Kommando:"
 
-#: gitk:6217
+#: gitk:6275
 msgid "Write"
 msgstr "Schreiben"
 
-#: gitk:6233
+#: gitk:6291
 msgid "Error writing commit:"
-msgstr "Fehler beim Version eintragen:"
+msgstr "Fehler beim Schreiben der Version:"
 
-#: gitk:6259
+#: gitk:6317
 msgid "Name:"
 msgstr "Name:"
 
-#: gitk:6278
+#: gitk:6336
 msgid "Please specify a name for the new branch"
 msgstr "Bitte geben Sie einen Namen für den neuen Zweig an."
 
-#: gitk:6307
+#: gitk:6365
 #, tcl-format
 msgid "Commit %s is already included in branch %s -- really re-apply it?"
 msgstr ""
 "Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut "
 "eintragen?"
 
-#: gitk:6312
+#: gitk:6370
 msgid "Cherry-picking"
 msgstr "Version pflücken"
 
-#: gitk:6324
+#: gitk:6382
 msgid "No changes committed"
 msgstr "Keine Änderungen eingetragen"
 
-#: gitk:6347
+#: gitk:6405
 msgid "Confirm reset"
 msgstr "Zurücksetzen bestätigen"
 
-#: gitk:6349
+#: gitk:6407
 #, tcl-format
 msgid "Reset branch %s to %s?"
 msgstr "Zweig »%s« auf »%s« zurücksetzen?"
 
-#: gitk:6353
+#: gitk:6411
 msgid "Reset type:"
 msgstr "Art des Zurücksetzens:"
 
-#: gitk:6357
+#: gitk:6415
 msgid "Soft: Leave working tree and index untouched"
 msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert"
 
-#: gitk:6360
+#: gitk:6418
 msgid "Mixed: Leave working tree untouched, reset index"
 msgstr ""
 "Gemischt: Arbeitskopie unverändert,\n"
 "Bereitstellung zurückgesetzt"
 
-#: gitk:6363
+#: gitk:6421
 msgid ""
 "Hard: Reset working tree and index\n"
 "(discard ALL local changes)"
@@ -530,21 +697,21 @@ msgstr ""
 "Hart: Arbeitskopie und Bereitstellung\n"
 "(Alle lokalen Änderungen werden gelöscht)"
 
-#: gitk:6379
+#: gitk:6437
 msgid "Resetting"
 msgstr "Zurücksetzen"
 
-#: gitk:6436
+#: gitk:6494
 msgid "Checking out"
 msgstr "Umstellen"
 
-#: gitk:6466
+#: gitk:6524
 msgid "Cannot delete the currently checked-out branch"
 msgstr ""
 "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht "
 "gelöscht werden."
 
-#: gitk:6472
+#: gitk:6530
 #, tcl-format
 msgid ""
 "The commits on branch %s aren't on any other branch.\n"
@@ -553,16 +720,16 @@ msgstr ""
 "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n"
 "Zweig »%s« trotzdem löschen?"
 
-#: gitk:6503
+#: gitk:6561
 #, tcl-format
 msgid "Tags and heads: %s"
 msgstr "Markierungen und Zweige: %s"
 
-#: gitk:6517
+#: gitk:6575
 msgid "Filter"
 msgstr "Filtern"
 
-#: gitk:6811
+#: gitk:6869
 msgid ""
 "Error reading commit topology information; branch and preceding/following "
 "tag information will be incomplete."
@@ -570,113 +737,117 @@ msgstr ""
 "Fehler beim Lesen der Strukturinformationen; Zweige und Vorgänger/Nachfolger "
 "Informationen werden unvollständig sein."
 
-#: gitk:7795
+#: gitk:7853
 msgid "Tag"
 msgstr "Markierung"
 
-#: gitk:7795
+#: gitk:7853
 msgid "Id"
 msgstr "Id"
 
-#: gitk:7835
+#: gitk:7893
 msgid "Gitk font chooser"
 msgstr "Gitk Schriften wählen"
 
-#: gitk:7852
+#: gitk:7910
 msgid "B"
 msgstr "F"
 
-#: gitk:7855
+#: gitk:7913
 msgid "I"
 msgstr "K"
 
-#: gitk:7948
+#: gitk:8006
 msgid "Gitk preferences"
 msgstr "Gitk Einstellungen"
 
-#: gitk:7949
+#: gitk:8007
 msgid "Commit list display options"
 msgstr "Anzeige Versionsliste"
 
-#: gitk:7952
+#: gitk:8010
 msgid "Maximum graph width (lines)"
 msgstr "Maximale Graphenbreite (Zeilen)"
 
-#: gitk:7956
+#: gitk:8014
 #, tcl-format
 msgid "Maximum graph width (% of pane)"
 msgstr "Maximale Graphenbreite (% des Fensters)"
 
-#: gitk:7961
+#: gitk:8019
 msgid "Show local changes"
 msgstr "Lokale Änderungen anzeigen"
 
-#: gitk:7966
+#: gitk:8024
+msgid "Auto-select SHA1"
+msgstr "SHA1-Hashwert automatisch markieren"
+
+#: gitk:8029
 msgid "Diff display options"
 msgstr "Anzeige Vergleich"
 
-#: gitk:7968
+#: gitk:8031
 msgid "Tab spacing"
 msgstr "Tabulatorbreite"
 
-#: gitk:7972
+#: gitk:8035
 msgid "Display nearby tags"
 msgstr "Naheliegende Überschriften anzeigen"
 
-#: gitk:7977
+#: gitk:8040
 msgid "Limit diffs to listed paths"
 msgstr "Vergleich nur für angezeigte Pfade"
 
-#: gitk:7982
+#: gitk:8045
 msgid "Colors: press to choose"
 msgstr "Farben: Klicken zum Wählen"
 
-#: gitk:7985
+#: gitk:8048
 msgid "Background"
-msgstr "Vordergrund"
+msgstr "Hintergrund"
 
-#: gitk:7989
+#: gitk:8052
 msgid "Foreground"
-msgstr "Hintergrund"
+msgstr "Vordergrund"
 
-#: gitk:7993
+#: gitk:8056
 msgid "Diff: old lines"
 msgstr "Vergleich: Alte Zeilen"
 
-#: gitk:7998
+#: gitk:8061
 msgid "Diff: new lines"
 msgstr "Vergleich: Neue Zeilen"
 
-#: gitk:8003
+#: gitk:8066
 msgid "Diff: hunk header"
 msgstr "Vergleich: Änderungstitel"
 
-#: gitk:8009
+#: gitk:8072
 msgid "Select bg"
 msgstr "Hintergrundfarbe Auswählen"
 
-#: gitk:8013
+#: gitk:8076
 msgid "Fonts: press to choose"
 msgstr "Schriftart: Klicken zum Wählen"
 
-#: gitk:8015
+#: gitk:8078
 msgid "Main font"
 msgstr "Programmschriftart"
 
-#: gitk:8016
+#: gitk:8079
 msgid "Diff display font"
 msgstr "Vergleich"
 
-#: gitk:8017
+#: gitk:8080
 msgid "User interface font"
 msgstr "Beschriftungen"
 
-#: gitk:8033
+#: gitk:8096
 #, tcl-format
 msgid "Gitk: choose color for %s"
 msgstr "Gitk: Farbe wählen für %s"
 
-#: gitk:8414
+#: gitk:8477
 msgid ""
 "Sorry, gitk cannot run with this version of Tcl/Tk.\n"
 " Gitk requires at least Tcl/Tk 8.4."
@@ -684,35 +855,33 @@ msgstr ""
 "Gitk läuft nicht mit dieser Version von Tcl/Tk.\n"
 "Gitk benötigt mindestens Tcl/Tk 8.4."
 
-#: gitk:8501
+#: gitk:8566
 msgid "Cannot find a git repository here."
 msgstr "Kein Git-Projektarchiv gefunden."
 
-#: gitk:8505
+#: gitk:8570
 #, tcl-format
 msgid "Cannot find the git directory \"%s\"."
 msgstr "Git-Verzeichnis »%s« wurde nicht gefunden."
 
-#: gitk:8544
+#: gitk:8613
 #, tcl-format
 msgid "Ambiguous argument '%s': both revision and filename"
 msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert."
 
-#: gitk:8556
+#: gitk:8625
 msgid "Bad arguments to gitk:"
 msgstr "Falsche Kommandozeilen-Parameter für gitk:"
 
-#: gitk:8568
+#: gitk:8637
 msgid "Couldn't get list of unmerged files:"
 msgstr "Liste der nicht-zusammengeführten Dateien nicht gefunden:"
 
-#: gitk:8584
+#: gitk:8653
 msgid "No files selected: --merge specified but no files are unmerged."
-msgstr ""
-"Keine Dateien ausgewähle: --merge angegeben, es existieren aber keine nicht-"
-"zusammengeführten Dateien."
+msgstr "Keine Dateien ausgewählt: --merge angegeben, es existieren aber keine nicht-zusammengeführten Dateien."
 
-#: gitk:8587
+#: gitk:8656
 msgid ""
 "No files selected: --merge specified but no unmerged files are within file "
 "limit."
@@ -720,6 +889,6 @@ msgstr ""
 "Keine Dateien ausgewähle: --merge angegeben, aber keine nicht-"
 "zusammengeführten Dateien sind in der Dateiauswahl."
 
-#: gitk:8646
+#: gitk:8717
 msgid "Command line"
 msgstr "Kommandozeile"
diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po
new file mode 100644 (file)
index 0000000..2cb1486
--- /dev/null
@@ -0,0 +1,890 @@
+# Translation of gitk
+# Copyright (C) 2005-2008 Santiago Gala
+# This file is distributed under the same license as the gitk package.
+# Santiago Gala <santiago.gala@gmail.com>, 2008.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-13 17:29+0100\n"
+"PO-Revision-Date: 2008-03-25 11:20+0100\n"
+"Last-Translator: Santiago Gala <santiago.gala@gmail.com>\n"
+"Language-Team: Spanish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:111
+msgid "Error executing git rev-list:"
+msgstr "Error al ejecutar git rev-list:"
+
+#: gitk:124
+msgid "Reading"
+msgstr "Leyendo"
+
+#: gitk:151 gitk:2191
+msgid "Reading commits..."
+msgstr "Leyendo revisiones..."
+
+#: gitk:275
+msgid "Can't parse git log output:"
+msgstr "Error analizando la salida de git log:"
+
+#: gitk:386 gitk:2195
+msgid "No commits selected"
+msgstr "No se seleccionaron revisiones"
+
+#: gitk:500
+msgid "No commit information available"
+msgstr "Falta información sobre las revisiones"
+
+#: gitk:599 gitk:621 gitk:1955 gitk:6423 gitk:7923 gitk:8082
+msgid "OK"
+msgstr "Aceptar"
+
+#: gitk:623 gitk:1956 gitk:6107 gitk:6178 gitk:6275 gitk:6321 gitk:6425
+#: gitk:7924 gitk:8083
+msgid "Cancel"
+msgstr "Cancelar"
+
+#: gitk:661
+msgid "File"
+msgstr "Archivo"
+
+#: gitk:663
+msgid "Update"
+msgstr "Actualizar"
+
+#: gitk:664
+msgid "Reread references"
+msgstr "Releer referencias"
+
+#: gitk:665
+msgid "List references"
+msgstr "Lista de referencias"
+
+#: gitk:666
+msgid "Quit"
+msgstr "Salir"
+
+#: gitk:668
+msgid "Edit"
+msgstr "Editar"
+
+#: gitk:669
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: gitk:672 gitk:1892
+msgid "View"
+msgstr "Vista"
+
+#: gitk:673
+msgid "New view..."
+msgstr "Nueva vista..."
+
+#: gitk:674 gitk:2133 gitk:8722
+msgid "Edit view..."
+msgstr "Modificar vista..."
+
+#: gitk:676 gitk:2134 gitk:8723
+msgid "Delete view"
+msgstr "Eliminar vista"
+
+#: gitk:678
+msgid "All files"
+msgstr "Todos los archivos"
+
+#: gitk:682
+msgid "Help"
+msgstr "Ayuda"
+
+#: gitk:683 gitk:1317
+msgid "About gitk"
+msgstr "Acerca de gitk"
+
+#: gitk:684
+msgid "Key bindings"
+msgstr "Combinaciones de teclas"
+
+#: gitk:741
+msgid "SHA1 ID: "
+msgstr "SHA1 ID: "
+
+#: gitk:791
+msgid "Find"
+msgstr "Buscar"
+
+#: gitk:792
+msgid "next"
+msgstr "<<"
+
+#: gitk:793
+msgid "prev"
+msgstr ">>"
+
+#: gitk:794
+msgid "commit"
+msgstr "revisión"
+
+#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369
+msgid "containing:"
+msgstr "que contiene:"
+
+#: gitk:800 gitk:1778 gitk:1783 gitk:2431
+msgid "touching paths:"
+msgstr "que modifica la ruta:"
+
+#: gitk:801 gitk:2436
+msgid "adding/removing string:"
+msgstr "que añade/elimina cadena:"
+
+#: gitk:810 gitk:812
+msgid "Exact"
+msgstr "Exacto"
+
+#: gitk:812 gitk:2514 gitk:4274
+msgid "IgnCase"
+msgstr "NoMayús"
+
+#: gitk:812 gitk:2405 gitk:2512 gitk:4270
+msgid "Regexp"
+msgstr "Regex"
+
+#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436
+msgid "All fields"
+msgstr "Todos los campos"
+
+#: gitk:815 gitk:2531 gitk:2563 gitk:4336
+msgid "Headline"
+msgstr "Título"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827
+msgid "Comments"
+msgstr "Comentarios"
+
+#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5956
+#: gitk:5971
+msgid "Author"
+msgstr "Autor"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4765
+msgid "Committer"
+msgstr ""
+
+#: gitk:845
+msgid "Search"
+msgstr "Buscar"
+
+#: gitk:852
+msgid "Diff"
+msgstr "Diferencia"
+
+#: gitk:854
+msgid "Old version"
+msgstr "Versión antigua"
+
+#: gitk:856
+msgid "New version"
+msgstr "Versión nueva"
+
+#: gitk:858
+msgid "Lines of context"
+msgstr "Líneas de contexto"
+
+#: gitk:868
+msgid "Ignore space change"
+msgstr "Ignora cambios de espaciado"
+
+#: gitk:926
+msgid "Patch"
+msgstr "Parche"
+
+#: gitk:928
+msgid "Tree"
+msgstr "Árbol"
+
+#: gitk:1053 gitk:1068 gitk:6022
+msgid "Diff this -> selected"
+msgstr "Diferencia de esta -> seleccionada"
+
+#: gitk:1055 gitk:1070 gitk:6023
+msgid "Diff selected -> this"
+msgstr "Diferencia de seleccionada -> esta"
+
+#: gitk:1057 gitk:1072 gitk:6024
+msgid "Make patch"
+msgstr "Crear patch"
+
+#: gitk:1058 gitk:6162
+msgid "Create tag"
+msgstr "Crear etiqueta"
+
+#: gitk:1059 gitk:6255
+msgid "Write commit to file"
+msgstr "Escribir revisiones a archivo"
+
+#: gitk:1060 gitk:6309
+msgid "Create new branch"
+msgstr "Crear nueva rama"
+
+#: gitk:1061
+msgid "Cherry-pick this commit"
+msgstr "Añadir esta revisión a la rama actual (cherry-pick)"
+
+#: gitk:1063
+msgid "Reset HEAD branch to here"
+msgstr "Traer la rama HEAD aquí"
+
+#: gitk:1079
+msgid "Check out this branch"
+msgstr "Cambiar a esta rama"
+
+#: gitk:1081
+msgid "Remove this branch"
+msgstr "Eliminar esta rama"
+
+#: gitk:1087
+msgid "Highlight this too"
+msgstr "Seleccionar también"
+
+#: gitk:1089
+msgid "Highlight this only"
+msgstr "Seleccionar sólo"
+
+#: gitk:1318
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - un visualizador de revisiones para git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Uso y redistribución permitidos según los términos de la Licencia Pública General de "
+"GNU (GNU GPL)"
+
+#: gitk:1326 gitk:1387 gitk:6581
+msgid "Close"
+msgstr "Cerrar"
+
+#: gitk:1345
+msgid "Gitk key bindings"
+msgstr "Combinaciones de tecla de Gitk"
+
+#: gitk:1347
+msgid "Gitk key bindings:"
+msgstr "Combinaciones de tecla de Gitk:"
+
+#: gitk:1349
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tSalir"
+
+#: gitk:1350
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tIr a la primera revisión"
+
+#: gitk:1351
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tIr a la última revisión"
+
+#: gitk:1352
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tSubir una revisión"
+
+#: gitk:1353
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tBajar una revisión"
+
+#: gitk:1354
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tRetroceder en la historia"
+
+#: gitk:1355
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tAvanzar en la historia"
+
+#: gitk:1356
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tSubir una página en la lista de revisiones"
+
+#: gitk:1357
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tBajar una página en la lista de revisiones"
+
+#: gitk:1358
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tDesplazarse al inicio de la lista de revisiones"
+
+#: gitk:1359
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tDesplazarse al final de la lista de revisiones"
+
+#: gitk:1360
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tDesplazar una línea hacia arriba la lista de revisiones"
+
+#: gitk:1361
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tDesplazar una línea hacia abajo la lista de revisiones"
+
+#: gitk:1362
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tDesplazar una página hacia arriba la lista de revisiones"
+
+#: gitk:1363
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tDesplazar una página hacia abajo la lista de revisiones"
+
+#: gitk:1364
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Shift-Up>\tBuscar hacia atrás (arriba, revisiones siguientes)"
+
+#: gitk:1365
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tBuscar hacia adelante (abajo, revisiones anteriores)"
+
+#: gitk:1366
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tDesplaza hacia arriba una página la vista de diferencias"
+
+#: gitk:1367
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tDesplaza hacia arriba una página la vista de diferencias"
+
+#: gitk:1368
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Space>\t\tDesplaza hacia abajo una página la vista de diferencias"
+
+#: gitk:1369
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tDesplaza hacia arriba 18 líneas la vista de diferencias"
+
+#: gitk:1370
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tDesplaza hacia abajo 18 líneas la vista de diferencias"
+
+#: gitk:1371
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tBuscar"
+
+#: gitk:1372
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tBuscar el siguiente"
+
+#: gitk:1373
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tBuscar el siguiente"
+
+#: gitk:1374
+msgid "/\t\tMove to next find hit, or redo find"
+msgstr "/\t\tBuscar el siguiente, o reiniciar la búsqueda"
+
+#: gitk:1375
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tBuscar el anterior"
+
+#: gitk:1376
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente"
+
+#: gitk:1377
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tBuscar siguiente en la vista de diferencias"
+
+#: gitk:1378
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tBuscar anterior en la vista de diferencias"
+
+#: gitk:1379
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tAumentar tamaño del texto"
+
+#: gitk:1380
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tAumentar tamaño del texto"
+
+#: gitk:1381
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tDisminuir tamaño del texto"
+
+#: gitk:1382
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tDisminuir tamaño del texto"
+
+#: gitk:1383
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tActualizar"
+
+#: gitk:1896
+msgid "Gitk view definition"
+msgstr "Definición de vistas de Gitk"
+
+#: gitk:1921
+msgid "Name"
+msgstr "Nombre"
+
+#: gitk:1924
+msgid "Remember this view"
+msgstr "Recordar esta vista"
+
+#: gitk:1928
+msgid "Commits to include (arguments to git rev-list):"
+msgstr "Revisiones a incluir (argumentos a git rev-list):"
+
+#: gitk:1935
+msgid "Command to generate more commits to include:"
+msgstr "Comando que genera más revisiones a incluir:"
+
+#: gitk:1942
+msgid "Enter files and directories to include, one per line:"
+msgstr "Introducir archivos y directorios a incluir, uno por línea:"
+
+#: gitk:1989
+msgid "Error in commit selection arguments:"
+msgstr "Error en los argumentos de selección de las revisiones:"
+
+#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8688 gitk:8689
+msgid "None"
+msgstr "Ninguno"
+
+#: gitk:2531 gitk:4336 gitk:5958 gitk:5973
+msgid "Date"
+msgstr "Fecha"
+
+#: gitk:2531 gitk:4336
+msgid "CDate"
+msgstr "Fecha de creación"
+
+#: gitk:2680 gitk:2685
+msgid "Descendant"
+msgstr "Descendiente"
+
+#: gitk:2681
+msgid "Not descendant"
+msgstr "No descendiente"
+
+#: gitk:2688 gitk:2693
+msgid "Ancestor"
+msgstr "Antepasado"
+
+#: gitk:2689
+msgid "Not ancestor"
+msgstr "No antepasado"
+
+#: gitk:2924
+msgid "Local changes checked in to index but not committed"
+msgstr "Cambios locales añadidos al índice pero sin completar revisión"
+
+#: gitk:2954
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Cambios locales sin añadir al índice"
+
+#: gitk:4305
+msgid "Searching"
+msgstr "Buscando"
+
+#: gitk:4767
+msgid "Tags:"
+msgstr "Etiquetas:"
+
+#: gitk:4784 gitk:4790 gitk:5951
+msgid "Parent"
+msgstr "Padre"
+
+#: gitk:4795
+msgid "Child"
+msgstr "Hija"
+
+#: gitk:4804
+msgid "Branch"
+msgstr "Rama"
+
+#: gitk:4807
+msgid "Follows"
+msgstr "Sigue-a"
+
+#: gitk:4810
+msgid "Precedes"
+msgstr "Precede-a"
+
+#: gitk:5093
+msgid "Error getting merge diffs:"
+msgstr "Error al leer las diferencias de fusión:"
+
+#: gitk:5778
+msgid "Goto:"
+msgstr "Ir a:"
+
+#: gitk:5780
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:5805
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "La id SHA1 abreviada %s es ambigua"
+
+#: gitk:5817
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "La id SHA1 %s es desconocida"
+
+#: gitk:5819
+#, tcl-format
+msgid "Tag/Head %s is not known"
+msgstr "La etiqueta/rama %s es deconocida"
+
+#: gitk:5961
+msgid "Children"
+msgstr "Hijas"
+
+#: gitk:6018
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Poner la rama %s en esta revisión"
+
+#: gitk:6049
+msgid "Top"
+msgstr "Origen"
+
+#: gitk:6050
+msgid "From"
+msgstr "De"
+
+#: gitk:6055
+msgid "To"
+msgstr "A"
+
+#: gitk:6078
+msgid "Generate patch"
+msgstr "Generar parche"
+
+#: gitk:6080
+msgid "From:"
+msgstr "De:"
+
+#: gitk:6089
+msgid "To:"
+msgstr "Para:"
+
+#: gitk:6098
+msgid "Reverse"
+msgstr "Invertir"
+
+#: gitk:6100 gitk:6269
+msgid "Output file:"
+msgstr "Escribir a archivo:"
+
+#: gitk:6106
+msgid "Generate"
+msgstr "Generar"
+
+#: gitk:6142
+msgid "Error creating patch:"
+msgstr "Error en la creación del parche:"
+
+#: gitk:6164 gitk:6257 gitk:6311
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:6173
+msgid "Tag name:"
+msgstr "Nombre de etiqueta:"
+
+#: gitk:6177 gitk:6320
+msgid "Create"
+msgstr "Crear"
+
+#: gitk:6192
+msgid "No tag name specified"
+msgstr "No se ha especificado etiqueta"
+
+#: gitk:6196
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "La etiqueta \"%s\" ya existe"
+
+#: gitk:6202
+msgid "Error creating tag:"
+msgstr "Error al crear la etiqueta:"
+
+#: gitk:6266
+msgid "Command:"
+msgstr "Comando:"
+
+#: gitk:6274
+msgid "Write"
+msgstr "Escribir"
+
+#: gitk:6290
+msgid "Error writing commit:"
+msgstr "Error al escribir revisión:"
+
+#: gitk:6316
+msgid "Name:"
+msgstr "Nombre:"
+
+#: gitk:6335
+msgid "Please specify a name for the new branch"
+msgstr "Especifique un nombre para la nueva rama"
+
+#: gitk:6364
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "La revisión %s ya está incluida en la rama %s -- ¿Volver a aplicarla?"
+
+#: gitk:6369
+msgid "Cherry-picking"
+msgstr "Eligiendo revisiones (cherry-picking)"
+
+#: gitk:6381
+msgid "No changes committed"
+msgstr "No se han guardado cambios"
+
+#: gitk:6404
+msgid "Confirm reset"
+msgstr "Confirmar git reset"
+
+#: gitk:6406
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "¿Reponer la rama %s a %s?"
+
+#: gitk:6410
+msgid "Reset type:"
+msgstr "Tipo de reposición:"
+
+#: gitk:6414
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Suave: No altera la copia de trabajo ni el índice"
+
+#: gitk:6417
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Mixta: Actualiza el índice, no altera la copia de trabajo"
+
+#: gitk:6420
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Dura: Actualiza el índice y la copia de trabajo\n"
+"(abandona TODAS las modificaciones locales)"
+
+#: gitk:6436
+msgid "Resetting"
+msgstr "Reponiendo"
+
+#: gitk:6493
+msgid "Checking out"
+msgstr "Creando copia de trabajo"
+
+#: gitk:6523
+msgid "Cannot delete the currently checked-out branch"
+msgstr "No se puede borrar la rama actual"
+
+#: gitk:6529
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Las revisiones de la rama %s no están presentes en otras ramas.\n"
+"¿Borrar la rama %s?"
+
+#: gitk:6560
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Etiquetas y ramas: %s"
+
+#: gitk:6574
+msgid "Filter"
+msgstr "Filtro"
+
+#: gitk:6868
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Error al leer la topología de revisiones: la información sobre "
+"las ramas y etiquetas precedentes y siguientes será incompleta."
+
+#: gitk:7852
+msgid "Tag"
+msgstr "Etiqueta"
+
+#: gitk:7852
+msgid "Id"
+msgstr "Id"
+
+#: gitk:7892
+msgid "Gitk font chooser"
+msgstr "Selector de tipografías gitk"
+
+#: gitk:7909
+msgid "B"
+msgstr "B"
+
+#: gitk:7912
+msgid "I"
+msgstr "I"
+
+#: gitk:8005
+msgid "Gitk preferences"
+msgstr "Preferencias de gitk"
+
+#: gitk:8006
+msgid "Commit list display options"
+msgstr "Opciones de visualización de la lista de revisiones"
+
+#: gitk:8009
+msgid "Maximum graph width (lines)"
+msgstr "Ancho máximo del gráfico (en líneas)"
+
+#: gitk:8013
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Ancho máximo del gráfico (en % del panel)"
+
+#: gitk:8018
+msgid "Show local changes"
+msgstr "Mostrar cambios locales"
+
+#: gitk:8023
+msgid "Auto-select SHA1"
+msgstr "Seleccionar automáticamente SHA1 hash"
+
+#: gitk:8028
+msgid "Diff display options"
+msgstr "Opciones de visualización de diferencias"
+
+#: gitk:8030
+msgid "Tab spacing"
+msgstr "Espaciado de tabulador"
+
+#: gitk:8034
+msgid "Display nearby tags"
+msgstr "Mostrar etiquetas cercanas"
+
+#: gitk:8039
+msgid "Limit diffs to listed paths"
+msgstr "Limitar las diferencias a las rutas seleccionadas"
+
+#: gitk:8044
+msgid "Colors: press to choose"
+msgstr "Colores: pulse para seleccionar"
+
+#: gitk:8047
+msgid "Background"
+msgstr "Fondo"
+
+#: gitk:8051
+msgid "Foreground"
+msgstr "Primer plano"
+
+#: gitk:8055
+msgid "Diff: old lines"
+msgstr "Diff: líneas viejas"
+
+#: gitk:8060
+msgid "Diff: new lines"
+msgstr "Diff: líneas nuevas"
+
+#: gitk:8065
+msgid "Diff: hunk header"
+msgstr "Diff: cabecera de fragmento"
+
+#: gitk:8071
+msgid "Select bg"
+msgstr "Color de fondo de la selección"
+
+#: gitk:8075
+msgid "Fonts: press to choose"
+msgstr "Tipografías: pulse para elegir"
+
+#: gitk:8077
+msgid "Main font"
+msgstr "Tipografía principal"
+
+#: gitk:8078
+msgid "Diff display font"
+msgstr "Tipografía para diferencias"
+
+#: gitk:8079
+msgid "User interface font"
+msgstr "Tipografía para interfaz de usuario"
+
+#: gitk:8095
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: elegir color para %s"
+
+#: gitk:8476
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Esta versión de Tcl/Tk es demasiado antigua.\n"
+" Gitk requiere Tcl/Tk versión 8.4 o superior."
+
+#: gitk:8565
+msgid "Cannot find a git repository here."
+msgstr "No hay un repositorio git aquí."
+
+#: gitk:8569
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "No hay directorio git \"%s\"."
+
+#: gitk:8612
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Argumento ambiguo: '%s' es tanto una revisión como un nombre de archivo"
+
+#: gitk:8624
+msgid "Bad arguments to gitk:"
+msgstr "Argumentos incorrectos a Gitk:"
+
+#: gitk:8636
+msgid "Couldn't get list of unmerged files:"
+msgstr "Imposible obtener la lista de archivos pendientes de fusión:"
+
+#: gitk:8652
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"No hay archivos seleccionados: se seleccionó la opción --merge pero no hay "
+"archivos pendientes de fusión."
+
+#: gitk:8655
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"No hay archivos seleccionados: se seleccionó la opción --merge pero los archivos "
+"especificados no necesitan fusión."
+
+#: gitk:8716
+msgid "Command line"
+msgstr "Línea de comandos"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po
new file mode 100644 (file)
index 0000000..f6b080d
--- /dev/null
@@ -0,0 +1,887 @@
+# Swedish translation for gitk
+# Copyright (C) 2005-2008 Paul Mackerras
+# This file is distributed under the same license as the gitk package.
+#
+# Peter Karlsson <peter@softwolves.pp.se>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: sv\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-14 15:03+0100\n"
+"PO-Revision-Date: 2008-03-14 16:06CET-1\n"
+"Last-Translator: Peter Karlsson <peter@softwolves.pp.se>\n"
+"Language-Team: Swedish <sv@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit"
+
+#: gitk:111
+msgid "Error executing git rev-list:"
+msgstr "Fel vid körning av git rev-list:"
+
+#: gitk:124
+msgid "Reading"
+msgstr "Läser"
+
+#: gitk:151 gitk:2191
+msgid "Reading commits..."
+msgstr "Läser incheckningar..."
+
+#: gitk:275
+msgid "Can't parse git log output:"
+msgstr "Kan inte tolka utdata från git log:"
+
+#: gitk:386 gitk:2195
+msgid "No commits selected"
+msgstr "Inga incheckningar markerade"
+
+#: gitk:500
+msgid "No commit information available"
+msgstr "Ingen incheckningsinformation är tillgänglig"
+
+#: gitk:599 gitk:621 gitk:1955 gitk:6423 gitk:7923 gitk:8082
+msgid "OK"
+msgstr "OK"
+
+#: gitk:623 gitk:1956 gitk:6107 gitk:6178 gitk:6275 gitk:6321 gitk:6425
+#: gitk:7924 gitk:8083
+msgid "Cancel"
+msgstr "Avbryt"
+
+#: gitk:661
+msgid "File"
+msgstr "Arkiv"
+
+#: gitk:663
+msgid "Update"
+msgstr "Uppdatera"
+
+#: gitk:664
+msgid "Reread references"
+msgstr "Läs om referenser"
+
+#: gitk:665
+msgid "List references"
+msgstr "Visa referenser"
+
+#: gitk:666
+msgid "Quit"
+msgstr "Avsluta"
+
+#: gitk:668
+msgid "Edit"
+msgstr "Redigera"
+
+#: gitk:669
+msgid "Preferences"
+msgstr "Inställningar"
+
+#: gitk:672 gitk:1892
+msgid "View"
+msgstr "Visa"
+
+#: gitk:673
+msgid "New view..."
+msgstr "Ny vy..."
+
+#: gitk:674 gitk:2133 gitk:8722
+msgid "Edit view..."
+msgstr "Ändra vy..."
+
+#: gitk:676 gitk:2134 gitk:8723
+msgid "Delete view"
+msgstr "Ta bort vy"
+
+#: gitk:678
+msgid "All files"
+msgstr "Alla filer"
+
+#: gitk:682
+msgid "Help"
+msgstr "Hjälp"
+
+#: gitk:683 gitk:1317
+msgid "About gitk"
+msgstr "Om gitk"
+
+#: gitk:684
+msgid "Key bindings"
+msgstr "Tangentbordsbindningar"
+
+#: gitk:741
+msgid "SHA1 ID: "
+msgstr "SHA1-id: "
+
+#: gitk:791
+msgid "Find"
+msgstr "Sök"
+
+#: gitk:792
+msgid "next"
+msgstr "nästa"
+
+#: gitk:793
+msgid "prev"
+msgstr "föreg"
+
+#: gitk:794
+msgid "commit"
+msgstr "incheckning"
+
+#: gitk:797 gitk:799 gitk:2356 gitk:2379 gitk:2403 gitk:4306 gitk:4369
+msgid "containing:"
+msgstr "som innehåller:"
+
+#: gitk:800 gitk:1778 gitk:1783 gitk:2431
+msgid "touching paths:"
+msgstr "som rör sökväg:"
+
+#: gitk:801 gitk:2436
+msgid "adding/removing string:"
+msgstr "som lägger/till tar bort sträng:"
+
+#: gitk:810 gitk:812
+msgid "Exact"
+msgstr "Exakt"
+
+#: gitk:812 gitk:2514 gitk:4274
+msgid "IgnCase"
+msgstr "IgnVersaler"
+
+#: gitk:812 gitk:2405 gitk:2512 gitk:4270
+msgid "Regexp"
+msgstr "Reg.uttr."
+
+#: gitk:814 gitk:815 gitk:2533 gitk:2563 gitk:2570 gitk:4380 gitk:4436
+msgid "All fields"
+msgstr "Alla fält"
+
+#: gitk:815 gitk:2531 gitk:2563 gitk:4336
+msgid "Headline"
+msgstr "Rubrik"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4436 gitk:4827
+msgid "Comments"
+msgstr "Kommentarer"
+
+#: gitk:816 gitk:2531 gitk:2535 gitk:2570 gitk:4336 gitk:4763 gitk:5956
+#: gitk:5971
+msgid "Author"
+msgstr "Författare"
+
+#: gitk:816 gitk:2531 gitk:4336 gitk:4765
+msgid "Committer"
+msgstr "Incheckare"
+
+#: gitk:845
+msgid "Search"
+msgstr "Sök"
+
+#: gitk:852
+msgid "Diff"
+msgstr "Diff"
+
+#: gitk:854
+msgid "Old version"
+msgstr "Gammal version"
+
+#: gitk:856
+msgid "New version"
+msgstr "Ny version"
+
+#: gitk:858
+msgid "Lines of context"
+msgstr "Rader sammanhang"
+
+#: gitk:868
+msgid "Ignore space change"
+msgstr "Ignorera ändringar i blanksteg"
+
+#: gitk:926
+msgid "Patch"
+msgstr "Patch"
+
+#: gitk:928
+msgid "Tree"
+msgstr "Träd"
+
+#: gitk:1053 gitk:1068 gitk:6022
+msgid "Diff this -> selected"
+msgstr "Diff denna -> markerad"
+
+#: gitk:1055 gitk:1070 gitk:6023
+msgid "Diff selected -> this"
+msgstr "Diff markerad -> denna"
+
+#: gitk:1057 gitk:1072 gitk:6024
+msgid "Make patch"
+msgstr "Skapa patch"
+
+#: gitk:1058 gitk:6162
+msgid "Create tag"
+msgstr "Skapa tagg"
+
+#: gitk:1059 gitk:6255
+msgid "Write commit to file"
+msgstr "Skriv incheckning till fil"
+
+#: gitk:1060 gitk:6309
+msgid "Create new branch"
+msgstr "Skapa ny gren"
+
+#: gitk:1061
+msgid "Cherry-pick this commit"
+msgstr "Plocka denna incheckning"
+
+#: gitk:1063
+msgid "Reset HEAD branch to here"
+msgstr "Återställ HEAD-grenen hit"
+
+#: gitk:1079
+msgid "Check out this branch"
+msgstr "Checka ut denna gren"
+
+#: gitk:1081
+msgid "Remove this branch"
+msgstr "Ta bort denna gren"
+
+#: gitk:1087
+msgid "Highlight this too"
+msgstr "Markera även detta"
+
+#: gitk:1089
+msgid "Highlight this only"
+msgstr "Markera bara detta"
+
+#: gitk:1318
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - en incheckningsvisare för git\n"
+"\n"
+"Copyright © 2005-2006 Paul Mackerras\n"
+"\n"
+"Använd och vidareförmedla enligt villkoren i GNU General Public License"
+
+#: gitk:1326 gitk:1387 gitk:6581
+msgid "Close"
+msgstr "Stäng"
+
+#: gitk:1345
+msgid "Gitk key bindings"
+msgstr "Tangentbordsbindningar för Gitk"
+
+#: gitk:1347
+msgid "Gitk key bindings:"
+msgstr "Tangentbordsbindningar för Gitk:"
+
+#: gitk:1349
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tAvsluta"
+
+#: gitk:1350
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tGå till första incheckning"
+
+#: gitk:1351
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tGå till sista incheckning"
+
+#: gitk:1352
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Upp>, p, i\tGå en incheckning upp"
+
+#: gitk:1353
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Ned>, n, k\tGå en incheckning ned"
+
+#: gitk:1354
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Vänster>, z, j\tGå bakåt i historiken"
+
+#: gitk:1355
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Höger>, x, l\tGå framåt i historiken"
+
+#: gitk:1356
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tGå upp en sida i incheckningslistan"
+
+#: gitk:1357
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tGå ned en sida i incheckningslistan"
+
+#: gitk:1358
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tRulla till början av incheckningslistan"
+
+#: gitk:1359
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tRulla till slutet av incheckningslistan"
+
+#: gitk:1360
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg"
+
+#: gitk:1361
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg"
+
+#: gitk:1362
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida"
+
+#: gitk:1363
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida"
+
+#: gitk:1364
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)"
+
+#: gitk:1365
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)"
+
+#: gitk:1366
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tRulla diffvisningen upp en sida"
+
+#: gitk:1367
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Baksteg>\tRulla diffvisningen upp en sida"
+
+#: gitk:1368
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Blanksteg>\tRulla diffvisningen ned en sida"
+
+#: gitk:1369
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tRulla diffvisningen upp 18 rader"
+
+#: gitk:1370
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tRulla diffvisningen ned 18 rader"
+
+#: gitk:1371
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tSök"
+
+#: gitk:1372
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tGå till nästa sökträff"
+
+#: gitk:1373
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\t\tGå till nästa sökträff"
+
+#: gitk:1374
+msgid "/\t\tMove to next find hit, or redo find"
+msgstr "/\t\tGå till nästa sökträff, eller sök på nytt"
+
+#: gitk:1375
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tGå till föregående sökträff"
+
+#: gitk:1376
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tRulla diffvisningen till nästa fil"
+
+#: gitk:1377
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen"
+
+#: gitk:1378
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen"
+
+#: gitk:1379
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-Num+>\tÖka teckenstorlek"
+
+#: gitk:1380
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tÖka teckenstorlek"
+
+#: gitk:1381
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-Num->\tMinska teckenstorlek"
+
+#: gitk:1382
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tMinska teckenstorlek"
+
+#: gitk:1383
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tUppdatera"
+
+#: gitk:1896
+msgid "Gitk view definition"
+msgstr "Definition av Gitk-vy"
+
+#: gitk:1921
+msgid "Name"
+msgstr "Namn"
+
+#: gitk:1924
+msgid "Remember this view"
+msgstr "Spara denna vy"
+
+#: gitk:1928
+msgid "Commits to include (arguments to git rev-list):"
+msgstr "Incheckningar att ta med (argument till git rev-list):"
+
+#: gitk:1935
+msgid "Command to generate more commits to include:"
+msgstr "Kommando för att generera fler incheckningar att ta med:"
+
+#: gitk:1942
+msgid "Enter files and directories to include, one per line:"
+msgstr "Ange filer och kataloger att ta med, en per rad:"
+
+#: gitk:1989
+msgid "Error in commit selection arguments:"
+msgstr "Fel i argument för val av incheckningar:"
+
+#: gitk:2043 gitk:2127 gitk:2583 gitk:2597 gitk:3781 gitk:8688 gitk:8689
+msgid "None"
+msgstr "Inget"
+
+#: gitk:2531 gitk:4336 gitk:5958 gitk:5973
+msgid "Date"
+msgstr "Datum"
+
+#: gitk:2531 gitk:4336
+msgid "CDate"
+msgstr "Skapat datum"
+
+#: gitk:2680 gitk:2685
+msgid "Descendant"
+msgstr "Avkomling"
+
+#: gitk:2681
+msgid "Not descendant"
+msgstr "Inte avkomling"
+
+#: gitk:2688 gitk:2693
+msgid "Ancestor"
+msgstr "Förfader"
+
+#: gitk:2689
+msgid "Not ancestor"
+msgstr "Inte förfader"
+
+#: gitk:2924
+msgid "Local changes checked in to index but not committed"
+msgstr "Lokala ändringar sparade i indexet men inte incheckade"
+
+#: gitk:2954
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Lokala ändringar, ej sparade i indexet"
+
+#: gitk:4305
+msgid "Searching"
+msgstr "Söker"
+
+#: gitk:4767
+msgid "Tags:"
+msgstr "Taggar:"
+
+#: gitk:4784 gitk:4790 gitk:5951
+msgid "Parent"
+msgstr "Förälder"
+
+#: gitk:4795
+msgid "Child"
+msgstr "Barn"
+
+#: gitk:4804
+msgid "Branch"
+msgstr "Gren"
+
+#: gitk:4807
+msgid "Follows"
+msgstr "Följer"
+
+#: gitk:4810
+msgid "Precedes"
+msgstr "Föregår"
+
+#: gitk:5093
+msgid "Error getting merge diffs:"
+msgstr "Fel vid hämtning av sammanslagningsdiff:"
+
+#: gitk:5778
+msgid "Goto:"
+msgstr "Gå till:"
+
+#: gitk:5780
+msgid "SHA1 ID:"
+msgstr "SHA1-id:"
+
+#: gitk:5805
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Förkortat SHA1-id %s är tvetydigt"
+
+#: gitk:5817
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA-id:t %s är inte känt"
+
+#: gitk:5819
+#, tcl-format
+msgid "Tag/Head %s is not known"
+msgstr "Tagg/huvud %s är okänt"
+
+#: gitk:5961
+msgid "Children"
+msgstr "Barn"
+
+#: gitk:6018
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Återställ grenen %s hit"
+
+#: gitk:6049
+msgid "Top"
+msgstr "Topp"
+
+#: gitk:6050
+msgid "From"
+msgstr "Från"
+
+#: gitk:6055
+msgid "To"
+msgstr "Till"
+
+#: gitk:6078
+msgid "Generate patch"
+msgstr "Generera patch"
+
+#: gitk:6080
+msgid "From:"
+msgstr "Från:"
+
+#: gitk:6089
+msgid "To:"
+msgstr "Till:"
+
+#: gitk:6098
+msgid "Reverse"
+msgstr "Vänd"
+
+#: gitk:6100 gitk:6269
+msgid "Output file:"
+msgstr "Utdatafil:"
+
+#: gitk:6106
+msgid "Generate"
+msgstr "Generera"
+
+#: gitk:6142
+msgid "Error creating patch:"
+msgstr "Fel vid generering av patch:"
+
+#: gitk:6164 gitk:6257 gitk:6311
+msgid "ID:"
+msgstr "Id:"
+
+#: gitk:6173
+msgid "Tag name:"
+msgstr "Taggnamn:"
+
+#: gitk:6177 gitk:6320
+msgid "Create"
+msgstr "Skapa"
+
+#: gitk:6192
+msgid "No tag name specified"
+msgstr "Inget taggnamn angavs"
+
+#: gitk:6196
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Taggen \"%s\" finns redan"
+
+#: gitk:6202
+msgid "Error creating tag:"
+msgstr "Fel vid skapande av tagg:"
+
+#: gitk:6266
+msgid "Command:"
+msgstr "Kommando:"
+
+#: gitk:6274
+msgid "Write"
+msgstr "Skriv"
+
+#: gitk:6290
+msgid "Error writing commit:"
+msgstr "Fel vid skrivning av incheckning:"
+
+#: gitk:6316
+msgid "Name:"
+msgstr "Namn:"
+
+#: gitk:6335
+msgid "Please specify a name for the new branch"
+msgstr "Ange ett namn för den nya grenen"
+
+#: gitk:6364
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras på nytt?"
+
+#: gitk:6369
+msgid "Cherry-picking"
+msgstr "Plockar"
+
+#: gitk:6381
+msgid "No changes committed"
+msgstr "Inga ändringar incheckade"
+
+#: gitk:6404
+msgid "Confirm reset"
+msgstr "Bekräfta återställning"
+
+#: gitk:6406
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Återställa grenen %s till %s?"
+
+#: gitk:6410
+msgid "Reset type:"
+msgstr "Typ av återställning:"
+
+#: gitk:6414
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Mjuk: Rör inte utcheckning och index"
+
+#: gitk:6417
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr "Blandad: Rör inte utcheckning, återställ index"
+
+#: gitk:6420
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Hård: Återställ utcheckning och index\n"
+"(förkastar ALLA lokala ändringar)"
+
+#: gitk:6436
+msgid "Resetting"
+msgstr "Återställer"
+
+#: gitk:6493
+msgid "Checking out"
+msgstr "Checkar ut"
+
+#: gitk:6523
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Kan inte ta bort den just nu utcheckade grenen"
+
+#: gitk:6529
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Incheckningarna på grenen %s existerar inte på någon annan gren.\n"
+"Vill du verkligen ta bort grenen %s?"
+
+#: gitk:6560
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Taggar och huvuden: %s"
+
+#: gitk:6574
+msgid "Filter"
+msgstr "Filter"
+
+#: gitk:6868
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Fel vid läsning av information om incheckningstopologi; information om "
+"grenar och föregående/senare taggar kommer inte vara komplett."
+
+#: gitk:7852
+msgid "Tag"
+msgstr "Tagg"
+
+#: gitk:7852
+msgid "Id"
+msgstr "Id"
+
+#: gitk:7892
+msgid "Gitk font chooser"
+msgstr "Teckensnittsväljare för Gitk"
+
+#: gitk:7909
+msgid "B"
+msgstr "F"
+
+#: gitk:7912
+msgid "I"
+msgstr "K"
+
+#: gitk:8005
+msgid "Gitk preferences"
+msgstr "Inställningar för Gitk"
+
+#: gitk:8006
+msgid "Commit list display options"
+msgstr "Alternativ för incheckningslistvy"
+
+#: gitk:8009
+msgid "Maximum graph width (lines)"
+msgstr "Maximal grafbredd (rader)"
+
+#: gitk:8013
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Maximal grafbredd (% av ruta)"
+
+#: gitk:8018
+msgid "Show local changes"
+msgstr "Visa lokala ändringar"
+
+#: gitk:8023
+msgid "Auto-select SHA1"
+msgstr "Välj SHA1 automatiskt"
+
+#: gitk:8028
+msgid "Diff display options"
+msgstr "Alternativ för diffvy"
+
+#: gitk:8030
+msgid "Tab spacing"
+msgstr "Blanksteg för tabulatortecken"
+
+#: gitk:8034
+msgid "Display nearby tags"
+msgstr "Visa närliggande taggar"
+
+#: gitk:8039
+msgid "Limit diffs to listed paths"
+msgstr "Begränsa diff till listade sökvägar"
+
+#: gitk:8044
+msgid "Colors: press to choose"
+msgstr "Färger: tryck för att välja"
+
+#: gitk:8047
+msgid "Background"
+msgstr "Bakgrund"
+
+#: gitk:8051
+msgid "Foreground"
+msgstr "Förgrund"
+
+#: gitk:8055
+msgid "Diff: old lines"
+msgstr "Diff: gamla rader"
+
+#: gitk:8060
+msgid "Diff: new lines"
+msgstr "Diff: nya rader"
+
+#: gitk:8065
+msgid "Diff: hunk header"
+msgstr "Diff: delhuvud"
+
+#: gitk:8071
+msgid "Select bg"
+msgstr "Markerad bakgrund"
+
+#: gitk:8075
+msgid "Fonts: press to choose"
+msgstr "Teckensnitt: tryck för att välja"
+
+#: gitk:8077
+msgid "Main font"
+msgstr "Huvudteckensnitt"
+
+#: gitk:8078
+msgid "Diff display font"
+msgstr "Teckensnitt för diffvisning"
+
+#: gitk:8079
+msgid "User interface font"
+msgstr "Teckensnitt för användargränssnitt"
+
+#: gitk:8095
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: välj färg för %s"
+
+#: gitk:8476
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n"
+" Gitk kräver åtminstone Tcl/Tk 8.4."
+
+#: gitk:8565
+msgid "Cannot find a git repository here."
+msgstr "Hittar inget gitk-arkiv här."
+
+#: gitk:8569
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Hittar inte git-katalogen \"%s\"."
+
+#: gitk:8612
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Tvetydigt argument \"%s\": både revision och filnamn"
+
+#: gitk:8624
+msgid "Bad arguments to gitk:"
+msgstr "Felaktiga argument till gitk:"
+
+#: gitk:8636
+msgid "Couldn't get list of unmerged files:"
+msgstr "Kunde inta hämta lista över ej sammanslagna filer:"
+
+#: gitk:8652
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr "Inga filer valdes: --merge angavs men det finns inga filer som inte har slagits samman."
+
+#: gitk:8655
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Inga filer valdes: --merge angavs men det finns inga filer inom "
+"filbegränsningen."
+
+#: gitk:8716
+msgid "Command line"
+msgstr "Kommandorad"
+
index 2facf2db7a9cd034476fa65496ef575f3073c6df..8308e2208e2a0064724367f3bcb6d33282b9e587 100755 (executable)
@@ -866,6 +866,10 @@ sub chop_str {
        my $add_len = shift || 10;
        my $where = shift || 'right'; # 'left' | 'center' | 'right'
 
+       # Make sure perl knows it is utf8 encoded so we don't
+       # cut in the middle of a utf8 multibyte char.
+       $str = to_utf8($str);
+
        # allow only $len chars, but don't cut a word if it would fit in $add_len
        # if it doesn't fit, cut it if it's still longer than the dots we would add
        # remove chopped character entities entirely
diff --git a/graph.c b/graph.c
new file mode 100644 (file)
index 0000000..9d6ed30
--- /dev/null
+++ b/graph.c
@@ -0,0 +1,945 @@
+#include "cache.h"
+#include "commit.h"
+#include "graph.h"
+#include "diff.h"
+#include "revision.h"
+
+/*
+ * TODO:
+ * - Add colors to the graph.
+ *   Pick a color for each column, and print all characters
+ *   in that column with the specified color.
+ *
+ * - Limit the number of columns, similar to the way gitk does.
+ *   If we reach more than a specified number of columns, omit
+ *   sections of some columns.
+ *
+ * - The output during the GRAPH_PRE_COMMIT and GRAPH_COLLAPSING states
+ *   could be made more compact by printing horizontal lines, instead of
+ *   long diagonal lines.  For example, during collapsing, something like
+ *   this:          instead of this:
+ *   | | | | |      | | | | |
+ *   | |_|_|/       | | | |/
+ *   |/| | |        | | |/|
+ *   | | | |        | |/| |
+ *                  |/| | |
+ *                  | | | |
+ *
+ *   If there are several parallel diagonal lines, they will need to be
+ *   replaced with horizontal lines on subsequent rows.
+ */
+
+struct column {
+       /*
+        * The parent commit of this column.
+        */
+       struct commit *commit;
+       /*
+        * XXX: Once we add support for colors, struct column could also
+        * contain the color of its branch line.
+        */
+};
+
+enum graph_state {
+       GRAPH_PADDING,
+       GRAPH_SKIP,
+       GRAPH_PRE_COMMIT,
+       GRAPH_COMMIT,
+       GRAPH_POST_MERGE,
+       GRAPH_COLLAPSING
+};
+
+struct git_graph {
+       /*
+        * The commit currently being processed
+        */
+       struct commit *commit;
+       /*
+        * The number of parents this commit has.
+        * (Stored so we don't have to walk over them each time we need
+        * this number)
+        */
+       int num_parents;
+       /*
+        * The width of the graph output for this commit.
+        * All rows for this commit are padded to this width, so that
+        * messages printed after the graph output are aligned.
+        */
+       int width;
+       /*
+        * The next expansion row to print
+        * when state is GRAPH_PRE_COMMIT
+        */
+       int expansion_row;
+       /*
+        * The current output state.
+        * This tells us what kind of line graph_next_line() should output.
+        */
+       enum graph_state state;
+       /*
+        * The maximum number of columns that can be stored in the columns
+        * and new_columns arrays.  This is also half the number of entries
+        * that can be stored in the mapping and new_mapping arrays.
+        */
+       int column_capacity;
+       /*
+        * The number of columns (also called "branch lines" in some places)
+        */
+       int num_columns;
+       /*
+        * The number of columns in the new_columns array
+        */
+       int num_new_columns;
+       /*
+        * The number of entries in the mapping array
+        */
+       int mapping_size;
+       /*
+        * The column state before we output the current commit.
+        */
+       struct column *columns;
+       /*
+        * The new column state after we output the current commit.
+        * Only valid when state is GRAPH_COLLAPSING.
+        */
+       struct column *new_columns;
+       /*
+        * An array that tracks the current state of each
+        * character in the output line during state GRAPH_COLLAPSING.
+        * Each entry is -1 if this character is empty, or a non-negative
+        * integer if the character contains a branch line.  The value of
+        * the integer indicates the target position for this branch line.
+        * (I.e., this array maps the current column positions to their
+        * desired positions.)
+        *
+        * The maximum capacity of this array is always
+        * sizeof(int) * 2 * column_capacity.
+        */
+       int *mapping;
+       /*
+        * A temporary array for computing the next mapping state
+        * while we are outputting a mapping line.  This is stored as part
+        * of the git_graph simply so we don't have to allocate a new
+        * temporary array each time we have to output a collapsing line.
+        */
+       int *new_mapping;
+};
+
+struct git_graph *graph_init(void)
+{
+       struct git_graph *graph = xmalloc(sizeof(struct git_graph));
+       graph->commit = NULL;
+       graph->num_parents = 0;
+       graph->expansion_row = 0;
+       graph->state = GRAPH_PADDING;
+       graph->num_columns = 0;
+       graph->num_new_columns = 0;
+       graph->mapping_size = 0;
+
+       /*
+        * Allocate a reasonably large default number of columns
+        * We'll automatically grow columns later if we need more room.
+        */
+       graph->column_capacity = 30;
+       graph->columns = xmalloc(sizeof(struct column) *
+                                graph->column_capacity);
+       graph->new_columns = xmalloc(sizeof(struct column) *
+                                    graph->column_capacity);
+       graph->mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
+       graph->new_mapping = xmalloc(sizeof(int) * 2 * graph->column_capacity);
+
+       return graph;
+}
+
+void graph_release(struct git_graph *graph)
+{
+       free(graph->columns);
+       free(graph->new_columns);
+       free(graph->mapping);
+       free(graph);
+}
+
+static void graph_ensure_capacity(struct git_graph *graph, int num_columns)
+{
+       if (graph->column_capacity >= num_columns)
+               return;
+
+       do {
+               graph->column_capacity *= 2;
+       } while (graph->column_capacity < num_columns);
+
+       graph->columns = xrealloc(graph->columns,
+                                 sizeof(struct column) *
+                                 graph->column_capacity);
+       graph->new_columns = xrealloc(graph->new_columns,
+                                     sizeof(struct column) *
+                                     graph->column_capacity);
+       graph->mapping = xrealloc(graph->mapping,
+                                 sizeof(int) * 2 * graph->column_capacity);
+       graph->new_mapping = xrealloc(graph->new_mapping,
+                                     sizeof(int) * 2 * graph->column_capacity);
+}
+
+static void graph_insert_into_new_columns(struct git_graph *graph,
+                                         struct commit *commit,
+                                         int *mapping_index)
+{
+       int i;
+
+       /*
+        * Ignore uinteresting and pruned commits
+        */
+       if (commit->object.flags & (UNINTERESTING | TREESAME))
+               return;
+
+       /*
+        * If the commit is already in the new_columns list, we don't need to
+        * add it.  Just update the mapping correctly.
+        */
+       for (i = 0; i < graph->num_new_columns; i++) {
+               if (graph->new_columns[i].commit == commit) {
+                       graph->mapping[*mapping_index] = i;
+                       *mapping_index += 2;
+                       return;
+               }
+       }
+
+       /*
+        * This commit isn't already in new_columns.  Add it.
+        */
+       graph->new_columns[graph->num_new_columns].commit = commit;
+       graph->mapping[*mapping_index] = graph->num_new_columns;
+       *mapping_index += 2;
+       graph->num_new_columns++;
+}
+
+static void graph_update_width(struct git_graph *graph,
+                              int is_commit_in_existing_columns)
+{
+       /*
+        * Compute the width needed to display the graph for this commit.
+        * This is the maximum width needed for any row.  All other rows
+        * will be padded to this width.
+        *
+        * Compute the number of columns in the widest row:
+        * Count each existing column (graph->num_columns), and each new
+        * column added by this commit.
+        */
+       int max_cols = graph->num_columns + graph->num_parents;
+
+       /*
+        * Even if the current commit has no parents, it still takes up a
+        * column for itself.
+        */
+       if (graph->num_parents < 1)
+               max_cols++;
+
+       /*
+        * We added a column for the the current commit as part of
+        * graph->num_parents.  If the current commit was already in
+        * graph->columns, then we have double counted it.
+        */
+       if (is_commit_in_existing_columns)
+               max_cols--;
+
+       /*
+        * Each column takes up 2 spaces
+        */
+       graph->width = max_cols * 2;
+}
+
+static void graph_update_columns(struct git_graph *graph)
+{
+       struct commit_list *parent;
+       struct column *tmp_columns;
+       int max_new_columns;
+       int mapping_idx;
+       int i, seen_this, is_commit_in_columns;
+
+       /*
+        * Swap graph->columns with graph->new_columns
+        * graph->columns contains the state for the previous commit,
+        * and new_columns now contains the state for our commit.
+        *
+        * We'll re-use the old columns array as storage to compute the new
+        * columns list for the commit after this one.
+        */
+       tmp_columns = graph->columns;
+       graph->columns = graph->new_columns;
+       graph->num_columns = graph->num_new_columns;
+
+       graph->new_columns = tmp_columns;
+       graph->num_new_columns = 0;
+
+       /*
+        * Now update new_columns and mapping with the information for the
+        * commit after this one.
+        *
+        * First, make sure we have enough room.  At most, there will
+        * be graph->num_columns + graph->num_parents columns for the next
+        * commit.
+        */
+       max_new_columns = graph->num_columns + graph->num_parents;
+       graph_ensure_capacity(graph, max_new_columns);
+
+       /*
+        * Clear out graph->mapping
+        */
+       graph->mapping_size = 2 * max_new_columns;
+       for (i = 0; i < graph->mapping_size; i++)
+               graph->mapping[i] = -1;
+
+       /*
+        * Populate graph->new_columns and graph->mapping
+        *
+        * Some of the parents of this commit may already be in
+        * graph->columns.  If so, graph->new_columns should only contain a
+        * single entry for each such commit.  graph->mapping should
+        * contain information about where each current branch line is
+        * supposed to end up after the collapsing is performed.
+        */
+       seen_this = 0;
+       mapping_idx = 0;
+       is_commit_in_columns = 1;
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       is_commit_in_columns = 0;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       for (parent = graph->commit->parents;
+                            parent;
+                            parent = parent->next) {
+                               graph_insert_into_new_columns(graph,
+                                                             parent->item,
+                                                             &mapping_idx);
+                       }
+               } else {
+                       graph_insert_into_new_columns(graph, col_commit,
+                                                     &mapping_idx);
+               }
+       }
+
+       /*
+        * Shrink mapping_size to be the minimum necessary
+        */
+       while (graph->mapping_size > 1 &&
+              graph->mapping[graph->mapping_size - 1] < 0)
+               graph->mapping_size--;
+
+       /*
+        * Compute graph->width for this commit
+        */
+       graph_update_width(graph, is_commit_in_columns);
+}
+
+void graph_update(struct git_graph *graph, struct commit *commit)
+{
+       struct commit_list *parent;
+
+       /*
+        * Set the new commit
+        */
+       graph->commit = commit;
+
+       /*
+        * Count how many parents this commit has
+        */
+       graph->num_parents = 0;
+       for (parent = commit->parents; parent; parent = parent->next)
+               graph->num_parents++;
+
+       /*
+        * Call graph_update_columns() to update
+        * columns, new_columns, and mapping.
+        */
+       graph_update_columns(graph);
+
+       graph->expansion_row = 0;
+
+       /*
+        * Update graph->state.
+        *
+        * If the previous commit didn't get to the GRAPH_PADDING state,
+        * it never finished its output.  Goto GRAPH_SKIP, to print out
+        * a line to indicate that portion of the graph is missing.
+        *
+        * Otherwise, if there are 3 or more parents, we need to print
+        * extra rows before the commit, to expand the branch lines around
+        * it and make room for it.
+        *
+        * If there are less than 3 parents, we can immediately print the
+        * commit line.
+        */
+       if (graph->state != GRAPH_PADDING)
+               graph->state = GRAPH_SKIP;
+       else if (graph->num_parents >= 3)
+               graph->state = GRAPH_PRE_COMMIT;
+       else
+               graph->state = GRAPH_COMMIT;
+}
+
+static int graph_is_mapping_correct(struct git_graph *graph)
+{
+       int i;
+
+       /*
+        * The mapping is up to date if each entry is at its target,
+        * or is 1 greater than its target.
+        * (If it is 1 greater than the target, '/' will be printed, so it
+        * will look correct on the next row.)
+        */
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->mapping[i];
+               if (target < 0)
+                       continue;
+               if (target == (i / 2))
+                       continue;
+               return 0;
+       }
+
+       return 1;
+}
+
+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
+{
+       /*
+        * Add additional spaces to the end of the strbuf, so that all
+        * lines for a particular commit have the same width.
+        *
+        * This way, fields printed to the right of the graph will remain
+        * aligned for the entire commit.
+        */
+       int extra;
+       if (sb->len >= graph->width)
+               return;
+
+       extra = graph->width - sb->len;
+       strbuf_addf(sb, "%*s", (int) extra, "");
+}
+
+static void graph_output_padding_line(struct git_graph *graph,
+                                     struct strbuf *sb)
+{
+       int i;
+
+       /*
+        * We could conceivable be called with a NULL commit
+        * if our caller has a bug, and invokes graph_next_line()
+        * immediately after graph_init(), without first calling
+        * graph_update().  Return without outputting anything in this
+        * case.
+        */
+       if (!graph->commit)
+               return;
+
+       /*
+        * Output a padding row, that leaves all branch lines unchanged
+        */
+       for (i = 0; i < graph->num_new_columns; i++) {
+               strbuf_addstr(sb, "| ");
+       }
+
+       graph_pad_horizontally(graph, sb);
+}
+
+static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
+{
+       /*
+        * Output an ellipsis to indicate that a portion
+        * of the graph is missing.
+        */
+       strbuf_addstr(sb, "...");
+       graph_pad_horizontally(graph, sb);
+
+       if (graph->num_parents >= 3)
+               graph->state = GRAPH_PRE_COMMIT;
+       else
+               graph->state = GRAPH_COMMIT;
+}
+
+static void graph_output_pre_commit_line(struct git_graph *graph,
+                                        struct strbuf *sb)
+{
+       int num_expansion_rows;
+       int i, seen_this;
+
+       /*
+        * This function formats a row that increases the space around a commit
+        * with multiple parents, to make room for it.  It should only be
+        * called when there are 3 or more parents.
+        *
+        * We need 2 extra rows for every parent over 2.
+        */
+       assert(graph->num_parents >= 3);
+       num_expansion_rows = (graph->num_parents - 2) * 2;
+
+       /*
+        * graph->expansion_row tracks the current expansion row we are on.
+        * It should be in the range [0, num_expansion_rows - 1]
+        */
+       assert(0 <= graph->expansion_row &&
+              graph->expansion_row < num_expansion_rows);
+
+       /*
+        * Output the row
+        */
+       seen_this = 0;
+       for (i = 0; i < graph->num_columns; i++) {
+               struct column *col = &graph->columns[i];
+               if (col->commit == graph->commit) {
+                       seen_this = 1;
+                       strbuf_addf(sb, "| %*s", graph->expansion_row, "");
+               } else if (seen_this) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Increment graph->expansion_row,
+        * and move to state GRAPH_COMMIT if necessary
+        */
+       graph->expansion_row++;
+       if (graph->expansion_row >= num_expansion_rows)
+               graph->state = GRAPH_COMMIT;
+}
+
+void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int seen_this = 0;
+       int i, j;
+
+       /*
+        * Output the row containing this commit
+        * Iterate up to and including graph->num_columns,
+        * since the current commit may not be in any of the existing
+        * columns.  (This happens when the current commit doesn't have any
+        * children that we have already processed.)
+        */
+       seen_this = 0;
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       if (graph->num_parents > 1)
+                               strbuf_addch(sb, 'M');
+                       else
+                               strbuf_addch(sb, '*');
+
+                       if (graph->num_parents < 2)
+                               strbuf_addch(sb, ' ');
+                       else if (graph->num_parents == 2)
+                               strbuf_addstr(sb, "  ");
+                       else {
+                               int num_dashes =
+                                       ((graph->num_parents - 2) * 2) - 1;
+                               for (j = 0; j < num_dashes; j++)
+                                       strbuf_addch(sb, '-');
+                               strbuf_addstr(sb, ". ");
+                       }
+               } else if (seen_this && (graph->num_parents > 1)) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Update graph->state
+        */
+       if (graph->num_parents > 1)
+               graph->state = GRAPH_POST_MERGE;
+       else if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+       else
+               graph->state = GRAPH_COLLAPSING;
+}
+
+void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int seen_this = 0;
+       int i, j;
+
+       /*
+        * Output the post-merge row
+        */
+       for (i = 0; i <= graph->num_columns; i++) {
+               struct commit *col_commit;
+               if (i == graph->num_columns) {
+                       if (seen_this)
+                               break;
+                       col_commit = graph->commit;
+               } else {
+                       col_commit = graph->columns[i].commit;
+               }
+
+               if (col_commit == graph->commit) {
+                       seen_this = 1;
+                       strbuf_addch(sb, '|');
+                       for (j = 0; j < graph->num_parents - 1; j++)
+                               strbuf_addstr(sb, "\\ ");
+                       if (graph->num_parents == 2)
+                               strbuf_addch(sb, ' ');
+               } else if (seen_this && (graph->num_parents > 2)) {
+                       strbuf_addstr(sb, "\\ ");
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Update graph->state
+        */
+       if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+       else
+               graph->state = GRAPH_COLLAPSING;
+}
+
+void graph_output_collapsing_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int i;
+       int *tmp_mapping;
+
+       /*
+        * Clear out the new_mapping array
+        */
+       for (i = 0; i < graph->mapping_size; i++)
+               graph->new_mapping[i] = -1;
+
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->mapping[i];
+               if (target < 0)
+                       continue;
+
+               /*
+                * Since update_columns() always inserts the leftmost
+                * column first, each branch's target location should
+                * always be either its current location or to the left of
+                * its current location.
+                *
+                * We never have to move branches to the right.  This makes
+                * the graph much more legible, since whenever branches
+                * cross, only one is moving directions.
+                */
+               assert(target * 2 <= i);
+
+               if (target * 2 == i) {
+                       /*
+                        * This column is already in the
+                        * correct place
+                        */
+                       assert(graph->new_mapping[i] == -1);
+                       graph->new_mapping[i] = target;
+               } else if (graph->new_mapping[i - 1] < 0) {
+                       /*
+                        * Nothing is to the left.
+                        * Move to the left by one
+                        */
+                       graph->new_mapping[i - 1] = target;
+               } else if (graph->new_mapping[i - 1] == target) {
+                       /*
+                        * There is a branch line to our left
+                        * already, and it is our target.  We
+                        * combine with this line, since we share
+                        * the same parent commit.
+                        *
+                        * We don't have to add anything to the
+                        * output or new_mapping, since the
+                        * existing branch line has already taken
+                        * care of it.
+                        */
+               } else {
+                       /*
+                        * There is a branch line to our left,
+                        * but it isn't our target.  We need to
+                        * cross over it.
+                        *
+                        * The space just to the left of this
+                        * branch should always be empty.
+                        */
+                       assert(graph->new_mapping[i - 1] > target);
+                       assert(graph->new_mapping[i - 2] < 0);
+                       graph->new_mapping[i - 2] = target;
+               }
+       }
+
+       /*
+        * The new mapping may be 1 smaller than the old mapping
+        */
+       if (graph->new_mapping[graph->mapping_size - 1] < 0)
+               graph->mapping_size--;
+
+       /*
+        * Output out a line based on the new mapping info
+        */
+       for (i = 0; i < graph->mapping_size; i++) {
+               int target = graph->new_mapping[i];
+               if (target < 0)
+                       strbuf_addch(sb, ' ');
+               else if (target * 2 == i)
+                       strbuf_addch(sb, '|');
+               else
+                       strbuf_addch(sb, '/');
+       }
+
+       graph_pad_horizontally(graph, sb);
+
+       /*
+        * Swap mapping and new_mapping
+        */
+       tmp_mapping = graph->mapping;
+       graph->mapping = graph->new_mapping;
+       graph->new_mapping = tmp_mapping;
+
+       /*
+        * If graph->mapping indicates that all of the branch lines
+        * are already in the correct positions, we are done.
+        * Otherwise, we need to collapse some branch lines together.
+        */
+       if (graph_is_mapping_correct(graph))
+               graph->state = GRAPH_PADDING;
+}
+
+int graph_next_line(struct git_graph *graph, struct strbuf *sb)
+{
+       switch (graph->state) {
+       case GRAPH_PADDING:
+               graph_output_padding_line(graph, sb);
+               return 0;
+       case GRAPH_SKIP:
+               graph_output_skip_line(graph, sb);
+               return 0;
+       case GRAPH_PRE_COMMIT:
+               graph_output_pre_commit_line(graph, sb);
+               return 0;
+       case GRAPH_COMMIT:
+               graph_output_commit_line(graph, sb);
+               return 1;
+       case GRAPH_POST_MERGE:
+               graph_output_post_merge_line(graph, sb);
+               return 0;
+       case GRAPH_COLLAPSING:
+               graph_output_collapsing_line(graph, sb);
+               return 0;
+       }
+
+       assert(0);
+       return 0;
+}
+
+void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
+{
+       int i, j;
+
+       if (graph->state != GRAPH_COMMIT) {
+               graph_next_line(graph, sb);
+               return;
+       }
+
+       /*
+        * Output the row containing this commit
+        * Iterate up to and including graph->num_columns,
+        * since the current commit may not be in any of the existing
+        * columns.  (This happens when the current commit doesn't have any
+        * children that we have already processed.)
+        */
+       for (i = 0; i < graph->num_columns; i++) {
+               struct commit *col_commit = graph->columns[i].commit;
+               if (col_commit == graph->commit) {
+                       strbuf_addch(sb, '|');
+
+                       if (graph->num_parents < 3)
+                               strbuf_addch(sb, ' ');
+                       else {
+                               int num_spaces = ((graph->num_parents - 2) * 2);
+                               for (j = 0; j < num_spaces; j++)
+                                       strbuf_addch(sb, ' ');
+                       }
+               } else {
+                       strbuf_addstr(sb, "| ");
+               }
+       }
+
+       graph_pad_horizontally(graph, sb);
+}
+
+int graph_is_commit_finished(struct git_graph const *graph)
+{
+       return (graph->state == GRAPH_PADDING);
+}
+
+void graph_show_commit(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+       int shown_commit_line = 0;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+
+       while (!shown_commit_line) {
+               shown_commit_line = graph_next_line(graph, &msgbuf);
+               fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+               if (!shown_commit_line)
+                       putchar('\n');
+               strbuf_setlen(&msgbuf, 0);
+       }
+
+       strbuf_release(&msgbuf);
+}
+
+void graph_show_oneline(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+       graph_next_line(graph, &msgbuf);
+       fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+       strbuf_release(&msgbuf);
+}
+
+void graph_show_padding(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+
+       if (!graph)
+               return;
+
+       strbuf_init(&msgbuf, 0);
+       graph_padding_line(graph, &msgbuf);
+       fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+       strbuf_release(&msgbuf);
+}
+
+int graph_show_remainder(struct git_graph *graph)
+{
+       struct strbuf msgbuf;
+       int shown = 0;
+
+       if (!graph)
+               return 0;
+
+       if (graph_is_commit_finished(graph))
+               return 0;
+
+       strbuf_init(&msgbuf, 0);
+       for (;;) {
+               graph_next_line(graph, &msgbuf);
+               fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
+               strbuf_setlen(&msgbuf, 0);
+               shown = 1;
+
+               if (!graph_is_commit_finished(graph))
+                       putchar('\n');
+               else
+                       break;
+       }
+       strbuf_release(&msgbuf);
+
+       return shown;
+}
+
+
+void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb)
+{
+       char *p;
+
+       if (!graph) {
+               fwrite(sb->buf, sizeof(char), sb->len, stdout);
+               return;
+       }
+
+       /*
+        * Print the strbuf line by line,
+        * and display the graph info before each line but the first.
+        */
+       p = sb->buf;
+       while (p) {
+               size_t len;
+               char *next_p = strchr(p, '\n');
+               if (next_p) {
+                       next_p++;
+                       len = next_p - p;
+               } else {
+                       len = (sb->buf + sb->len) - p;
+               }
+               fwrite(p, sizeof(char), len, stdout);
+               if (next_p && *next_p != '\0')
+                       graph_show_oneline(graph);
+               p = next_p;
+       }
+}
+
+void graph_show_commit_msg(struct git_graph *graph,
+                          struct strbuf const *sb)
+{
+       int newline_terminated;
+
+       if (!graph) {
+               /*
+                * If there's no graph, just print the message buffer.
+                *
+                * The message buffer for CMIT_FMT_ONELINE and
+                * CMIT_FMT_USERFORMAT are already missing a terminating
+                * newline.  All of the other formats should have it.
+                */
+               fwrite(sb->buf, sizeof(char), sb->len, stdout);
+               return;
+       }
+
+       newline_terminated = (sb->len && sb->buf[sb->len - 1] == '\n');
+
+       /*
+        * Show the commit message
+        */
+       graph_show_strbuf(graph, sb);
+
+       /*
+        * If there is more output needed for this commit, show it now
+        */
+       if (!graph_is_commit_finished(graph)) {
+               /*
+                * If sb doesn't have a terminating newline, print one now,
+                * so we can start the remainder of the graph output on a
+                * new line.
+                */
+               if (!newline_terminated)
+                       putchar('\n');
+
+               graph_show_remainder(graph);
+
+               /*
+                * If sb ends with a newline, our output should too.
+                */
+               if (newline_terminated)
+                       putchar('\n');
+       }
+}
diff --git a/graph.h b/graph.h
new file mode 100644 (file)
index 0000000..a7748a5
--- /dev/null
+++ b/graph.h
@@ -0,0 +1,121 @@
+#ifndef GRAPH_H
+#define GRAPH_H
+
+/* A graph is a pointer to this opaque structure */
+struct git_graph;
+
+/*
+ * Create a new struct git_graph.
+ * The graph should be freed with graph_release() when no longer needed.
+ */
+struct git_graph *graph_init();
+
+/*
+ * Destroy a struct git_graph and free associated memory.
+ */
+void graph_release(struct git_graph *graph);
+
+/*
+ * Update a git_graph with a new commit.
+ * This will cause the graph to begin outputting lines for the new commit
+ * the next time graph_next_line() is called.
+ *
+ * If graph_update() is called before graph_is_commit_finished() returns 1,
+ * the next call to graph_next_line() will output an ellipsis ("...")
+ * to indicate that a portion of the graph is missing.
+ */
+void graph_update(struct git_graph *graph, struct commit *commit);
+
+/*
+ * Output the next line for a graph.
+ * This formats the next graph line into the specified strbuf.  It is not
+ * terminated with a newline.
+ *
+ * Returns 1 if the line includes the current commit, and 0 otherwise.
+ * graph_next_line() will return 1 exactly once for each time
+ * graph_update() is called.
+ */
+int graph_next_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Output a padding line in the graph.
+ * This is similar to graph_next_line().  However, it is guaranteed to
+ * never print the current commit line.  Instead, if the commit line is
+ * next, it will simply output a line of vertical padding, extending the
+ * branch lines downwards, but leaving them otherwise unchanged.
+ */
+void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
+
+/*
+ * Determine if a graph has finished outputting lines for the current
+ * commit.
+ *
+ * Returns 1 if graph_next_line() needs to be called again before
+ * graph_update() should be called.  Returns 0 if no more lines are needed
+ * for this commit.  If 0 is returned, graph_next_line() may still be
+ * called without calling graph_update(), and it will merely output
+ * appropriate "vertical padding" in the graph.
+ */
+int graph_is_commit_finished(struct git_graph const *graph);
+
+
+/*
+ * graph_show_*: helper functions for printing to stdout
+ */
+
+
+/*
+ * If the graph is non-NULL, print the history graph to stdout,
+ * up to and including the line containing this commit.
+ * Does not print a terminating newline on the last line.
+ */
+void graph_show_commit(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print one line of the history graph to stdout.
+ * Does not print a terminating newline on the last line.
+ */
+void graph_show_oneline(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print one line of vertical graph padding to
+ * stdout.  Does not print a terminating newline on the last line.
+ */
+void graph_show_padding(struct git_graph *graph);
+
+/*
+ * If the graph is non-NULL, print the rest of the history graph for this
+ * commit to stdout.  Does not print a terminating newline on the last line.
+ */
+int graph_show_remainder(struct git_graph *graph);
+
+/*
+ * Print a strbuf to stdout.  If the graph is non-NULL, all lines but the
+ * first will be prefixed with the graph output.
+ *
+ * If the strbuf ends with a newline, the output will end after this
+ * newline.  A new graph line will not be printed after the final newline.
+ * If the strbuf is empty, no output will be printed.
+ *
+ * Since the first line will not include the graph ouput, the caller is
+ * responsible for printing this line's graph (perhaps via
+ * graph_show_commit() or graph_show_oneline()) before calling
+ * graph_show_strbuf().
+ */
+void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
+
+/*
+ * Print a commit message strbuf and the remainder of the graph to stdout.
+ *
+ * This is similar to graph_show_strbuf(), but it always prints the
+ * remainder of the graph.
+ *
+ * If the strbuf ends with a newline, the output printed by
+ * graph_show_commit_msg() will end with a newline.  If the strbuf is
+ * missing a terminating newline (including if it is empty), the output
+ * printed by graph_show_commit_msg() will also be missing a terminating
+ * newline.
+ */
+void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb);
+
+#endif /* GRAPH_H */
index 42727c8a45c7b50e8e6b2b04a0799c9ba85a0114..f173dcd64f73bddc891808c96c43407533aac1f5 100644 (file)
@@ -1349,6 +1349,24 @@ static int unlock_remote(struct remote_lock *lock)
        return rc;
 }
 
+static void remove_locks(void)
+{
+       struct remote_lock *lock = remote->locks;
+
+       fprintf(stderr, "Removing remote locks...\n");
+       while (lock) {
+               unlock_remote(lock);
+               lock = lock->next;
+       }
+}
+
+static void remove_locks_on_signal(int signo)
+{
+       remove_locks();
+       signal(signo, SIG_DFL);
+       raise(signo);
+}
+
 static void remote_ls(const char *path, int flags,
                      void (*userFunc)(struct remote_ls_ctx *ls),
                      void *userData);
@@ -2256,6 +2274,10 @@ int main(int argc, char **argv)
                goto cleanup;
        }
 
+       signal(SIGINT, remove_locks_on_signal);
+       signal(SIGHUP, remove_locks_on_signal);
+       signal(SIGQUIT, remove_locks_on_signal);
+
        /* Check whether the remote has server info files */
        remote->can_update_info_refs = 0;
        remote->has_info_refs = remote_exists("info/refs");
index 663f18f9c40145e5ab772021e0c700e9123a4b01..b0118d0592f691d6ae9f321c6303f93d43007f6f 100644 (file)
@@ -24,7 +24,7 @@ static void remove_lock_file(void)
 static void remove_lock_file_on_signal(int signo)
 {
        remove_lock_file();
-       signal(SIGINT, SIG_DFL);
+       signal(signo, SIG_DFL);
        raise(signo);
 }
 
index d3fb0e520c749e0d57afaab7666861a1a4e7cd9f..1474d1f5d96c9b04b0d5c190f88b2fdefd2ef679 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
+#include "graph.h"
 #include "log-tree.h"
 #include "reflog-walk.h"
 
@@ -165,11 +166,16 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
        }
 
        printf("From %s Mon Sep 17 00:00:00 2001\n", name);
-       if (opt->message_id)
+       graph_show_oneline(opt->graph);
+       if (opt->message_id) {
                printf("Message-Id: <%s>\n", opt->message_id);
-       if (opt->ref_message_id)
+               graph_show_oneline(opt->graph);
+       }
+       if (opt->ref_message_id) {
                printf("In-Reply-To: <%s>\nReferences: <%s>\n",
                       opt->ref_message_id, opt->ref_message_id);
+               graph_show_oneline(opt->graph);
+       }
        if (opt->mime_boundary) {
                static char subject_buffer[1024];
                static char buffer[1024];
@@ -220,6 +226,8 @@ void show_log(struct rev_info *opt)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
+               graph_show_commit(opt->graph);
+
                if (commit->object.flags & BOUNDARY)
                        putchar('-');
                else if (commit->object.flags & UNINTERESTING)
@@ -231,9 +239,13 @@ void show_log(struct rev_info *opt)
                                putchar('>');
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
-               if (opt->parents)
+               if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                show_decorations(commit);
+               if (opt->graph && !graph_is_commit_finished(opt->graph)) {
+                       putchar('\n');
+                       graph_show_remainder(opt->graph);
+               }
                putchar(opt->diffopt.line_termination);
                return;
        }
@@ -243,10 +255,32 @@ void show_log(struct rev_info *opt)
         * Otherwise, add a diffopt.line_termination character before all
         * entries but the first.  (IOW, as a separator between entries)
         */
-       if (opt->shown_one && !opt->use_terminator)
+       if (opt->shown_one && !opt->use_terminator) {
+               /*
+                * If entries are separated by a newline, the output
+                * should look human-readable.  If the last entry ended
+                * with a newline, print the graph output before this
+                * newline.  Otherwise it will end up as a completely blank
+                * line and will look like a gap in the graph.
+                *
+                * If the entry separator is not a newline, the output is
+                * primarily intended for programmatic consumption, and we
+                * never want the extra graph output before the entry
+                * separator.
+                */
+               if (opt->diffopt.line_termination == '\n' &&
+                   !opt->missing_newline)
+                       graph_show_padding(opt->graph);
                putchar(opt->diffopt.line_termination);
+       }
        opt->shown_one = 1;
 
+       /*
+        * If the history graph was requested,
+        * print the graph, up to this commit's line
+        */
+       graph_show_commit(opt->graph);
+
        /*
         * Print header line of header..
         */
@@ -271,7 +305,7 @@ void show_log(struct rev_info *opt)
                }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
                      stdout);
-               if (opt->parents)
+               if (opt->print_parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
                        printf(" (from %s)",
@@ -279,8 +313,19 @@ void show_log(struct rev_info *opt)
                                                  abbrev_commit));
                show_decorations(commit);
                printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
-               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+               if (opt->commit_format == CMIT_FMT_ONELINE) {
+                       putchar(' ');
+               } else {
+                       putchar('\n');
+                       graph_show_oneline(opt->graph);
+               }
                if (opt->reflog_info) {
+                       /*
+                        * setup_revisions() ensures that opt->reflog_info
+                        * and opt->graph cannot both be set,
+                        * so we don't need to worry about printing the
+                        * graph info here.
+                        */
                        show_reflog_message(opt->reflog_info,
                                    opt->commit_format == CMIT_FMT_ONELINE,
                                    opt->date_mode);
@@ -304,13 +349,30 @@ void show_log(struct rev_info *opt)
 
        if (opt->add_signoff)
                append_signoff(&msgbuf, opt->add_signoff);
-       if (opt->show_log_size)
+       if (opt->show_log_size) {
                printf("log size %i\n", (int)msgbuf.len);
+               graph_show_oneline(opt->graph);
+       }
 
-       if (msgbuf.len)
+       /*
+        * Set opt->missing_newline if msgbuf doesn't
+        * end in a newline (including if it is empty)
+        */
+       if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
+               opt->missing_newline = 1;
+       else
+               opt->missing_newline = 0;
+
+       if (opt->graph)
+               graph_show_commit_msg(opt->graph, &msgbuf);
+       else
                fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
-       if (opt->use_terminator)
+       if (opt->use_terminator) {
+               if (!opt->missing_newline)
+                       graph_show_padding(opt->graph);
                putchar('\n');
+       }
+
        strbuf_release(&msgbuf);
 }
 
index 0382804e7694e9a0e87cc2dc8340187ddeea2d64..8b467f8f415b68db6e6ea83720ce6ae63dedb9e4 100644 (file)
@@ -470,7 +470,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
        unsigned ce_option = CE_MATCH_IGNORE_VALID|CE_MATCH_RACY_IS_DIRTY;
 
        if (!S_ISREG(st_mode) && !S_ISLNK(st_mode) && !S_ISDIR(st_mode))
-               die("%s: can only add regular files, symbolic links or git-directories", path);
+               return error("%s: can only add regular files, symbolic links or git-directories", path);
 
        namelen = strlen(path);
        if (S_ISDIR(st_mode)) {
@@ -505,12 +505,12 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
                return 0;
        }
        if (index_path(ce->sha1, path, st, 1))
-               die("unable to index file %s", path);
+               return error("unable to index file %s", path);
        if (ignore_case && alias && different_name(ce, alias))
                ce = create_alias_ce(ce, alias);
        ce->ce_flags |= CE_ADDED;
        if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
-               die("unable to add %s to index",path);
+               return error("unable to add %s to index",path);
        if (verbose)
                printf("add '%s'\n", path);
        return 0;
index 91cbb72ddeba1c60dab7aad2105e1c004ea6e198..9e4f2b84d90cb97a6cb19779325f9ea443a76e43 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -434,6 +434,16 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                }
 
                rhs = strrchr(lhs, ':');
+
+               /*
+                * Before going on, special case ":" (or "+:") as a refspec
+                * for matching refs.
+                */
+               if (!fetch && rhs == lhs && rhs[1] == '\0') {
+                       rs[i].matching = 1;
+                       continue;
+               }
+
                if (rhs) {
                        rhs++;
                        rlen = strlen(rhs);
@@ -855,7 +865,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
        const char *dst_value = rs->dst;
        char *dst_guess;
 
-       if (rs->pattern)
+       if (rs->pattern || rs->matching)
                return errs;
 
        matched_src = matched_dst = NULL;
@@ -945,13 +955,23 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
                                                 const struct ref *src)
 {
        int i;
+       int matching_refs = -1;
        for (i = 0; i < rs_nr; i++) {
+               if (rs[i].matching &&
+                   (matching_refs == -1 || rs[i].force)) {
+                       matching_refs = i;
+                       continue;
+               }
+
                if (rs[i].pattern &&
                    !prefixcmp(src->name, rs[i].src) &&
                    src->name[strlen(rs[i].src)] == '/')
                        return rs + i;
        }
-       return NULL;
+       if (matching_refs != -1)
+               return rs + matching_refs;
+       else
+               return NULL;
 }
 
 /*
@@ -962,11 +982,16 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
 int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
               int nr_refspec, const char **refspec, int flags)
 {
-       struct refspec *rs =
-               parse_push_refspec(nr_refspec, (const char **) refspec);
+       struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       static const char *default_refspec[] = { ":", 0 };
 
+       if (!nr_refspec) {
+               nr_refspec = 1;
+               refspec = default_refspec;
+       }
+       rs = parse_push_refspec(nr_refspec, (const char **) refspec);
        if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
                return -1;
 
@@ -977,48 +1002,50 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                char *dst_name;
                if (src->peer_ref)
                        continue;
-               if (nr_refspec) {
-                       pat = check_pattern_match(rs, nr_refspec, src);
-                       if (!pat)
-                               continue;
-               }
-               else if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+
+               pat = check_pattern_match(rs, nr_refspec, src);
+               if (!pat)
+                       continue;
+
+               if (pat->matching) {
                        /*
                         * "matching refs"; traditionally we pushed everything
                         * including refs outside refs/heads/ hierarchy, but
                         * that does not make much sense these days.
                         */
-                       continue;
+                       if (!send_mirror && prefixcmp(src->name, "refs/heads/"))
+                               continue;
+                       dst_name = xstrdup(src->name);
 
-               if (pat) {
+               } else {
                        const char *dst_side = pat->dst ? pat->dst : pat->src;
                        dst_name = xmalloc(strlen(dst_side) +
                                           strlen(src->name) -
                                           strlen(pat->src) + 2);
                        strcpy(dst_name, dst_side);
                        strcat(dst_name, src->name + strlen(pat->src));
-               } else
-                       dst_name = xstrdup(src->name);
+               }
                dst_peer = find_ref_by_name(dst, dst_name);
-               if (dst_peer && dst_peer->peer_ref)
-                       /* We're already sending something to this ref. */
-                       goto free_name;
+               if (dst_peer) {
+                       if (dst_peer->peer_ref)
+                               /* We're already sending something to this ref. */
+                               goto free_name;
+
+               } else {
+                       if (pat->matching && !(send_all || send_mirror))
+                               /*
+                                * Remote doesn't have it, and we have no
+                                * explicit pattern, and we don't have
+                                * --all nor --mirror.
+                                */
+                               goto free_name;
 
-               if (!dst_peer && !nr_refspec && !(send_all || send_mirror))
-                       /*
-                        * Remote doesn't have it, and we have no
-                        * explicit pattern, and we don't have
-                        * --all nor --mirror.
-                        */
-                       goto free_name;
-               if (!dst_peer) {
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
                dst_peer->peer_ref = src;
-               if (pat)
-                       dst_peer->force = pat->force;
+               dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
index 2ee83a33b3bb7ddaa5bb33493dacfbeca4ae5dbe..c2f557357fd4a0eb247f0e6f688efd10f946a08a 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -47,6 +47,7 @@ int remote_has_url(struct remote *remote, const char *url);
 struct refspec {
        unsigned force : 1;
        unsigned pattern : 1;
+       unsigned matching : 1;
 
        char *src;
        char *dst;
index 4231ea2cce57c74a4110c9c69459a4caf67dc15c..7142cf96cde81377522e49de5f102a25eaec2d54 100644 (file)
@@ -6,6 +6,7 @@
 #include "diff.h"
 #include "refs.h"
 #include "revision.h"
+#include "graph.h"
 #include "grep.h"
 #include "reflog-walk.h"
 #include "patch-ids.h"
@@ -415,7 +416,6 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
 {
        struct commit_list *parent = commit->parents;
        unsigned left_flag;
-       int add, rest;
 
        if (commit->object.flags & ADDED)
                return 0;
@@ -462,19 +462,18 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
 
        left_flag = (commit->object.flags & SYMMETRIC_LEFT);
 
-       rest = !revs->first_parent_only;
-       for (parent = commit->parents, add = 1; parent; add = rest) {
+       for (parent = commit->parents; parent; parent = parent->next) {
                struct commit *p = parent->item;
 
-               parent = parent->next;
                if (parse_commit(p) < 0)
                        return -1;
                p->object.flags |= left_flag;
-               if (p->object.flags & SEEN)
-                       continue;
-               p->object.flags |= SEEN;
-               if (add)
+               if (!(p->object.flags & SEEN)) {
+                       p->object.flags |= SEEN;
                        insert_by_date(p, list);
+               }
+               if(revs->first_parent_only)
+                       break;
        }
        return 0;
 }
@@ -1105,7 +1104,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                }
                        }
                        if (!strcmp(arg, "--parents")) {
-                               revs->parents = 1;
+                               revs->rewrite_parents = 1;
+                               revs->print_parents = 1;
                                continue;
                        }
                        if (!strcmp(arg, "--dense")) {
@@ -1202,6 +1202,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                get_commit_format(arg+8, revs);
                                continue;
                        }
+                       if (!prefixcmp(arg, "--graph")) {
+                               revs->topo_order = 1;
+                               revs->rewrite_parents = 1;
+                               revs->graph = graph_init();
+                               continue;
+                       }
                        if (!strcmp(arg, "--root")) {
                                revs->show_root_diff = 1;
                                continue;
@@ -1396,6 +1402,15 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        if (revs->reverse && revs->reflog_info)
                die("cannot combine --reverse with --walk-reflogs");
 
+       /*
+        * Limitations on the graph functionality
+        */
+       if (revs->reverse && revs->graph)
+               die("cannot combine --reverse with --graph");
+
+       if (revs->reflog_info && revs->graph)
+               die("cannot combine --walk-reflogs with --graph");
+
        return left;
 }
 
@@ -1524,13 +1539,13 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
                /* Commit without changes? */
                if (commit->object.flags & TREESAME) {
                        /* drop merges unless we want parenthood */
-                       if (!revs->parents)
+                       if (!revs->rewrite_parents)
                                return commit_ignore;
                        /* non-merge - always ignore it */
                        if (!commit->parents || !commit->parents->next)
                                return commit_ignore;
                }
-               if (revs->parents && rewrite_parents(revs, commit) < 0)
+               if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
                        return commit_error;
        }
        return commit_show;
@@ -1597,7 +1612,7 @@ static void gc_boundary(struct object_array *array)
        }
 }
 
-struct commit *get_revision(struct rev_info *revs)
+static struct commit *get_revision_internal(struct rev_info *revs)
 {
        struct commit *c = NULL;
        struct commit_list *l;
@@ -1704,3 +1719,11 @@ struct commit *get_revision(struct rev_info *revs)
 
        return c;
 }
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit *c = get_revision_internal(revs);
+       if (c && revs->graph)
+               graph_update(revs->graph, c);
+       return c;
+}
index 31217f8c67307aa09c2f470ccdbdebd5c2445890..abce5001f19a60bb15b519b26773b57c83563021 100644 (file)
@@ -46,7 +46,8 @@ struct rev_info {
                        unpacked:1, /* see also ignore_packed below */
                        boundary:2,
                        left_right:1,
-                       parents:1,
+                       rewrite_parents:1,
+                       print_parents:1,
                        reverse:1,
                        cherry_pick:1,
                        first_parent_only:1;
@@ -65,7 +66,8 @@ struct rev_info {
        /* Format info */
        unsigned int    shown_one:1,
                        abbrev_commit:1,
-                       use_terminator:1;
+                       use_terminator:1,
+                       missing_newline:1;
        enum date_mode date_mode;
 
        const char **ignore_packed; /* pretend objects in these are unpacked */
@@ -88,6 +90,9 @@ struct rev_info {
        /* Filter by commit log message */
        struct grep_opt *grep_filter;
 
+       /* Display history graph */
+       struct git_graph *graph;
+
        /* special limits */
        int skip_count;
        int max_count;
index 3516777bc7614c23da02dd7a9aa28a48294d56cb..c3170961cedd080f7021093b50e3d607169874bd 100644 (file)
@@ -176,21 +176,22 @@ char *sha1_file_name(const unsigned char *sha1)
        return base;
 }
 
-char *sha1_pack_name(const unsigned char *sha1)
+static char *sha1_get_pack_name(const unsigned char *sha1,
+                               char **name, char **base)
 {
        static const char hex[] = "0123456789abcdef";
-       static char *name, *base, *buf;
+       char *buf;
        int i;
 
-       if (!base) {
+       if (!*base) {
                const char *sha1_file_directory = get_object_directory();
                int len = strlen(sha1_file_directory);
-               base = xmalloc(len + 60);
-               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
-               name = base + len + 11;
+               *base = xmalloc(len + 60);
+               sprintf(*base, "%s/pack/pack-1234567890123456789012345678901234567890.pack", sha1_file_directory);
+               *name = *base + len + 11;
        }
 
-       buf = name;
+       buf = *name;
 
        for (i = 0; i < 20; i++) {
                unsigned int val = *sha1++;
@@ -198,32 +199,21 @@ char *sha1_pack_name(const unsigned char *sha1)
                *buf++ = hex[val & 0xf];
        }
 
-       return base;
+       return *base;
 }
 
-char *sha1_pack_index_name(const unsigned char *sha1)
+char *sha1_pack_name(const unsigned char *sha1)
 {
-       static const char hex[] = "0123456789abcdef";
-       static char *name, *base, *buf;
-       int i;
-
-       if (!base) {
-               const char *sha1_file_directory = get_object_directory();
-               int len = strlen(sha1_file_directory);
-               base = xmalloc(len + 60);
-               sprintf(base, "%s/pack/pack-1234567890123456789012345678901234567890.idx", sha1_file_directory);
-               name = base + len + 11;
-       }
+       static char *name, *base;
 
-       buf = name;
+       return sha1_get_pack_name(sha1, &name, &base);
+}
 
-       for (i = 0; i < 20; i++) {
-               unsigned int val = *sha1++;
-               *buf++ = hex[val >> 4];
-               *buf++ = hex[val & 0xf];
-       }
+char *sha1_pack_index_name(const unsigned char *sha1)
+{
+       static char *name, *base;
 
-       return base;
+       return sha1_get_pack_name(sha1, &name, &base);
 }
 
 struct alternate_object_database *alt_odb_list;
@@ -2102,26 +2092,16 @@ int hash_sha1_file(const void *buf, unsigned long len, const char *type,
        return 0;
 }
 
-int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
+                             void *buf, unsigned long len, time_t mtime)
 {
-       int size, ret;
+       int fd, size, ret;
        unsigned char *compressed;
        z_stream stream;
-       unsigned char sha1[20];
        char *filename;
        static char tmpfile[PATH_MAX];
-       char hdr[32];
-       int fd, hdrlen;
 
-       /* Normally if we have it in the pack then we do not bother writing
-        * it out into .git/objects/??/?{38} file.
-        */
-       write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
        filename = sha1_file_name(sha1);
-       if (returnsha1)
-               hashcpy(returnsha1, sha1);
-       if (has_sha1_file(sha1))
-               return 0;
        fd = open(filename, O_RDONLY);
        if (fd >= 0) {
                /*
@@ -2182,9 +2162,53 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
                die("unable to write sha1 file");
        free(compressed);
 
+       if (mtime) {
+               struct utimbuf utb;
+               utb.actime = mtime;
+               utb.modtime = mtime;
+               if (utime(tmpfile, &utb) < 0)
+                       warning("failed utime() on %s: %s",
+                               tmpfile, strerror(errno));
+       }
+
        return move_temp_to_file(tmpfile, filename);
 }
 
+int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
+{
+       unsigned char sha1[20];
+       char hdr[32];
+       int hdrlen;
+
+       /* Normally if we have it in the pack then we do not bother writing
+        * it out into .git/objects/??/?{38} file.
+        */
+       write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+       if (returnsha1)
+               hashcpy(returnsha1, sha1);
+       if (has_sha1_file(sha1))
+               return 0;
+       return write_loose_object(sha1, hdr, hdrlen, buf, len, 0);
+}
+
+int force_object_loose(const unsigned char *sha1, time_t mtime)
+{
+       struct stat st;
+       void *buf;
+       unsigned long len;
+       enum object_type type;
+       char hdr[32];
+       int hdrlen;
+
+       if (find_sha1_file(sha1, &st))
+               return 0;
+       buf = read_packed_sha1(sha1, &type, &len);
+       if (!buf)
+               return error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+       hdrlen = sprintf(hdr, "%s %lu", typename(type), len) + 1;
+       return write_loose_object(sha1, hdr, hdrlen, buf, len, mtime);
+}
+
 /*
  * We need to unpack and recompress the object for writing
  * it out to a different file.
index 72d7884232fbb4ce04082a778f9f952395764bf6..c6a60ab165b504393d3ecd5db1ea9d241687be05 100644 (file)
@@ -20,7 +20,7 @@ $(T):
        @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
 
 clean:
-       $(RM) -r trash
+       $(RM) -r 'trash directory'
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
index 73ed11bfe24edf56879d90a43c8df0813332fb08..70841a4645b2469d8263553a7ba41d4f953774f7 100644 (file)
--- a/t/README
+++ b/t/README
@@ -123,7 +123,7 @@ This test harness library does the following things:
    (or -h), it shows the test_description and exits.
 
  - Creates an empty test directory with an empty .git/objects
-   database and chdir(2) into it.  This directory is 't/trash'
+   database and chdir(2) into it.  This directory is 't/trash directory'
    if you must know, but I do not think you care.
 
  - Defines standard test helper functions for your scripts to
index 3fbad77811e3782523e83dfdd58fe5308f237771..c5360e23d9ff1a5f31d42919eabdb2561f0d2e3d 100755 (executable)
@@ -7,6 +7,7 @@ test_description='Various filesystem issues'
 auml=`printf '\xc3\xa4'`
 aumlcdiar=`printf '\x61\xcc\x88'`
 
+case_insensitive=
 test_expect_success 'see if we expect ' '
 
        test_case=test_expect_success
@@ -17,6 +18,7 @@ test_expect_success 'see if we expect ' '
        if test "$(cat junk/CamelCase)" != good
        then
                test_case=test_expect_failure
+               case_insensitive=t
                say "will test on a case insensitive filesystem"
        fi &&
        rm -fr junk &&
@@ -32,8 +34,23 @@ test_expect_success 'see if we expect ' '
        rm -fr junk
 '
 
+if test "$case_insensitive"
+then
+test_expect_success "detection of case insensitive filesystem during repo init" '
+
+       test $(git config --bool core.ignorecase) = true
+'
+else
+test_expect_success "detection of case insensitive filesystem during repo init" '
+
+       ! git config --bool core.ignorecase >/dev/null ||
+       test $(git config --bool core.ignorecase) = false
+'
+fi
+
 test_expect_success "setup case tests" '
 
+       git config core.ignorecase true &&
        touch camelcase &&
        git add camelcase &&
        git commit -m "initial" &&
@@ -55,11 +72,23 @@ $test_case 'rename (case change)' '
 
 $test_case 'merge (case change)' '
 
+       rm -f CamelCase &&
+       rm -f camelcase &&
        git reset --hard initial &&
        git merge topic
 
 '
 
+$test_case 'add (with different case)' '
+
+       git reset --hard initial &&
+       rm camelcase &&
+       echo 1 >CamelCase &&
+       git add CamelCase &&
+       test $(git-ls-files | grep -i camelcase | wc -l) = 1
+
+'
+
 test_expect_success "setup unicode normalization tests" '
 
   test_create_repo unicode &&
index 68c5ddebdf565912af5f481283980169f069a7a6..e83fa1f6894dd13eb11cae6afb8ecbd3dcb999db 100755 (executable)
@@ -179,4 +179,47 @@ test_expect_success 'git add --refresh' '
        test -z "`git diff-index HEAD -- foo`"
 '
 
+test_expect_success 'git add should fail atomically upon an unreadable file' '
+       git reset --hard &&
+       date >foo1 &&
+       date >foo2 &&
+       chmod 0 foo2 &&
+       test_must_fail git add --verbose . &&
+       ! ( git ls-files foo1 | grep foo1 )
+'
+
+rm -f foo2
+
+test_expect_success 'git add --ignore-errors' '
+       git reset --hard &&
+       date >foo1 &&
+       date >foo2 &&
+       chmod 0 foo2 &&
+       test_must_fail git add --verbose --ignore-errors . &&
+       git ls-files foo1 | grep foo1
+'
+
+rm -f foo2
+
+test_expect_success 'git add (add.ignore-errors)' '
+       git config add.ignore-errors 1 &&
+       git reset --hard &&
+       date >foo1 &&
+       date >foo2 &&
+       chmod 0 foo2 &&
+       test_must_fail git add --verbose . &&
+       git ls-files foo1 | grep foo1
+'
+rm -f foo2
+
+test_expect_success 'git add (add.ignore-errors = false)' '
+       git config add.ignore-errors 0 &&
+       git reset --hard &&
+       date >foo1 &&
+       date >foo2 &&
+       chmod 0 foo2 &&
+       test_must_fail git add --verbose . &&
+       ! ( git ls-files foo1 | grep foo1 )
+'
+
 test_done
index f15be93e7709acbb517bb85020d4d6b94a50f725..fae64eae9f9ff9b34b935e8556df61d2093425cf 100755 (executable)
@@ -66,6 +66,11 @@ test_expect_success 'revert works (commit)' '
        grep "unchanged *+3/-0 file" output
 '
 
+if test "$(git config --bool core.filemode)" = false
+then
+    say 'skipping filemode tests (filesystem does not properly support modes)'
+else
+
 test_expect_success 'patch does not affect mode' '
        git reset --hard &&
        echo content >>file &&
@@ -84,5 +89,7 @@ test_expect_success 'stage mode but not hunk' '
        git diff          file | grep "+content"
 '
 
+fi
+# end of tests disabled when filemode is not usable
 
 test_done
index 670a8f1c9992f9c8d15144b2c19b242821e3814b..22ba380034775e7584a33ca606294af34f568443 100755 (executable)
@@ -23,10 +23,13 @@ test_refspec () {
 }
 
 test_refspec push ''                                           invalid
-test_refspec push ':'                                          invalid
+test_refspec push ':'
+test_refspec push '::'                                         invalid
+test_refspec push '+:'
 
 test_refspec fetch ''
 test_refspec fetch ':'
+test_refspec fetch '::'                                                invalid
 
 test_refspec push 'refs/heads/*:refs/remotes/frotz/*'
 test_refspec push 'refs/heads/*:refs/remotes/frotz'            invalid
index 3af03d482727b69e4724d50eaf9ccc5fc8dceeee..c5c59335f36d0d8f6a26ee606fd605e74e2d1e33 100755 (executable)
@@ -165,6 +165,47 @@ test_expect_success 'push with matching heads' '
 
 '
 
+test_expect_success 'push with matching heads on the command line' '
+
+       mk_test heads/master &&
+       git push testrepo : &&
+       check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'failed (non-fast-forward) push with matching heads' '
+
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       ! git push testrepo &&
+       check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+
+'
+
+test_expect_success 'push --force with matching heads' '
+
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       git push --force testrepo &&
+       ! check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+
+'
+
+test_expect_success 'push with matching heads and forced update' '
+
+       mk_test heads/master &&
+       git push testrepo : &&
+       git commit --amend -massaged &&
+       git push testrepo +: &&
+       ! check_push_result $the_commit heads/master &&
+       git reset --hard $the_commit
+
+'
+
 test_expect_success 'push with no ambiguity (1)' '
 
        mk_test heads/master &&
index 9484129ca5aafab369a6ee1f7a1264d568f8cb44..997b2db827c4f37512c6b5d2f861e12105e2a32d 100755 (executable)
@@ -92,4 +92,22 @@ test_expect_success '--rebase with rebased upstream' '
 
 '
 
+test_expect_success 'pull --rebase dies early with dirty working directory' '
+
+       git update-ref refs/remotes/me/copy copy^ &&
+       COPY=$(git rev-parse --verify me/copy) &&
+       git rebase --onto $COPY copy &&
+       git config branch.to-rebase.remote me &&
+       git config branch.to-rebase.merge refs/heads/copy &&
+       git config branch.to-rebase.rebase true &&
+       echo dirty >> file &&
+       git add file &&
+       test_must_fail git pull &&
+       test $COPY = $(git rev-parse --verify me/copy) &&
+       git checkout HEAD -- file &&
+       git pull &&
+       test $COPY != $(git rev-parse --verify me/copy)
+
+'
+
 test_done
index 2ef85a869d515b557815476f126386dc19f755c5..6c7b9024822024ca24f7d1ddea63c94d3aa016b0 100755 (executable)
@@ -196,4 +196,17 @@ test_expect_success 'apply submodule diff' '
        test -z "$D"
 '
 
+test_expect_success 'update --init' '
+
+       mv init init2 &&
+       git config -f .gitmodules submodule.example.url "$(pwd)/init2" &&
+       git config --remove-section submodule.example
+       git submodule update init > update.out &&
+       grep "not initialized" update.out &&
+       test ! -d init/.git &&
+       git submodule update --init init &&
+       test -d init/.git
+
+'
+
 test_done
index 018060c60f590946c8e02181bfb1f572972bb787..3531a992a9a50e25a1585435e024097c7028af27 100755 (executable)
@@ -166,7 +166,9 @@ test_expect_success 'author different from committer' '
        test_cmp expect actual
 '
 
-sed -i '$d' expect
+mv expect expect.tmp
+sed '$d' < expect.tmp > expect
+rm -f expect.tmp
 echo "# Committer:
 #" >> expect
 unset GIT_COMMITTER_EMAIL
diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh
new file mode 100755 (executable)
index 0000000..6a5211f
--- /dev/null
@@ -0,0 +1,47 @@
+#!/bin/sh
+
+test_description='git-repack works correctly'
+
+. ./test-lib.sh
+
+test_expect_success '-A option leaves unreachable objects unpacked' '
+       echo content > file1 &&
+       git add . &&
+       git commit -m initial_commit &&
+       # create a transient branch with unique content
+       git checkout -b transient_branch &&
+       echo more content >> file1 &&
+       # record the objects created in the database for file, commit, tree
+       fsha1=$(git hash-object file1) &&
+       git commit -a -m more_content &&
+       csha1=$(git rev-parse HEAD^{commit}) &&
+       tsha1=$(git rev-parse HEAD^{tree}) &&
+       git checkout master &&
+       echo even more content >> file1 &&
+       git commit -a -m even_more_content &&
+       # delete the transient branch
+       git branch -D transient_branch &&
+       # pack the repo
+       git repack -A -d -l &&
+       # verify objects are packed in repository
+       test 3 = $(git verify-pack -v -- .git/objects/pack/*.idx |
+                  grep -e "^$fsha1 " -e "^$csha1 " -e "^$tsha1 " |
+                  sort | uniq | wc -l) &&
+       git show $fsha1 &&
+       git show $csha1 &&
+       git show $tsha1 &&
+       # now expire the reflog
+       sleep 1 &&
+       git reflog expire --expire-unreachable=now --all &&
+       # and repack
+       git repack -A -d -l &&
+       # verify objects are retained unpacked
+       test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx |
+                  grep -e "^$fsha1 " -e "^$csha1 " -e "^$tsha1 " |
+                  sort | uniq | wc -l) &&
+       git show $fsha1 &&
+       git show $csha1 &&
+       git show $tsha1
+'
+
+test_done
index 04baa61c0485377d033f4a10d2bb8ceeac1a4686..3e4eb63f1c4d63d6ff38c79ff1f7a47fa3aa1597 100755 (executable)
@@ -233,4 +233,63 @@ test_expect_success 'sendemail.cc unset' '
        test_cmp expected-show-all-headers actual-show-all-headers
 '
 
+test_expect_success '--compose adds MIME for utf8 body' '
+       clean_fake_sendmail &&
+       (echo "#!/bin/sh" &&
+        echo "echo utf8 body: àéìöú >>\"\$1\""
+       ) >fake-editor-utf8 &&
+       chmod +x fake-editor-utf8 &&
+       echo y | \
+         GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
+         GIT_SEND_EMAIL_NOTTY=1 \
+         git send-email \
+         --compose --subject foo \
+         --from="Example <nobody@example.com>" \
+         --to=nobody@example.com \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         $patches &&
+       grep "^utf8 body" msgtxt1 &&
+       grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+'
+
+test_expect_success '--compose respects user mime type' '
+       clean_fake_sendmail &&
+       (echo "#!/bin/sh" &&
+        echo "(echo MIME-Version: 1.0"
+        echo " echo Content-Type: text/plain\\; charset=iso-8859-1"
+        echo " echo Content-Transfer-Encoding: 8bit"
+        echo " echo Subject: foo"
+        echo " echo "
+        echo " echo utf8 body: àéìöú) >\"\$1\""
+       ) >fake-editor-utf8-mime &&
+       chmod +x fake-editor-utf8-mime &&
+       echo y | \
+         GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
+         GIT_SEND_EMAIL_NOTTY=1 \
+         git send-email \
+         --compose --subject foo \
+         --from="Example <nobody@example.com>" \
+         --to=nobody@example.com \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         $patches &&
+       grep "^utf8 body" msgtxt1 &&
+       grep "^Content-Type: text/plain; charset=iso-8859-1" msgtxt1 &&
+       ! grep "^Content-Type: text/plain; charset=utf-8" msgtxt1
+'
+
+test_expect_success '--compose adds MIME for utf8 subject' '
+       clean_fake_sendmail &&
+       echo y | \
+         GIT_EDITOR="\"$(pwd)/fake-editor\"" \
+         GIT_SEND_EMAIL_NOTTY=1 \
+         git send-email \
+         --compose --subject utf8-sübjëct \
+         --from="Example <nobody@example.com>" \
+         --to=nobody@example.com \
+         --smtp-server="$(pwd)/fake.sendmail" \
+         $patches &&
+       grep "^fake edit" msgtxt1 &&
+       grep "^Subject: =?utf-8?q?utf8-s=C3=BCbj=C3=ABct?=" msgtxt1
+'
+
 test_done
index 5002fb04b58ea91572b58dcaf7fe92342f4eeab1..3bf570b068dd56c802e7bdcef0384aa8e1209985 100644 (file)
@@ -368,7 +368,7 @@ test_done () {
        case "$test_failure" in
        0)
                # We could:
-               # cd .. && rm -fr trash
+               # cd .. && rm -fr 'trash directory'
                # but that means we forbid any tests that use their own
                # subdirectory from calling test_done without coming back
                # to where they started from.
@@ -419,7 +419,7 @@ rm -fr "$test" || {
 }
 
 test_create_repo "$test"
-cd "$test"
+cd "$test" || exit 1
 
 this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
 for skp in $GIT_SKIP_TESTS
index ff0f42a1d984cdcd4b647e713eaec668177fb1f6..d3c1da34d2c7cbdd66ca142b5bf0ec5b877e1baa 100644 (file)
 # The third example adds a Signed-off-by line to the message, that can
 # still be edited.  This is rarely a good idea.
 
-case "$2 $3" in
-  merge)
-    sed -i '/^Conflicts:/,/#/!b;s/^/# &/;s/^# #/#/' "$1" ;;
+case "$2,$3" in
+  merge,)
+    perl -i -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;
 
-# ""|template)
+# ,|template,)
 #   perl -i -pe '
 #      print "\n" . `git diff --cached --name-status -r`
 #       if /^#/ && $first++ == 0' "$1" ;;
index a44c5433754eb89cb84a64c7c7c6d2181bcf7641..6e6516cd874a92376a672dcdc181aed6a3b5636c 100644 (file)
@@ -18,6 +18,7 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
        "\033[32m", /* WT_STATUS_UPDATED: green */
        "\033[31m", /* WT_STATUS_CHANGED: red */
        "\033[31m", /* WT_STATUS_UNTRACKED: red */
+       "\033[31m", /* WT_STATUS_NOBRANCH: red */
 };
 
 static const char use_add_msg[] =
@@ -38,6 +39,8 @@ static int parse_status_slot(const char *var, int offset)
                return WT_STATUS_CHANGED;
        if (!strcasecmp(var+offset, "untracked"))
                return WT_STATUS_UNTRACKED;
+       if (!strcasecmp(var+offset, "nobranch"))
+               return WT_STATUS_NOBRANCH;
        die("bad config variable '%s'", var);
 }
 
@@ -314,8 +317,9 @@ static void wt_status_print_verbose(struct wt_status *s)
 void wt_status_print(struct wt_status *s)
 {
        unsigned char sha1[20];
-       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+       const char *branch_color = color(WT_STATUS_HEADER);
 
+       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
        if (s->branch) {
                const char *on_what = "On branch ";
                const char *branch_name = s->branch;
@@ -323,10 +327,11 @@ void wt_status_print(struct wt_status *s)
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
                        branch_name = "";
+                       branch_color = color(WT_STATUS_NOBRANCH);
                        on_what = "Not currently on any branch.";
                }
-               color_fprintf_ln(s->fp, color(WT_STATUS_HEADER),
-                       "# %s%s", on_what, branch_name);
+               color_fprintf(s->fp, color(WT_STATUS_HEADER), "# ");
+               color_fprintf_ln(s->fp, branch_color, "%s%s", on_what, branch_name);
        }
 
        if (s->is_initial) {
index 7d61410b1733825e3bb8042781a75de3ee62c41d..f0675fdff33420ba42f3a700501003a43afda376 100644 (file)
@@ -8,6 +8,7 @@ enum color_wt_status {
        WT_STATUS_UPDATED,
        WT_STATUS_CHANGED,
        WT_STATUS_UNTRACKED,
+       WT_STATUS_NOBRANCH,
 };
 
 struct wt_status {