Merge branch 'jc/xsha1'
authorJunio C Hamano <junkio@cox.net>
Tue, 9 May 2006 23:44:59 +0000 (16:44 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 9 May 2006 23:44:59 +0000 (16:44 -0700)
* jc/xsha1:
  get_sha1() - fix infinite loop on nonexistent stage.
  get_sha1(): :path and :[0-3]:path to extract from index.

87 files changed:
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/git-add.txt
Documentation/git-checkout-index.txt
Documentation/git-cherry.txt
Documentation/git-clone.txt
Documentation/git-commit.txt
Documentation/git-count-objects.txt
Documentation/git-diff-tree.txt
Documentation/git-imap-send.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-merge-index.txt
Documentation/git-name-rev.txt
Documentation/git-prune.txt
Documentation/git-rebase.txt
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rm.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-verify-pack.txt
Documentation/git-whatchanged.txt
Documentation/gitk.txt
Documentation/glossary.txt
Documentation/sort_glossary.pl
Makefile
apply.c
base85.c [new file with mode: 0644]
blame.c
builtin-count.c [new file with mode: 0644]
builtin-diff.c [new file with mode: 0644]
builtin-push.c [new file with mode: 0644]
builtin.h
cache.h
cat-file.c
checkout-index.c
combine-diff.c
commit-tree.c
config.c
contrib/git-svn/git-svn.perl
contrib/git-svn/git-svn.txt
convert-objects.c
delta.h
describe.c
diff-delta.c
diff.c
diff.h
environment.c
git-am.sh
git-clone.sh
git-count-objects.sh [deleted file]
git-cvsserver.perl
git-diff.sh [deleted file]
git-fetch.sh
git-rebase.sh
git-repack.sh
git-send-email.perl
git-whatchanged.sh [deleted file]
git.c
gitk
log-tree.c
ls-tree.c
merge-base.c
merge-tree.c
pack-objects.c
patch-delta.c
read-cache.c
read-tree.c
refs.c
repo-config.c
revision.c
setup.c
sha1_file.c
show-branch.c
ssh-upload.c
t/t1300-repo-config.sh
t/t2101-update-index-reupdate.sh [new file with mode: 0755]
t/t4012-diff-binary.sh [new file with mode: 0755]
t/t5700-clone-reference.sh [new file with mode: 0755]
t/test4012.png [new file with mode: 0644]
tar-tree.c
unpack-file.c
update-index.c
update-ref.c

index b27b0d5..d1a4bec 100644 (file)
@@ -64,9 +64,11 @@ core.ignoreStat::
        slow, such as Microsoft Windows.  See gitlink:git-update-index[1].
        False by default.
 
-core.onlyUseSymrefs::
-       Always use the "symref" format instead of symbolic links for HEAD
-       and other symbolic reference files. True by default.
+core.preferSymlinkRefs::
+       Instead of the default "symref" format for HEAD
+       and other symbolic reference files, use symbolic links.
+       This is sometimes needed to work with old scripts that
+       expect HEAD to be a symbolic link.
 
 core.repositoryFormatVersion::
        Internal variable identifying the repository format and layout
index 4211c81..d1360ec 100644 (file)
@@ -971,7 +971,7 @@ $ git show-branch --topo-order master mybranch
 The first two lines indicate that it is showing the two branches
 and the first line of the commit log message from their
 top-of-the-tree commits, you are currently on `master` branch
-(notice the asterisk `*` character), and the first column for
+(notice the asterisk `\*` character), and the first column for
 the later output lines is used to show commits contained in the
 `master` branch, and the second column for the `mybranch`
 branch. Three commits are shown along with their log messages.
index ae24547..5e31129 100644 (file)
@@ -26,7 +26,7 @@ OPTIONS
 -v::
         Be verbose.
 
---::
+\--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
index 09bd6a5..765c173 100644 (file)
@@ -63,7 +63,7 @@ OPTIONS
        Only meaningful with `--stdin`; paths are separated with
        NUL character instead of LF.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 The order of the flags used to matter, but not anymore.
index 9a5e371..893baaa 100644 (file)
@@ -11,11 +11,20 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Each commit between the fork-point and <head> is examined, and compared against
-the change each commit between the fork-point and <upstream> introduces.
-Commits already included in upstream are prefixed with '-' (meaning "drop from
-my local pull"), while commits missing from upstream are prefixed with '+'
-(meaning "add to the updated upstream").
+The changeset (or "diff") of each commit between the fork-point and <head>
+is compared against each commit between the fork-point and <upstream>.
+
+Every commit with a changeset that doesn't exist in the other branch
+has its id (sha1) reported, prefixed by a symbol.  Those existing only
+in the <upstream> branch are prefixed with a minus (-) sign, and those
+that only exist in the <head> branch are prefixed with a plus (+) symbol.
+
+Because git-cherry compares the changeset rather than the commit id
+(sha1), you can use git-cherry to find out if a commit you made locally
+has been applied <upstream> under a different commit id.  For example,
+this will happen if you're feeding patches <upstream> via email rather
+than pushing or pulling commits directly.
+
 
 OPTIONS
 -------
index 131e445..b333f51 100644 (file)
@@ -101,7 +101,7 @@ OPTIONS
        is not allowed.
 
 Examples
-~~~~~~~~
+--------
 
 Clone from upstream::
 +
index 0a7365b..38df59c 100644 (file)
@@ -106,7 +106,7 @@ but can be used to amend a merge commit.
        index and the latest commit does not match on the
        specified paths to avoid confusion.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>...::
index 47216f4..198ce77 100644 (file)
@@ -7,13 +7,23 @@ git-count-objects - Reports on unpacked objects
 
 SYNOPSIS
 --------
-'git-count-objects'
+'git-count-objects' [-v]
 
 DESCRIPTION
 -----------
 This counts the number of unpacked object files and disk space consumed by
 them, to help you decide when it is a good time to repack.
 
+
+OPTIONS
+-------
+-v::
+       In addition to the number of loose objects and disk
+       space consumed, it reports the number of in-pack
+       objects, and number of objects that can be removed by
+       running `git-prune-packed`.
+
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index 2169169..906830d 100644 (file)
@@ -92,7 +92,7 @@ separated with a single space are given.
        Furthermore, it lists only files which were modified
        from all parents.
 
--cc::
+--cc::
        This flag changes the way a merge commit patch is displayed,
        in a similar way to the '-c' option. It implies the '-c'
        and '-p' options and further compresses the patch output
index cfc0d88..eca9e9c 100644 (file)
@@ -29,6 +29,7 @@ CONFIGURATION
 git-imap-send requires the following values in the repository
 configuration file (shown with examples):
 
+..........................
 [imap]
     Folder = "INBOX.Drafts"
 
@@ -38,8 +39,9 @@ configuration file (shown with examples):
 [imap]
     Host = imap.server.com
     User = bob
-    Password = pwd
+    Pass = pwd
     Port = 143
+..........................
 
 
 BUGS
index af378ff..c9ffff7 100644 (file)
@@ -51,7 +51,7 @@ git log v2.6.12.. include/scsi drivers/scsi::
        Show all commits since version 'v2.6.12' that changed any file
        in the include/scsi or drivers/scsi subdirectories
 
-git log --since="2 weeks ago" -- gitk::
+git log --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index 796d049..a29c633 100644 (file)
@@ -106,7 +106,7 @@ OPTIONS
        lines, show only handful hexdigits prefix.
        Non default number of digits can be specified with --abbrev=<n>.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>::
index fbc986a..332e023 100644 (file)
@@ -8,7 +8,7 @@ git-merge-index - Runs a merge for files needing merging
 
 SYNOPSIS
 --------
-'git-merge-index' [-o] [-q] <merge-program> (-a | -- | <file>\*)
+'git-merge-index' [-o] [-q] <merge-program> (-a | \-- | <file>\*)
 
 DESCRIPTION
 -----------
@@ -19,7 +19,7 @@ files are passed as arguments 5, 6 and 7.
 
 OPTIONS
 -------
---::
+\--::
        Do not interpret any more arguments as options.
 
 -a::
index 6870708..ffaa004 100644 (file)
@@ -41,6 +41,7 @@ Enter git-name-rev:
 
 ------------
 % git name-rev 33db5f4d9027a10e477ccf054b2c1ab94f74c85a
+33db5f4d9027a10e477ccf054b2c1ab94f74c85a tags/v0.99^0~940
 ------------
 
 Now you are wiser, because you know that it happened 940 revisions before v0.99.
index f694fcb..a11e303 100644 (file)
@@ -28,7 +28,7 @@ OPTIONS
        Do not remove anything; just report what it would
        remove.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <head>...::
index 4a7e67a..1b482ab 100644 (file)
@@ -3,38 +3,54 @@ git-rebase(1)
 
 NAME
 ----
-git-rebase - Rebase local commits to new upstream head
+git-rebase - Rebase local commits to a new head
 
 SYNOPSIS
 --------
 'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
+'git-rebase' --continue
+
+'git-rebase' --abort
+
 DESCRIPTION
 -----------
-git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
+git-rebase replaces <branch> with a new branch of the same name.  When
+the --onto option is provided the new branch starts out with a HEAD equal
+to <newbase>, otherwise it is equal to <upstream>.  It then attempts to
+create a new commit for each commit from the original <branch> that does
+not exist in the <upstream> branch.
 
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run `git rebase --continue`.  If you can not resolve the merge
+failure, running `git rebase --abort` will restore the original <branch>
+and remove the working files found in the .dotest directory.
+
+Note that if <branch> is not specified on the command line, the currently
+checked out branch is used.
 
 Assume the following history exists and the current branch is "topic":
 
+------------
           A---B---C topic
          /
     D---E---F---G master
+------------
 
 From this point, the result of either of the following commands:
 
+
     git-rebase master
     git-rebase master topic
 
 would be:
 
+------------
                   A'--B'--C' topic
                  /
     D---E---F---G master
+------------
 
 While, starting from the same point, the result of either of the following
 commands:
@@ -44,21 +60,33 @@ commands:
 
 would be:
 
+------------
               A'--B'--C' topic
              /
     D---E---F---G master
+------------
 
 In case of conflict, git-rebase will stop at the first problematic commit
-and leave conflict markers in the tree.  After resolving the conflict manually
-and updating the index with the desired resolution, you can continue the
-rebasing process with
+and leave conflict markers in the tree.  You can use git diff to locate
+the markers (<<<<<<) and make edits to resolve the conflict.  For each
+file you edit, you need to tell git that the conflict has been resolved,
+typically this would be done with
+
+
+    git update-index <filename>
+
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the rebasing process with
+
+
+    git rebase --continue
 
-    git am --resolved --3way
 
 Alternatively, you can undo the git-rebase with
 
-    git reset --hard ORIG_HEAD
-    rm -r .dotest
+
+    git rebase --abort
 
 OPTIONS
 -------
@@ -73,6 +101,28 @@ OPTIONS
 <branch>::
        Working branch; defaults to HEAD.
 
+--continue::
+       Restart the rebasing process after having resolved a merge conflict.
+
+--abort::
+       Restore the original branch and abort the rebase operation.
+
+NOTES
+-----
+When you rebase a branch, you are changing its history in a way that
+will cause problems for anyone who already has a copy of the branch
+in their repository and tries to pull updates from you.  You should
+understand the implications of using 'git rebase' on a repository that
+you share.
+
+When the git rebase command is run, it will first execute a "pre-rebase"
+hook if one exists.  You can use this hook to do sanity checks and
+reject the rebase if it isn't appropriate.  Please see the template
+pre-rebase hook script for an example.
+
+You must be in the top directory of your project to start (or continue)
+a rebase.  Upon completion, <branch> will be the current branch.
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index d2f9a44..9516227 100644 (file)
@@ -38,6 +38,7 @@ OPTIONS
 -d::
        After packing, if the newly created packs make some
        existing packs redundant, remove the redundant packs.
+       Also runs gitlink:git-prune-packed[1].
 
 -l::
         Pass the `--local` option to `git pack-objects`, see
index 566cfa1..660c18f 100644 (file)
@@ -23,10 +23,11 @@ You can query/set/replace/unset options with this command. The name is
 actually the section and the key separated by a dot, and the value will be
 escaped.
 
-If you want to set/unset an option which can occur on multiple lines, you
-should provide a POSIX regex for the value. If you want to handle the lines
-*not* matching the regex, just prepend a single exclamation mark in front
-(see EXAMPLES).
+If you want to set/unset an option which can occur on multiple
+lines, a POSIX regexp `value_regex` needs to be given.  Only the
+existing values that match the regexp are updated or unset.  If
+you want to handle the lines that do *not* match the regex, just
+prepend a single exclamation mark in front (see EXAMPLES).
 
 The type specifier can be either '--int' or '--bool', which will make
 'git-repo-config' ensure that the variable(s) are of the given type and
@@ -34,10 +35,10 @@ convert the value to the canonical form (simple decimal number for int,
 a "true" or "false" string for bool). If no type specifier is passed,
 no checks or transformations are performed on the value.
 
-This command will fail if
+This command will fail if:
 
-. .git/config is invalid,
-. .git/config can not be written to,
+. The .git/config file is invalid,
+. Can not write to .git/config,
 . no section was provided,
 . the section or key is invalid,
 . you try to unset an option which does not exist, or
@@ -49,7 +50,7 @@ OPTIONS
 
 --replace-all::
        Default behaviour is to replace at most one line. This replaces
-       all lines matching the key (and optionally the value_regex)
+       all lines matching the key (and optionally the value_regex).
 
 --get::
        Get the value for a given key (optionally filtered by a regex
@@ -59,6 +60,9 @@ OPTIONS
        Like get, but does not fail if the number of values for the key
        is not exactly one.
 
+--get-regexp::
+       Like --get-all, but interprets the name as a regular expression.
+
 --unset::
        Remove the line matching the key from .git/config.
 
index ebcfe5e..b27399d 100644 (file)
@@ -43,7 +43,7 @@ OPTIONS
        Commit to make the current HEAD.
 
 Examples
-~~~~~~~~
+--------
 
 Undo a commit and redo::
 +
index 8255ae1..ad6d14c 100644 (file)
@@ -68,9 +68,10 @@ OPTIONS
 --bisect::
        Limit output to the one commit object which is roughly halfway
        between the included and excluded commits. Thus, if 'git-rev-list
-       --bisect foo ^bar ^baz' outputs 'midpoint', the output
-       of 'git-rev-list foo ^midpoint' and 'git-rev-list midpoint
-       ^bar ^baz' would be of roughly the same length. Finding the change
+       --bisect foo {caret}bar {caret}baz' outputs 'midpoint', the output
+       of 'git-rev-list foo {caret}midpoint' and 'git-rev-list midpoint
+       {caret}bar {caret}baz' would be of roughly the same length.
+       Finding the change
        which introduces a regression is thus reduced to a binary search:
        repeatedly generate and test new 'midpoint's until the commit chain
        is of length one.
index c9c3088..66fc478 100644 (file)
@@ -32,7 +32,7 @@ OPTIONS
 -v::
         Be verbose.
 
---::
+\--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
index 1828062..c20b38b 100644 (file)
@@ -13,9 +13,16 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Reads a packed archive (.pack) from the standard input, and
-expands the objects contained in the pack into "one-file
-one-object" format in $GIT_OBJECT_DIRECTORY.
+Read a packed archive (.pack) from the standard input, expanding
+the objects contained within and writing them into the repository in
+"loose" (one object per file) format.
+
+Objects that already exist in the repository will *not* be unpacked
+from the pack-file.  Therefore, nothing will be unpacked if you use
+this command on a pack-file that exists within the target repository.
+
+Please see the `git-repack` documentation for options to generate
+new packs and replace existing ones.
 
 OPTIONS
 -------
index d4137fc..d043e86 100644 (file)
@@ -10,12 +10,12 @@ SYNOPSIS
 --------
 [verse]
 'git-update-index'
-            [--add] [--remove | --force-remove] [--replace] 
-            [--refresh [-q] [--unmerged] [--ignore-missing]]
+            [--add] [--remove | --force-remove] [--replace]
+            [--refresh] [-q] [--unmerged] [--ignore-missing]
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
-            [--really-refresh]
+            [--really-refresh] [--unresolve] [--again]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
@@ -80,6 +80,14 @@ OPTIONS
        filesystem that has very slow lstat(2) system call
        (e.g. cifs).
 
+--again::
+       Runs `git-update-index` itself on the paths whose index
+       entries are different from those from the `HEAD` commit.
+
+--unresolve::
+       Restores the 'unmerged' or 'needs updating' state of a
+       file during a merge if it was cleared by accident.
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
@@ -109,7 +117,7 @@ OPTIONS
        Only meaningful with `--stdin`; paths are separated with
        NUL character instead of LF.
 
---::
+\--::
        Do not interpret any more arguments as options.
 
 <file>::
index 4962d69..7a6132b 100644 (file)
@@ -25,7 +25,7 @@ OPTIONS
 -v::
        After verifying the pack, show list of objects contained
        in the pack.
---::
+\--::
        Do not interpret any more arguments as options.
 
 OUTPUT FORMAT
index 641cb7e..e8f21d0 100644 (file)
@@ -58,7 +58,7 @@ git-whatchanged -p v2.6.12.. include/scsi drivers/scsi::
        Show as patches the commits since version 'v2.6.12' that changed
        any file in the include/scsi or drivers/scsi subdirectories
 
-git-whatchanged --since="2 weeks ago" -- gitk::
+git-whatchanged --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index eb126d7..cb482bf 100644 (file)
@@ -31,7 +31,7 @@ gitk v2.6.12.. include/scsi drivers/scsi::
        Show as the changes since version 'v2.6.12' that changed any
        file in the include/scsi or drivers/scsi subdirectories
 
-gitk --since="2 weeks ago" -- gitk::
+gitk --since="2 weeks ago" \-- gitk::
 
        Show the changes during the last two weeks to the file 'gitk'.
        The "--" is necessary to avoid confusion with the *branch* named
index 02a9d9c..39c90ad 100644 (file)
@@ -1,79 +1,57 @@
-object::
-       The unit of storage in git. It is uniquely identified by
-       the SHA1 of its contents. Consequently, an object can not
-       be changed.
-
-object name::
-       The unique identifier of an object. The hash of the object's contents
-       using the Secure Hash Algorithm 1 and usually represented by the 40
-       character hexadecimal encoding of the hash of the object (possibly
-       followed by a white space).
-
-SHA1::
-       Synonym for object name.
-
-object identifier::
-       Synonym for object name.
-
-hash::
-       In git's context, synonym to object name.
+alternate object database::
+       Via the alternates mechanism, a repository can inherit part of its
+       object database from another object database, which is called
+       "alternate".
 
-object database::
-       Stores a set of "objects", and an individual object is identified
-       by its object name. The objects usually live in `$GIT_DIR/objects/`.
+bare repository::
+       A bare repository is normally an appropriately named
+       directory with a `.git` suffix that does not have a
+       locally checked-out copy of any of the files under revision
+       control.  That is, all of the `git` administrative and
+       control files that would normally be present in the
+       hidden `.git` sub-directory are directly present in
+       the `repository.git` directory instead, and no other files
+       are present and checked out.  Usually publishers of public
+       repositories make bare repositories available.
 
 blob object::
        Untyped object, e.g. the contents of a file.
 
-tree object::
-       An object containing a list of file names and modes along with refs
-       to the associated blob and/or tree objects. A tree is equivalent
-       to a directory.
-
-tree::
-       Either a working tree, or a tree object together with the
-       dependent blob and tree objects (i.e. a stored representation
-       of a working tree).
-
-DAG::
-       Directed acyclic graph. The commit objects form a directed acyclic
-       graph, because they have parents (directed), and the graph of commit
-       objects is acyclic (there is no chain which begins and ends with the
-       same object).
-
-index::
-       A collection of files with stat information, whose contents are
-       stored as objects. The index is a stored version of your working
-       tree. Truth be told, it can also contain a second, and even a third
-       version of a working tree, which are used when merging.
-
-index entry::
-       The information regarding a particular file, stored in the index.
-       An index entry can be unmerged, if a merge was started, but not
-       yet finished (i.e. if the index contains multiple versions of
-       that file).
-
-unmerged index:
-       An index which contains unmerged index entries.
+branch::
+       A non-cyclical graph of revisions, i.e. the complete history of
+       a particular revision, which is called the branch head. The
+       branch heads are stored in `$GIT_DIR/refs/heads/`.
 
 cache::
        Obsolete for: index.
 
-working tree::
-       The set of files and directories currently being worked on,
-       i.e. you can work in your working tree without using git at all.
-
-directory::
-       The list you get with "ls" :-)
+chain::
+       A list of objects, where each object in the list contains a
+       reference to its successor (for example, the successor of a commit
+       could be one of its parents).
 
-revision::
-       A particular state of files and directories which was stored in
-       the object database. It is referenced by a commit object.
+changeset::
+       BitKeeper/cvsps speak for "commit". Since git does not store
+       changes, but states, it really does not make sense to use
+       the term "changesets" with git.
 
 checkout::
        The action of updating the working tree to a revision which was
        stored in the object database.
 
+cherry-picking::
+       In SCM jargon, "cherry pick" means to choose a subset of
+       changes out of a series of changes (typically commits)
+       and record them as a new series of changes on top of
+       different codebase.  In GIT, this is performed by
+       "git cherry-pick" command to extract the change
+       introduced by an existing commit and to record it based
+       on the tip of the current branch as a new commit.
+
+clean::
+       A working tree is clean, if it corresponds to the revision
+       referenced by the current head.  Also see "dirty".
+
 commit::
        As a verb: The action of storing the current state of the index in the
        object database. The result is a revision.
@@ -85,73 +63,90 @@ commit object::
        tree object which corresponds to the top directory of the
        stored revision.
 
-parent::
-       A commit object contains a (possibly empty) list of the logical
-       predecessor(s) in the line of development, i.e. its parents.
+core git::
+       Fundamental data structures and utilities of git. Exposes only
+       limited source code management tools.
 
-changeset::
-       BitKeeper/cvsps speak for "commit". Since git does not store
-       changes, but states, it really does not make sense to use
-       the term "changesets" with git.
+DAG::
+       Directed acyclic graph. The commit objects form a directed acyclic
+       graph, because they have parents (directed), and the graph of commit
+       objects is acyclic (there is no chain which begins and ends with the
+       same object).
 
-clean::
-       A working tree is clean, if it corresponds to the revision
-       referenced by the current head.
+dircache::
+       You are *waaaaay* behind.
 
 dirty::
        A working tree is said to be dirty if it contains modifications
        which have not been committed to the current branch.
 
-head::
-       The top of a branch. It contains a ref to the corresponding
-       commit object.
+directory::
+       The list you get with "ls" :-)
 
-branch::
-       A non-cyclical graph of revisions, i.e. the complete history of
-       a particular revision, which is called the branch head. The
-       branch heads are stored in `$GIT_DIR/refs/heads/`.
+ent::
+       Favorite synonym to "tree-ish" by some total geeks. See
+       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
+       explanation.
 
-master::
-       The default branch. Whenever you create a git repository, a branch
-       named "master" is created, and becomes the active branch. In most
-       cases, this contains the local development.
+fast forward::
+       A fast-forward is a special type of merge where you have
+       a revision and you are "merging" another branch's changes
+       that happen to be a descendant of what you have.
+       In such these cases, you do not make a new merge commit but
+       instead just update to his revision. This will happen
+       frequently on a tracking branch of a remote repository.
 
-origin::
-       The default upstream branch. Most projects have one upstream
-       project which they track, and by default 'origin' is used for
-       that purpose.  New updates from upstream will be fetched into
-       this branch; you should never commit to it yourself.
+fetch::
+       Fetching a branch means to get the branch's head ref from a
+       remote repository, to find out which objects are missing from
+       the local object database, and to get them, too.
 
-ref::
-       A 40-byte hex representation of a SHA1 pointing to a particular
-       object. These may be stored in `$GIT_DIR/refs/`.
+file system::
+       Linus Torvalds originally designed git to be a user space file
+       system, i.e. the infrastructure to hold files and directories.
+       That ensured the efficiency and speed of git.
+
+git archive::
+       Synonym for repository (for arch people).
+
+hash::
+       In git's context, synonym to object name.
+
+head::
+       The top of a branch. It contains a ref to the corresponding
+       commit object.
 
 head ref::
        A ref pointing to a head. Often, this is abbreviated to "head".
        Head refs are stored in `$GIT_DIR/refs/heads/`.
 
-tree-ish::
-       A ref pointing to either a commit object, a tree object, or a
-       tag object pointing to a tag or commit or tree object.
+hook::
+       During the normal execution of several git commands,
+       call-outs are made to optional scripts that allow
+       a developer to add functionality or checking.
+       Typically, the hooks allow for a command to be pre-verified
+       and potentially aborted, and allow for a post-notification
+       after the operation is done.
+       The hook scripts are found in the `$GIT_DIR/hooks/` directory,
+       and are enabled by simply making them executable.
 
-ent::
-       Favorite synonym to "tree-ish" by some total geeks. See
-       `http://en.wikipedia.org/wiki/Ent_(Middle-earth)` for an in-depth
-       explanation.
+index::
+       A collection of files with stat information, whose contents are
+       stored as objects. The index is a stored version of your working
+       tree. Truth be told, it can also contain a second, and even a third
+       version of a working tree, which are used when merging.
 
-tag object::
-       An object containing a ref pointing to another object, which can
-       contain a message just like a commit object. It can also
-       contain a (PGP) signature, in which case it is called a "signed
-       tag object".
+index entry::
+       The information regarding a particular file, stored in the index.
+       An index entry can be unmerged, if a merge was started, but not
+       yet finished (i.e. if the index contains multiple versions of
+       that file).
 
-tag::
-       A ref pointing to a tag or commit object. In contrast to a head,
-       a tag is not changed by a commit. Tags (not tag objects) are
-       stored in `$GIT_DIR/refs/tags/`. A git tag has nothing to do with
-       a Lisp tag (which is called object type in git's context).
-       A tag is most typically used to mark a particular point in the
-       commit ancestry chain.
+master::
+       The default development branch. Whenever you create a git
+       repository, a branch named "master" is created, and becomes
+       the active branch. In most cases, this contains the local
+       development, though that is purely conventional and not required.
 
 merge::
        To merge branches means to try to accumulate the changes since a
@@ -159,55 +154,65 @@ merge::
        merge uses heuristics to accomplish that. Evidently, an automatic
        merge can fail.
 
-octopus::
-       To merge more than two branches. Also denotes an intelligent
-       predator.
+object::
+       The unit of storage in git. It is uniquely identified by
+       the SHA1 of its contents. Consequently, an object can not
+       be changed.
 
-resolve::
-       The action of fixing up manually what a failed automatic merge
-       left behind.
+object database::
+       Stores a set of "objects", and an individual object is identified
+       by its object name. The objects usually live in `$GIT_DIR/objects/`.
 
-rewind::
-       To throw away part of the development, i.e. to assign the head to
-       an earlier revision.
+object identifier::
+       Synonym for object name.
 
-rebase::
-       To clean a branch by starting from the head of the main line of
-       development ("master"), and reapply the (possibly cherry-picked)
-       changes from that branch.
+object name::
+       The unique identifier of an object. The hash of the object's contents
+       using the Secure Hash Algorithm 1 and usually represented by the 40
+       character hexadecimal encoding of the hash of the object (possibly
+       followed by a white space).
 
-repository::
-       A collection of refs together with an object database containing
-       all objects, which are reachable from the refs, possibly accompanied
-       by meta data from one or more porcelains. A repository can
-       share an object database with other repositories.
+object type:
+       One of the identifiers "commit","tree","tag" and "blob" describing
+       the type of an object.
 
-git archive::
-       Synonym for repository (for arch people).
+octopus::
+       To merge more than two branches. Also denotes an intelligent
+       predator.
 
-file system::
-       Linus Torvalds originally designed git to be a user space file
-       system, i.e. the infrastructure to hold files and directories.
-       That ensured the efficiency and speed of git.
+origin::
+       The default upstream tracking branch. Most projects have at
+       least one upstream project which they track. By default
+       'origin' is used for that purpose.  New upstream updates
+       will be fetched into this branch; you should never commit
+       to it yourself.
 
-alternate object database::
-       Via the alternates mechanism, a repository can inherit part of its
-       object database from another object database, which is called
-       "alternate".
+pack::
+       A set of objects which have been compressed into one file (to save
+       space or to transmit them efficiently).
 
-reachable::
-       An object is reachable from a ref/commit/tree/tag, if there is a
-       chain leading from the latter to the former.
+pack index::
+       The list of identifiers, and other information, of the objects in a
+       pack, to assist in efficiently accessing the contents of a pack.
 
-chain::
-       A list of objects, where each object in the list contains a
-       reference to its successor (for example, the successor of a commit
-       could be one of its parents).
+parent::
+       A commit object contains a (possibly empty) list of the logical
+       predecessor(s) in the line of development, i.e. its parents.
 
-fetch::
-       Fetching a branch means to get the branch's head ref from a
-       remote repository, to find out which objects are missing from
-       the local object database, and to get them, too.
+pickaxe::
+       The term pickaxe refers to an option to the diffcore routines
+       that help select changes that add or delete a given text string.
+       With the --pickaxe-all option, it can be used to view the
+       full changeset that introduced or removed, say, a particular
+       line of text.  See gitlink:git-diff[1].
+
+plumbing::
+       Cute name for core git.
+
+porcelain::
+       Cute name for programs and program suites depending on core git,
+       presenting a high level access to core git. Porcelains expose
+       more of a SCM interface than the plumbing.
 
 pull::
        Pulling a branch means to fetch it and merge it.
@@ -221,33 +226,101 @@ push::
        the remote head ref. If the remote head is not an ancestor to the
        local head, the push fails.
 
-pack::
-       A set of objects which have been compressed into one file (to save
-       space or to transmit them efficiently).
+reachable::
+       An object is reachable from a ref/commit/tree/tag, if there is a
+       chain leading from the latter to the former.
 
-pack index::
-       The list of identifiers, and other information, of the objects in a
-       pack, to assist in efficiently accessing the contents of a pack. 
+rebase::
+       To clean a branch by starting from the head of the main line of
+       development ("master"), and reapply the (possibly cherry-picked)
+       changes from that branch.
 
-core git::
-       Fundamental data structures and utilities of git. Exposes only
-       limited source code management tools.
+ref::
+       A 40-byte hex representation of a SHA1 or a name that denotes
+       a particular object. These may be stored in `$GIT_DIR/refs/`.
+
+refspec::
+       A refspec is used by fetch and push to describe the mapping
+       between remote ref and local ref.  They are combined with
+       a colon in the format <src>:<dst>, preceded by an optional
+       plus sign, +.  For example:
+       `git fetch $URL refs/heads/master:refs/heads/origin`
+       means "grab the master branch head from the $URL and store
+       it as my origin branch head".
+       And `git push $URL refs/heads/master:refs/heads/to-upstream`
+       means "publish my master branch head as to-upstream master head
+       at $URL".   See also gitlink:git-push[1]
 
-plumbing::
-       Cute name for core git.
+repository::
+       A collection of refs together with an object database containing
+       all objects, which are reachable from the refs, possibly accompanied
+       by meta data from one or more porcelains. A repository can
+       share an object database with other repositories.
 
-porcelain::
-       Cute name for programs and program suites depending on core git,
-       presenting a high level access to core git. Porcelains expose
-       more of a SCM interface than the plumbing.
+resolve::
+       The action of fixing up manually what a failed automatic merge
+       left behind.
 
-object type:
-       One of the identifiers "commit","tree","tag" and "blob" describing
-       the type of an object.
+revision::
+       A particular state of files and directories which was stored in
+       the object database. It is referenced by a commit object.
+
+rewind::
+       To throw away part of the development, i.e. to assign the head to
+       an earlier revision.
 
 SCM::
        Source code management (tool).
 
-dircache::
-       You are *waaaaay* behind.
+SHA1::
+       Synonym for object name.
+
+topic branch::
+       A regular git branch that is used by a developer to
+       identify a conceptual line of development.  Since branches
+       are very easy and inexpensive, it is often desirable to
+       have several small branches that each contain very well
+       defined concepts or small incremental yet related changes.
+
+tracking branch::
+       A regular git branch that is used to follow changes from
+       another repository.  A tracking branch should not contain
+       direct modifications or have local commits made to it.
+       A tracking branch can usually be identified as the
+       right-hand-side ref in a Pull: refspec.
+
+tree object::
+       An object containing a list of file names and modes along with refs
+       to the associated blob and/or tree objects. A tree is equivalent
+       to a directory.
+
+tree::
+       Either a working tree, or a tree object together with the
+       dependent blob and tree objects (i.e. a stored representation
+       of a working tree).
+
+tree-ish::
+       A ref pointing to either a commit object, a tree object, or a
+       tag object pointing to a tag or commit or tree object.
+
+tag object::
+       An object containing a ref pointing to another object, which can
+       contain a message just like a commit object. It can also
+       contain a (PGP) signature, in which case it is called a "signed
+       tag object".
+
+tag::
+       A ref pointing to a tag or commit object. In contrast to a head,
+       a tag is not changed by a commit. Tags (not tag objects) are
+       stored in `$GIT_DIR/refs/tags/`. A git tag has nothing to do with
+       a Lisp tag (which is called object type in git's context).
+       A tag is most typically used to mark a particular point in the
+       commit ancestry chain.
+
+unmerged index:
+       An index which contains unmerged index entries.
+
+working tree::
+       The set of files and directories currently being worked on,
+       i.e. you can work in your working tree without using git at all.
 
index e57dc78..e0bc552 100644 (file)
@@ -48,7 +48,7 @@ This list is sorted alphabetically:
 ';
 
 @keys=sort {uc($a) cmp uc($b)} keys %terms;
-$pattern='(\b'.join('\b|\b',reverse @keys).'\b)';
+$pattern='(\b(?<!link:git-)'.join('\b|\b(?<!link:git-)',reverse @keys).'\b)';
 foreach $key (@keys) {
        $terms{$key}=~s/$pattern/sprintf "<<ref_".no_spaces($1).",$1>>";/eg;
        print '[[ref_'.no_spaces($key).']]'.$key."::\n"
index 8ce27a6..37fbe78 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -28,8 +28,8 @@ all:
 #
 # Define NO_SETENV if you don't have setenv in the C library.
 #
-# Define USE_SYMLINK_HEAD if you want .git/HEAD to be a symbolic link.
-# Don't enable it on Windows.
+# Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
+# Enable it on Windows.  By default, symrefs are still used.
 #
 # Define PPC_SHA1 environment variable when running make to make use of
 # a bundled SHA1 routine optimized for PowerPC.
@@ -115,13 +115,13 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 SCRIPT_SH = \
        git-add.sh git-bisect.sh git-branch.sh git-checkout.sh \
        git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
-       git-count-objects.sh git-diff.sh git-fetch.sh \
+       git-fetch.sh \
        git-format-patch.sh git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
-       git-prune.sh git-pull.sh git-push.sh git-rebase.sh \
+       git-prune.sh git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-resolve.sh git-revert.sh git-rm.sh git-sh-setup.sh \
-       git-tag.sh git-verify-tag.sh git-whatchanged.sh \
+       git-tag.sh git-verify-tag.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-merge-ours.sh git-grep.sh \
@@ -139,7 +139,7 @@ SCRIPT_PYTHON = \
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
          $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
-         git-cherry-pick git-show git-status
+         git-cherry-pick git-status
 
 # The ones that do not have to link with lcrypto, lz nor xdiff.
 SIMPLE_PROGRAMS = \
@@ -167,7 +167,8 @@ PROGRAMS = \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
        git-describe$X git-merge-tree$X git-blame$X git-imap-send$X
 
-BUILT_INS = git-log$X
+BUILT_INS = git-log$X git-whatchanged$X git-show$X \
+       git-count-objects$X git-diff$X git-push$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -204,7 +205,7 @@ DIFF_OBJS = \
        diffcore-delta.o log-tree.o
 
 LIB_OBJS = \
-       blob.o commit.o connect.o csum-file.o \
+       blob.o commit.o connect.o csum-file.o base85.o \
        date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o \
        quote.o read-cache.o refs.o run-command.o \
@@ -214,7 +215,7 @@ LIB_OBJS = \
        $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-       builtin-log.o builtin-help.o
+       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -263,6 +264,7 @@ ifeq ($(uname_O),Cygwin)
        NO_D_TYPE_IN_DIRENT = YesPlease
        NO_D_INO_IN_DIRENT = YesPlease
        NO_STRCASESTR = YesPlease
+       NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
@@ -386,6 +388,9 @@ endif
 ifdef NO_D_INO_IN_DIRENT
        ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
 endif
+ifdef NO_SYMLINK_HEAD
+       ALL_CFLAGS += -DNO_SYMLINK_HEAD
+endif
 ifdef NO_STRCASESTR
        COMPAT_CFLAGS += -DNO_STRCASESTR
        COMPAT_OBJS += compat/strcasestr.o
@@ -505,9 +510,6 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
 git-cherry-pick: git-revert
        cp $< $@
 
-git-show: git-whatchanged
-       cp $< $@
-
 git-status: git-commit
        cp $< $@
 
@@ -562,10 +564,6 @@ git-http-push$X: revision.o http.o http-push.o $(LIB_FILE)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
                $(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)
 
-git-rev-list$X: rev-list.o $(LIB_FILE)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(OPENSSL_LIBSSL)
-
 init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
                -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
@@ -609,7 +607,7 @@ test-date$X: test-date.c date.o ctype.o
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
 
 test-delta$X: test-delta.c diff-delta.o patch-delta.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^ -lz
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
 
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
diff --git a/apply.c b/apply.c
index 269210a..7c8146a 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -10,6 +10,7 @@
 #include "cache.h"
 #include "quote.h"
 #include "blob.h"
+#include "delta.h"
 
 //  --check turns on checking that the working tree matches the
 //    files that are being modified, but doesn't apply the patch
@@ -19,6 +20,7 @@
 //
 static const char *prefix;
 static int prefix_length = -1;
+static int newfd = -1;
 
 static int p_value = 1;
 static int allow_binary_replacement = 0;
@@ -113,6 +115,9 @@ struct patch {
        char *new_name, *old_name, *def_name;
        unsigned int old_mode, new_mode;
        int is_rename, is_copy, is_new, is_delete, is_binary;
+#define BINARY_DELTA_DEFLATED 1
+#define BINARY_LITERAL_DEFLATED 2
+       unsigned long deflate_origlen;
        int lines_added, lines_deleted;
        int score;
        struct fragment *fragments;
@@ -966,6 +971,88 @@ static inline int metadata_changes(struct patch *patch)
                 patch->old_mode != patch->new_mode);
 }
 
+static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
+{
+       /* We have read "GIT binary patch\n"; what follows is a line
+        * that says the patch method (currently, either "deflated
+        * literal" or "deflated delta") and the length of data before
+        * deflating; a sequence of 'length-byte' followed by base-85
+        * encoded data follows.
+        *
+        * Each 5-byte sequence of base-85 encodes up to 4 bytes,
+        * and we would limit the patch line to 66 characters,
+        * so one line can fit up to 13 groups that would decode
+        * to 52 bytes max.  The length byte 'A'-'Z' corresponds
+        * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes.
+        * The end of binary is signalled with an empty line.
+        */
+       int llen, used;
+       struct fragment *fragment;
+       char *data = NULL;
+
+       patch->fragments = fragment = xcalloc(1, sizeof(*fragment));
+
+       /* Grab the type of patch */
+       llen = linelen(buffer, size);
+       used = llen;
+       linenr++;
+
+       if (!strncmp(buffer, "delta ", 6)) {
+               patch->is_binary = BINARY_DELTA_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 6, NULL, 10);
+       }
+       else if (!strncmp(buffer, "literal ", 8)) {
+               patch->is_binary = BINARY_LITERAL_DEFLATED;
+               patch->deflate_origlen = strtoul(buffer + 8, NULL, 10);
+       }
+       else
+               return error("unrecognized binary patch at line %d: %.*s",
+                            linenr-1, llen-1, buffer);
+       buffer += llen;
+       while (1) {
+               int byte_length, max_byte_length, newsize;
+               llen = linelen(buffer, size);
+               used += llen;
+               linenr++;
+               if (llen == 1)
+                       break;
+               /* Minimum line is "A00000\n" which is 7-byte long,
+                * and the line length must be multiple of 5 plus 2.
+                */
+               if ((llen < 7) || (llen-2) % 5)
+                       goto corrupt;
+               max_byte_length = (llen - 2) / 5 * 4;
+               byte_length = *buffer;
+               if ('A' <= byte_length && byte_length <= 'Z')
+                       byte_length = byte_length - 'A' + 1;
+               else if ('a' <= byte_length && byte_length <= 'z')
+                       byte_length = byte_length - 'a' + 27;
+               else
+                       goto corrupt;
+               /* if the input length was not multiple of 4, we would
+                * have filler at the end but the filler should never
+                * exceed 3 bytes
+                */
+               if (max_byte_length < byte_length ||
+                   byte_length <= max_byte_length - 4)
+                       goto corrupt;
+               newsize = fragment->size + byte_length;
+               data = xrealloc(data, newsize);
+               if (decode_85(data + fragment->size,
+                             buffer + 1,
+                             byte_length))
+                       goto corrupt;
+               fragment->size = newsize;
+               buffer += llen;
+               size -= llen;
+       }
+       fragment->patch = data;
+       return used;
+ corrupt:
+       return error("corrupt binary patch at line %d: %.*s",
+                    linenr-1, llen-1, buffer);
+}
+
 static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
 {
        int hdrsize, patchsize;
@@ -982,19 +1069,34 @@ static int parse_chunk(char *buffer, unsigned long size, struct patch *patch)
                        "Files ",
                        NULL,
                };
+               static const char git_binary[] = "GIT binary patch\n";
                int i;
                int hd = hdrsize + offset;
                unsigned long llen = linelen(buffer + hd, size - hd);
 
-               if (!memcmp(" differ\n", buffer + hd + llen - 8, 8))
+               if (llen == sizeof(git_binary) - 1 &&
+                   !memcmp(git_binary, buffer + hd, llen)) {
+                       int used;
+                       linenr++;
+                       used = parse_binary(buffer + hd + llen,
+                                           size - hd - llen, patch);
+                       if (used)
+                               patchsize = used + llen;
+                       else
+                               patchsize = 0;
+               }
+               else if (!memcmp(" differ\n", buffer + hd + llen - 8, 8)) {
                        for (i = 0; binhdr[i]; i++) {
                                int len = strlen(binhdr[i]);
                                if (len < size - hd &&
                                    !memcmp(binhdr[i], buffer + hd, len)) {
+                                       linenr++;
                                        patch->is_binary = 1;
+                                       patchsize = llen;
                                        break;
                                }
                        }
+               }
 
                /* Empty patch cannot be applied if:
                 * - it is a binary patch and we do not do binary_replace, or
@@ -1345,76 +1447,150 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
        return offset;
 }
 
-static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+static char *inflate_it(const void *data, unsigned long size,
+                       unsigned long inflated_size)
+{
+       z_stream stream;
+       void *out;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       stream.next_out = out = xmalloc(inflated_size);
+       stream.avail_out = inflated_size;
+       inflateInit(&stream);
+       st = inflate(&stream, Z_FINISH);
+       if ((st != Z_STREAM_END) || stream.total_out != inflated_size) {
+               free(out);
+               return NULL;
+       }
+       return out;
+}
+
+static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch)
+{
+       unsigned long dst_size;
+       struct fragment *fragment = patch->fragments;
+       void *data;
+       void *result;
+
+       data = inflate_it(fragment->patch, fragment->size,
+                         patch->deflate_origlen);
+       if (!data)
+               return error("corrupt patch data");
+       switch (patch->is_binary) {
+       case BINARY_DELTA_DEFLATED:
+               result = patch_delta(desc->buffer, desc->size,
+                                    data,
+                                    patch->deflate_origlen,
+                                    &dst_size);
+               free(desc->buffer);
+               desc->buffer = result;
+               free(data);
+               break;
+       case BINARY_LITERAL_DEFLATED:
+               free(desc->buffer);
+               desc->buffer = data;
+               dst_size = patch->deflate_origlen;
+               break;
+       }
+       if (!desc->buffer)
+               return -1;
+       desc->size = desc->alloc = dst_size;
+       return 0;
+}
+
+static int apply_binary(struct buffer_desc *desc, struct patch *patch)
 {
-       struct fragment *frag = patch->fragments;
        const char *name = patch->old_name ? patch->old_name : patch->new_name;
+       unsigned char sha1[20];
+       unsigned char hdr[50];
+       int hdrlen;
 
-       if (patch->is_binary) {
-               unsigned char sha1[20];
+       if (!allow_binary_replacement)
+               return error("cannot apply binary patch to '%s' "
+                            "without --allow-binary-replacement",
+                            name);
 
-               if (!allow_binary_replacement)
-                       return error("cannot apply binary patch to '%s' "
-                                    "without --allow-binary-replacement",
-                                    name);
+       /* For safety, we require patch index line to contain
+        * full 40-byte textual SHA1 for old and new, at least for now.
+        */
+       if (strlen(patch->old_sha1_prefix) != 40 ||
+           strlen(patch->new_sha1_prefix) != 40 ||
+           get_sha1_hex(patch->old_sha1_prefix, sha1) ||
+           get_sha1_hex(patch->new_sha1_prefix, sha1))
+               return error("cannot apply binary patch to '%s' "
+                            "without full index line", name);
 
-               /* For safety, we require patch index line to contain
-                * full 40-byte textual SHA1 for old and new, at least for now.
+       if (patch->old_name) {
+               /* See if the old one matches what the patch
+                * applies to.
                 */
-               if (strlen(patch->old_sha1_prefix) != 40 ||
-                   strlen(patch->new_sha1_prefix) != 40 ||
-                   get_sha1_hex(patch->old_sha1_prefix, sha1) ||
-                   get_sha1_hex(patch->new_sha1_prefix, sha1))
-                       return error("cannot apply binary patch to '%s' "
-                                    "without full index line", name);
-
-               if (patch->old_name) {
-                       unsigned char hdr[50];
-                       int hdrlen;
-
-                       /* See if the old one matches what the patch
-                        * applies to.
-                        */
-                       write_sha1_file_prepare(desc->buffer, desc->size,
-                                               blob_type, sha1, hdr, &hdrlen);
-                       if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
-                               return error("the patch applies to '%s' (%s), "
-                                            "which does not match the "
-                                            "current contents.",
-                                            name, sha1_to_hex(sha1));
-               }
-               else {
-                       /* Otherwise, the old one must be empty. */
-                       if (desc->size)
-                               return error("the patch applies to an empty "
-                                            "'%s' but it is not empty", name);
-               }
+               write_sha1_file_prepare(desc->buffer, desc->size,
+                                       blob_type, sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
+                       return error("the patch applies to '%s' (%s), "
+                                    "which does not match the "
+                                    "current contents.",
+                                    name, sha1_to_hex(sha1));
+       }
+       else {
+               /* Otherwise, the old one must be empty. */
+               if (desc->size)
+                       return error("the patch applies to an empty "
+                                    "'%s' but it is not empty", name);
+       }
 
-               /* For now, we do not record post-image data in the patch,
-                * and require the object already present in the recipient's
-                * object database.
+       get_sha1_hex(patch->new_sha1_prefix, sha1);
+       if (!memcmp(sha1, null_sha1, 20)) {
+               free(desc->buffer);
+               desc->alloc = desc->size = 0;
+               desc->buffer = NULL;
+               return 0; /* deletion patch */
+       }
+
+       if (has_sha1_file(sha1)) {
+               /* We already have the postimage */
+               char type[10];
+               unsigned long size;
+
+               free(desc->buffer);
+               desc->buffer = read_sha1_file(sha1, type, &size);
+               if (!desc->buffer)
+                       return error("the necessary postimage %s for "
+                                    "'%s' cannot be read",
+                                    patch->new_sha1_prefix, name);
+               desc->alloc = desc->size = size;
+       }
+       else {
+               /* We have verified desc matches the preimage;
+                * apply the patch data to it, which is stored
+                * in the patch->fragments->{patch,size}.
                 */
-               if (desc->buffer) {
-                       free(desc->buffer);
-                       desc->alloc = desc->size = 0;
-               }
-               get_sha1_hex(patch->new_sha1_prefix, sha1);
-
-               if (memcmp(sha1, null_sha1, 20)) {
-                       char type[10];
-                       unsigned long size;
-
-                       desc->buffer = read_sha1_file(sha1, type, &size);
-                       if (!desc->buffer)
-                               return error("the necessary postimage %s for "
-                                            "'%s' does not exist",
-                                            patch->new_sha1_prefix, name);
-                       desc->alloc = desc->size = size;
-               }
+               if (apply_binary_fragment(desc, patch))
+                       return error("binary patch does not apply to '%s'",
+                                    name);
 
-               return 0;
+               /* verify that the result matches */
+               write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
+                                       sha1, hdr, &hdrlen);
+               if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
+                       return error("binary patch to '%s' creates incorrect result", name);
        }
 
+       return 0;
+}
+
+static int apply_fragments(struct buffer_desc *desc, struct patch *patch)
+{
+       struct fragment *frag = patch->fragments;
+       const char *name = patch->old_name ? patch->old_name : patch->new_name;
+
+       if (patch->is_binary)
+               return apply_binary(desc, patch);
+
        while (frag) {
                if (apply_one_fragment(desc, frag) < 0)
                        return error("patch failed: %s:%ld",
@@ -1873,7 +2049,6 @@ static int use_patch(struct patch *p)
 
 static int apply_patch(int fd, const char *filename)
 {
-       int newfd;
        unsigned long offset, size;
        char *buffer = read_patch_file(fd, &size);
        struct patch *list = NULL, **listp = &list;
@@ -1904,12 +2079,11 @@ static int apply_patch(int fd, const char *filename)
                size -= nr;
        }
 
-       newfd = -1;
        if (whitespace_error && (new_whitespace == error_on_whitespace))
                apply = 0;
 
        write_index = check_index && apply;
-       if (write_index)
+       if (write_index && newfd < 0)
                newfd = hold_index_file_for_update(&cache_file, get_index_file());
        if (check_index) {
                if (read_cache() < 0)
@@ -1922,12 +2096,6 @@ static int apply_patch(int fd, const char *filename)
        if (apply)
                write_out_results(list, skipped_patch);
 
-       if (write_index) {
-               if (write_cache(newfd, active_cache, active_nr) ||
-                   commit_index_file(&cache_file))
-                       die("Unable to write new cachefile");
-       }
-
        if (show_index_info)
                show_index_list(list);
 
@@ -1990,7 +2158,8 @@ int main(int argc, char **argv)
                        diffstat = 1;
                        continue;
                }
-               if (!strcmp(arg, "--allow-binary-replacement")) {
+               if (!strcmp(arg, "--allow-binary-replacement") ||
+                   !strcmp(arg, "--binary")) {
                        allow_binary_replacement = 1;
                        continue;
                }
@@ -2085,5 +2254,12 @@ int main(int argc, char **argv)
                                whitespace_error == 1 ? "" : "s",
                                whitespace_error == 1 ? "s" : "");
        }
+
+       if (write_index) {
+               if (write_cache(newfd, active_cache, active_nr) ||
+                   commit_index_file(&cache_file))
+                       die("Unable to write new cachefile");
+       }
+
        return 0;
 }
diff --git a/base85.c b/base85.c
new file mode 100644 (file)
index 0000000..a9e97f8
--- /dev/null
+++ b/base85.c
@@ -0,0 +1,140 @@
+#include "cache.h"
+
+#undef DEBUG_85
+
+#ifdef DEBUG_85
+#define say(a) fprintf(stderr, a)
+#define say1(a,b) fprintf(stderr, a, b)
+#define say2(a,b,c) fprintf(stderr, a, b, c)
+#else
+#define say(a) do {} while(0)
+#define say1(a,b) do {} while(0)
+#define say2(a,b,c) do {} while(0)
+#endif
+
+static const char en85[] = {
+       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+       'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+       'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+       'U', 'V', 'W', 'X', 'Y', 'Z',
+       'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+       'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+       'u', 'v', 'w', 'x', 'y', 'z',
+       '!', '#', '$', '%', '&', '(', ')', '*', '+', '-',
+       ';', '<', '=', '>', '?', '@', '^', '_', '`', '{',
+       '|', '}', '~'
+};
+
+static char de85[256];
+static void prep_base85(void)
+{
+       int i;
+       if (de85['Z'])
+               return;
+       for (i = 0; i < ARRAY_SIZE(en85); i++) {
+               int ch = en85[i];
+               de85[ch] = i + 1;
+       }
+}
+
+int decode_85(char *dst, char *buffer, int len)
+{
+       prep_base85();
+
+       say2("decode 85 <%.*s>", len/4*5, buffer);
+       while (len) {
+               unsigned acc = 0;
+               int de, cnt = 4;
+               unsigned char ch;
+               do {
+                       ch = *buffer++;
+                       de = de85[ch];
+                       if (--de < 0)
+                               return error("invalid base85 alphabet %c", ch);
+                       acc = acc * 85 + de;
+               } while (--cnt);
+               ch = *buffer++;
+               de = de85[ch];
+               if (--de < 0)
+                       return error("invalid base85 alphabet %c", ch);
+               /*
+                * Detect overflow.  The largest
+                * 5-letter possible is "|NsC0" to
+                * encode 0xffffffff, and "|NsC" gives
+                * 0x03030303 at this point (i.e.
+                * 0xffffffff = 0x03030303 * 85).
+                */
+               if (0x03030303 < acc ||
+                   0xffffffff - de < (acc *= 85))
+                       error("invalid base85 sequence %.5s", buffer-5);
+               acc += de;
+               say1(" %08x", acc);
+
+               cnt = (len < 4) ? len : 4;
+               len -= cnt;
+               do {
+                       acc = (acc << 8) | (acc >> 24);
+                       *dst++ = acc;
+               } while (--cnt);
+       }
+       say("\n");
+
+       return 0;
+}
+
+void encode_85(char *buf, unsigned char *data, int bytes)
+{
+       prep_base85();
+
+       say("encode 85");
+       while (bytes) {
+               unsigned acc = 0;
+               int cnt;
+               for (cnt = 24; cnt >= 0; cnt -= 8) {
+                       int ch = *data++;
+                       acc |= ch << cnt;
+                       if (--bytes == 0)
+                               break;
+               }
+               say1(" %08x", acc);
+               for (cnt = 4; cnt >= 0; cnt--) {
+                       int val = acc % 85;
+                       acc /= 85;
+                       buf[cnt] = en85[val];
+               }
+               buf += 5;
+       }
+       say("\n");
+
+       *buf = 0;
+}
+
+#ifdef DEBUG_85
+int main(int ac, char **av)
+{
+       char buf[1024];
+
+       if (!strcmp(av[1], "-e")) {
+               int len = strlen(av[2]);
+               encode_85(buf, av[2], len);
+               if (len <= 26) len = len + 'A' - 1;
+               else len = len + 'a' - 26 + 1;
+               printf("encoded: %c%s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-d")) {
+               int len = *av[2];
+               if ('A' <= len && len <= 'Z') len = len - 'A' + 1;
+               else len = len - 'a' + 26 + 1;
+               decode_85(buf, av[2]+1, len);
+               printf("decoded: %.*s\n", len, buf);
+               return 0;
+       }
+       if (!strcmp(av[1], "-t")) {
+               char t[4] = { -1,-1,-1,-1 };
+               encode_85(buf, t, 4);
+               printf("encoded: D%s\n", buf);
+               return 0;
+       }
+}
+#endif
diff --git a/blame.c b/blame.c
index 07d2d27..99ceea8 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -515,9 +515,9 @@ static int compare_tree_path(struct rev_info* revs,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_compare_tree(revs, c1->tree, c2->tree);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -531,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_same_tree_as_empty(revs, t1);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -834,7 +834,7 @@ int main(int argc, const char **argv)
 
        args[0] = filename;
        args[1] = NULL;
-       diff_tree_setup_paths(args, &rev.diffopt);
+       diff_tree_setup_paths(args, &rev.pruning);
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
diff --git a/builtin-count.c b/builtin-count.c
new file mode 100644 (file)
index 0000000..5ee72df
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Builtin "git count-objects".
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+
+#include "cache.h"
+#include "builtin.h"
+
+static const char count_objects_usage[] = "git-count-objects [-v]";
+
+static void count_objects(DIR *d, char *path, int len, int verbose,
+                         unsigned long *loose,
+                         unsigned long *loose_size,
+                         unsigned long *packed_loose,
+                         unsigned long *garbage)
+{
+       struct dirent *ent;
+       while ((ent = readdir(d)) != NULL) {
+               char hex[41];
+               unsigned char sha1[20];
+               const char *cp;
+               int bad = 0;
+
+               if ((ent->d_name[0] == '.') &&
+                   (ent->d_name[1] == 0 ||
+                    ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+                       continue;
+               for (cp = ent->d_name; *cp; cp++) {
+                       int ch = *cp;
+                       if (('0' <= ch && ch <= '9') ||
+                           ('a' <= ch && ch <= 'f'))
+                               continue;
+                       bad = 1;
+                       break;
+               }
+               if (cp - ent->d_name != 38)
+                       bad = 1;
+               else {
+                       struct stat st;
+                       memcpy(path + len + 3, ent->d_name, 38);
+                       path[len + 2] = '/';
+                       path[len + 41] = 0;
+                       if (lstat(path, &st) || !S_ISREG(st.st_mode))
+                               bad = 1;
+                       else
+                               (*loose_size) += st.st_blocks;
+               }
+               if (bad) {
+                       if (verbose) {
+                               error("garbage found: %.*s/%s",
+                                     len + 2, path, ent->d_name);
+                               (*garbage)++;
+                       }
+                       continue;
+               }
+               (*loose)++;
+               if (!verbose)
+                       continue;
+               memcpy(hex, path+len, 2);
+               memcpy(hex+2, ent->d_name, 38);
+               hex[40] = 0;
+               if (get_sha1_hex(hex, sha1))
+                       die("internal error");
+               if (has_sha1_pack(sha1))
+                       (*packed_loose)++;
+       }
+}
+
+int cmd_count_objects(int ac, const char **av, char **ep)
+{
+       int i;
+       int verbose = 0;
+       const char *objdir = get_object_directory();
+       int len = strlen(objdir);
+       char *path = xmalloc(len + 50);
+       unsigned long loose = 0, packed = 0, packed_loose = 0, garbage = 0;
+       unsigned long loose_size = 0;
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               if (*arg != '-')
+                       break;
+               else if (!strcmp(arg, "-v"))
+                       verbose = 1;
+               else
+                       usage(count_objects_usage);
+       }
+
+       /* we do not take arguments other than flags for now */
+       if (i < ac)
+               usage(count_objects_usage);
+       memcpy(path, objdir, len);
+       if (len && objdir[len-1] != '/')
+               path[len++] = '/';
+       for (i = 0; i < 256; i++) {
+               DIR *d;
+               sprintf(path + len, "%02x", i);
+               d = opendir(path);
+               if (!d)
+                       continue;
+               count_objects(d, path, len, verbose,
+                             &loose, &loose_size, &packed_loose, &garbage);
+               closedir(d);
+       }
+       if (verbose) {
+               struct packed_git *p;
+               if (!packed_git)
+                       prepare_packed_git();
+               for (p = packed_git; p; p = p->next) {
+                       if (!p->pack_local)
+                               continue;
+                       packed += num_packed_objects(p);
+               }
+               printf("count: %lu\n", loose);
+               printf("size: %lu\n", loose_size / 2);
+               printf("in-pack: %lu\n", packed);
+               printf("prune-packable: %lu\n", packed_loose);
+               printf("garbage: %lu\n", garbage);
+       }
+       else
+               printf("%lu objects, %lu kilobytes\n",
+                      loose, loose_size / 2);
+       return 0;
+}
diff --git a/builtin-diff.c b/builtin-diff.c
new file mode 100644 (file)
index 0000000..d3ac581
--- /dev/null
@@ -0,0 +1,368 @@
+/*
+ * Builtin "git diff"
+ *
+ * Copyright (c) 2006 Junio C Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "blob.h"
+#include "tag.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+#include "log-tree.h"
+#include "builtin.h"
+
+/* NEEDSWORK: struct object has place for name but we _do_
+ * know mode when we extracted the blob out of a tree, which
+ * we currently lose.
+ */
+struct blobinfo {
+       unsigned char sha1[20];
+       const char *name;
+};
+
+static const char builtin_diff_usage[] =
+"diff <options> <rev>{0,2} -- <path>*";
+
+static int builtin_diff_files(struct rev_info *revs,
+                             int argc, const char **argv)
+{
+       int silent = 0;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--base"))
+                       revs->max_count = 1;
+               else if (!strcmp(arg, "--ours"))
+                       revs->max_count = 2;
+               else if (!strcmp(arg, "--theirs"))
+                       revs->max_count = 3;
+               else if (!strcmp(arg, "-q"))
+                       silent = 1;
+               else if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there are NO revision (i.e. pending object) parameter,
+        * specified rev.max_count is reasonable (0 <= n <= 3), and
+        * there is no other revision filtering parameter.
+        */
+       if (revs->pending_objects ||
+           revs->min_age != -1 ||
+           revs->max_age != -1 ||
+           3 < revs->max_count)
+               usage(builtin_diff_usage);
+       if (revs->max_count < 0 &&
+           (revs->diffopt.output_format == DIFF_FORMAT_PATCH))
+               revs->combine_merges = revs->dense_combined_merges = 1;
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
+        */
+       if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               revs->diffopt.output_format = DIFF_FORMAT_RAW;
+       return run_diff_files(revs, silent);
+}
+
+static void stuff_change(struct diff_options *opt,
+                        unsigned old_mode, unsigned new_mode,
+                        const unsigned char *old_sha1,
+                        const unsigned char *new_sha1,
+                        const char *old_name,
+                        const char *new_name)
+{
+       struct diff_filespec *one, *two;
+
+       if (memcmp(null_sha1, old_sha1, 20) &&
+           memcmp(null_sha1, new_sha1, 20) &&
+           !memcmp(old_sha1, new_sha1, 20))
+               return;
+
+       if (opt->reverse_diff) {
+               unsigned tmp;
+               const unsigned char *tmp_u;
+               const char *tmp_c;
+               tmp = old_mode; old_mode = new_mode; new_mode = tmp;
+               tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
+               tmp_c = old_name; old_name = new_name; new_name = tmp_c;
+       }
+       one = alloc_filespec(old_name);
+       two = alloc_filespec(new_name);
+       fill_filespec(one, old_sha1, old_mode);
+       fill_filespec(two, new_sha1, new_mode);
+
+       /* NEEDSWORK: shouldn't this part of diffopt??? */
+       diff_queue(&diff_queued_diff, one, two);
+}
+
+static int builtin_diff_b_f(struct rev_info *revs,
+                           int argc, const char **argv,
+                           struct blobinfo *blob,
+                           const char *path)
+{
+       /* Blob vs file in the working tree*/
+       struct stat st;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (lstat(path, &st))
+               die("'%s': %s", path, strerror(errno));
+       if (!(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)))
+               die("'%s': not a regular file or symlink", path);
+       stuff_change(&revs->diffopt,
+                    canon_mode(st.st_mode), canon_mode(st.st_mode),
+                    blob[0].sha1, null_sha1,
+                    blob[0].name, path);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_blobs(struct rev_info *revs,
+                             int argc, const char **argv,
+                             struct blobinfo *blob)
+{
+       /* Blobs */
+       unsigned mode = canon_mode(S_IFREG | 0644);
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       stuff_change(&revs->diffopt,
+                    mode, mode,
+                    blob[0].sha1, blob[1].sha1,
+                    blob[1].name, blob[1].name);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+static int builtin_diff_index(struct rev_info *revs,
+                             int argc, const char **argv)
+{
+       int cached = 0;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!revs->pending_objects || revs->pending_objects->next ||
+           revs->max_count != -1 || revs->min_age != -1 ||
+           revs->max_age != -1)
+               usage(builtin_diff_usage);
+       return run_diff_index(revs, cached);
+}
+
+static int builtin_diff_tree(struct rev_info *revs,
+                            int argc, const char **argv,
+                            struct object_list *ent)
+{
+       const unsigned char *(sha1[2]);
+       int swap = 1;
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+
+       /* We saw two trees, ent[0] and ent[1].
+        * unless ent[0] is unintesting, they are swapped
+        */
+       if (ent[0].item->flags & UNINTERESTING)
+               swap = 0;
+       sha1[swap] = ent[0].item->sha1;
+       sha1[1-swap] = ent[1].item->sha1;
+       diff_tree_sha1(sha1[0], sha1[1], "", &revs->diffopt);
+       log_tree_diff_flush(revs);
+       return 0;
+}
+
+static int builtin_diff_combined(struct rev_info *revs,
+                                int argc, const char **argv,
+                                struct object_list *ent,
+                                int ents)
+{
+       const unsigned char (*parent)[20];
+       int i;
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (!strcmp(arg, "--raw"))
+                       revs->diffopt.output_format = DIFF_FORMAT_RAW;
+               else
+                       usage(builtin_diff_usage);
+               argv++; argc--;
+       }
+       if (!revs->dense_combined_merges && !revs->combine_merges)
+               revs->dense_combined_merges = revs->combine_merges = 1;
+       parent = xmalloc(ents * sizeof(*parent));
+       /* Again, the revs are all reverse */
+       for (i = 0; i < ents; i++)
+               memcpy(parent + i, ent[ents - 1 - i].item->sha1, 20);
+       diff_tree_combined(parent[0], parent + 1, ents - 1,
+                          revs->dense_combined_merges, revs);
+       return 0;
+}
+
+static void add_head(struct rev_info *revs)
+{
+       unsigned char sha1[20];
+       struct object *obj;
+       if (get_sha1("HEAD", sha1))
+               return;
+       obj = parse_object(sha1);
+       if (!obj)
+               return;
+       add_object(obj, &revs->pending_objects, NULL, "HEAD");
+}
+
+int cmd_diff(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+       struct object_list *list, ent[100];
+       int ents = 0, blobs = 0, paths = 0;
+       const char *path = NULL;
+       struct blobinfo blob[2];
+
+       /*
+        * We could get N tree-ish in the rev.pending_objects list.
+        * Also there could be M blobs there, and P pathspecs.
+        *
+        * N=0, M=0:
+        *      cache vs files (diff-files)
+        * N=0, M=2:
+        *      compare two random blobs.  P must be zero.
+        * N=0, M=1, P=1:
+        *      compare a blob with a working tree file.
+        *
+        * N=1, M=0:
+        *      tree vs cache (diff-index --cached)
+        *
+        * N=2, M=0:
+        *      tree vs tree (diff-tree)
+        *
+        * Other cases are errors.
+        */
+
+       git_config(git_diff_config);
+       init_revisions(&rev);
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       /* Do we have --cached and not have a pending object, then
+        * default to HEAD by hand.  Eek.
+        */
+       if (!rev.pending_objects) {
+               int i;
+               for (i = 1; i < argc; i++) {
+                       const char *arg = argv[i];
+                       if (!strcmp(arg, "--"))
+                               break;
+                       else if (!strcmp(arg, "--cached")) {
+                               add_head(&rev);
+                               break;
+                       }
+               }
+       }
+
+       for (list = rev.pending_objects; list; list = list->next) {
+               struct object *obj = list->item;
+               const char *name = list->name;
+               int flags = (obj->flags & UNINTERESTING);
+               if (!obj->parsed)
+                       obj = parse_object(obj->sha1);
+               obj = deref_tag(obj, NULL, 0);
+               if (!obj)
+                       die("invalid object '%s' given.", name);
+               if (!strcmp(obj->type, commit_type))
+                       obj = &((struct commit *)obj)->tree->object;
+               if (!strcmp(obj->type, tree_type)) {
+                       if (ARRAY_SIZE(ent) <= ents)
+                               die("more than %d trees given: '%s'",
+                                   (int) ARRAY_SIZE(ent), name);
+                       obj->flags |= flags;
+                       ent[ents].item = obj;
+                       ent[ents].name = name;
+                       ents++;
+                       continue;
+               }
+               if (!strcmp(obj->type, blob_type)) {
+                       if (2 <= blobs)
+                               die("more than two blobs given: '%s'", name);
+                       memcpy(blob[blobs].sha1, obj->sha1, 20);
+                       blob[blobs].name = name;
+                       blobs++;
+                       continue;
+
+               }
+               die("unhandled object '%s' given.", name);
+       }
+       if (rev.prune_data) {
+               const char **pathspec = rev.prune_data;
+               while (*pathspec) {
+                       if (!path)
+                               path = *pathspec;
+                       paths++;
+                       pathspec++;
+               }
+       }
+
+       /*
+        * Now, do the arguments look reasonable?
+        */
+       if (!ents) {
+               switch (blobs) {
+               case 0:
+                       return builtin_diff_files(&rev, argc, argv);
+                       break;
+               case 1:
+                       if (paths != 1)
+                               usage(builtin_diff_usage);
+                       return builtin_diff_b_f(&rev, argc, argv, blob, path);
+                       break;
+               case 2:
+                       if (paths)
+                               usage(builtin_diff_usage);
+                       return builtin_diff_blobs(&rev, argc, argv, blob);
+                       break;
+               default:
+                       usage(builtin_diff_usage);
+               }
+       }
+       else if (blobs)
+               usage(builtin_diff_usage);
+       else if (ents == 1)
+               return builtin_diff_index(&rev, argc, argv);
+       else if (ents == 2)
+               return builtin_diff_tree(&rev, argc, argv, ent);
+       else
+               return builtin_diff_combined(&rev, argc, argv, ent, ents);
+       usage(builtin_diff_usage);
+}
diff --git a/builtin-push.c b/builtin-push.c
new file mode 100644 (file)
index 0000000..e530022
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ * "git push"
+ */
+#include "cache.h"
+#include "refs.h"
+#include "run-command.h"
+#include "builtin.h"
+
+#define MAX_URI (16)
+
+static const char push_usage[] = "git push [--all] [--tags] [--force] <repository> [<refspec>...]";
+
+static int all = 0, tags = 0, force = 0, thin = 1;
+static const char *execute = NULL;
+
+#define BUF_SIZE (2084)
+static char buffer[BUF_SIZE];
+
+static const char **refspec = NULL;
+static int refspec_nr = 0;
+
+static void add_refspec(const char *ref)
+{
+       int nr = refspec_nr + 1;
+       refspec = xrealloc(refspec, nr * sizeof(char *));
+       refspec[nr-1] = ref;
+       refspec_nr = nr;
+}
+
+static int expand_one_ref(const char *ref, const unsigned char *sha1)
+{
+       /* Ignore the "refs/" at the beginning of the refname */
+       ref += 5;
+
+       if (strncmp(ref, "tags/", 5))
+               return 0;
+
+       add_refspec(strdup(ref));
+       return 0;
+}
+
+static void expand_refspecs(void)
+{
+       if (all) {
+               if (refspec_nr)
+                       die("cannot mix '--all' and a refspec");
+
+               /*
+                * No need to expand "--all" - we'll just use
+                * the "--all" flag to send-pack
+                */
+               return;
+       }
+       if (!tags)
+               return;
+       for_each_ref(expand_one_ref);
+}
+
+static void set_refspecs(const char **refs, int nr)
+{
+       if (nr) {
+               size_t bytes = nr * sizeof(char *);
+
+               refspec = xrealloc(refspec, bytes);
+               memcpy(refspec, refs, bytes);
+               refspec_nr = nr;
+       }
+       expand_refspecs();
+}
+
+static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       int n = 0;
+       FILE *f = fopen(git_path("remotes/%s", repo), "r");
+       int has_explicit_refspec = refspec_nr || all || tags;
+
+       if (!f)
+               return -1;
+       while (fgets(buffer, BUF_SIZE, f)) {
+               int is_refspec;
+               char *s, *p;
+
+               if (!strncmp("URL: ", buffer, 5)) {
+                       is_refspec = 0;
+                       s = buffer + 5;
+               } else if (!strncmp("Push: ", buffer, 6)) {
+                       is_refspec = 1;
+                       s = buffer + 6;
+               } else
+                       continue;
+
+               /* Remove whitespace at the head.. */
+               while (isspace(*s))
+                       s++;
+               if (!*s)
+                       continue;
+
+               /* ..and at the end */
+               p = s + strlen(s);
+               while (isspace(p[-1]))
+                       *--p = 0;
+
+               if (!is_refspec) {
+                       if (n < MAX_URI)
+                               uri[n++] = strdup(s);
+                       else
+                               error("more than %d URL's specified, ignoreing the rest", MAX_URI);
+               }
+               else if (is_refspec && !has_explicit_refspec)
+                       add_refspec(strdup(s));
+       }
+       fclose(f);
+       if (!n)
+               die("remote '%s' has no URL", repo);
+       return n;
+}
+
+static const char **config_uri;
+static const char *config_repo;
+static int config_repo_len;
+static int config_current_uri;
+static int config_get_refspecs;
+
+static int get_remote_config(const char* key, const char* value)
+{
+       if (!strncmp(key, "remote.", 7) &&
+           !strncmp(key + 7, config_repo, config_repo_len)) {
+               if (!strcmp(key + 7 + config_repo_len, ".url")) {
+                       if (config_current_uri < MAX_URI)
+                               config_uri[config_current_uri++] = strdup(value);
+                       else
+                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
+               }
+               else if (config_get_refspecs &&
+                        !strcmp(key + 7 + config_repo_len, ".push"))
+                       add_refspec(strdup(value));
+       }
+       return 0;
+}
+
+static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
+{
+       config_repo_len = strlen(repo);
+       config_repo = repo;
+       config_current_uri = 0;
+       config_uri = uri;
+       config_get_refspecs = !(refspec_nr || all || tags);
+
+       git_config(get_remote_config);
+       return config_current_uri;
+}
+
+static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
+{
+       const char *slash = strchr(repo, '/');
+       int n = slash ? slash - repo : 1000;
+       FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
+       char *s, *p;
+       int len;
+
+       if (!f)
+               return 0;
+       s = fgets(buffer, BUF_SIZE, f);
+       fclose(f);
+       if (!s)
+               return 0;
+       while (isspace(*s))
+               s++;
+       if (!*s)
+               return 0;
+       p = s + strlen(s);
+       while (isspace(p[-1]))
+               *--p = 0;
+       len = p - s;
+       if (slash)
+               len += strlen(slash);
+       p = xmalloc(len + 1);
+       strcpy(p, s);
+       if (slash)
+               strcat(p, slash);
+       uri[0] = p;
+       return 1;
+}
+
+/*
+ * Read remotes and branches file, fill the push target URI
+ * list.  If there is no command line refspecs, read Push: lines
+ * to set up the *refspec list as well.
+ * return the number of push target URIs
+ */
+static int read_config(const char *repo, const char *uri[MAX_URI])
+{
+       int n;
+
+       if (*repo != '/') {
+               n = get_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_config_remotes_uri(repo, uri);
+               if (n > 0)
+                       return n;
+
+               n = get_branches_uri(repo, uri);
+               if (n > 0)
+                       return n;
+       }
+
+       uri[0] = repo;
+       return 1;
+}
+
+static int do_push(const char *repo)
+{
+       const char *uri[MAX_URI];
+       int i, n;
+       int remote;
+       const char **argv;
+       int argc;
+
+       n = read_config(repo, uri);
+       if (n <= 0)
+               die("bad repository '%s'", repo);
+
+       argv = xmalloc((refspec_nr + 10) * sizeof(char *));
+       argv[0] = "dummy-send-pack";
+       argc = 1;
+       if (all)
+               argv[argc++] = "--all";
+       if (force)
+               argv[argc++] = "--force";
+       if (execute)
+               argv[argc++] = execute;
+       if (thin)
+               argv[argc++] = "--thin";
+       remote = argc;
+       argv[argc++] = "dummy-remote";
+       while (refspec_nr--)
+               argv[argc++] = *refspec++;
+       argv[argc] = NULL;
+
+       for (i = 0; i < n; i++) {
+               int error;
+               const char *dest = uri[i];
+               const char *sender = "git-send-pack";
+               if (!strncmp(dest, "http://", 7) ||
+                   !strncmp(dest, "https://", 8))
+                       sender = "git-http-push";
+               argv[0] = sender;
+               argv[remote] = dest;
+               error = run_command_v(argc, argv);
+               if (!error)
+                       continue;
+               switch (error) {
+               case -ERR_RUN_COMMAND_FORK:
+                       die("unable to fork for %s", sender);
+               case -ERR_RUN_COMMAND_EXEC:
+                       die("unable to exec %s", sender);
+               case -ERR_RUN_COMMAND_WAITPID:
+               case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
+               case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
+               case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
+                       die("%s died with strange error", sender);
+               default:
+                       return -error;
+               }
+       }
+       return 0;
+}
+
+int cmd_push(int argc, const char **argv, char **envp)
+{
+       int i;
+       const char *repo = "origin";    // default repository
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-') {
+                       repo = arg;
+                       i++;
+                       break;
+               }
+               if (!strcmp(arg, "--all")) {
+                       all = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--tags")) {
+                       tags = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--force")) {
+                       force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--thin")) {
+                       thin = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-thin")) {
+                       thin = 0;
+                       continue;
+               }
+               if (!strncmp(arg, "--exec=", 7)) {
+                       execute = arg;
+                       continue;
+               }
+               usage(push_usage);
+       }
+       set_refspecs(argv + i, argc - i);
+       return do_push(repo);
+}
index 47408a0..bb63f07 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -19,5 +19,9 @@ extern int cmd_version(int argc, const char **argv, char **envp);
 extern int cmd_whatchanged(int argc, const char **argv, char **envp);
 extern int cmd_show(int argc, const char **argv, char **envp);
 extern int cmd_log(int argc, const char **argv, char **envp);
+extern int cmd_diff(int argc, const char **argv, char **envp);
+extern int cmd_count_objects(int argc, const char **argv, char **envp);
+
+extern int cmd_push(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/cache.h b/cache.h
index 4d8fabc..4b7a439 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -135,6 +135,7 @@ extern const char *setup_git_directory(void);
 extern const char *prefix_path(const char *prefix, int len, const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char *path);
 extern void verify_filename(const char *prefix, const char *name);
+extern void verify_non_filename(const char *prefix, const char *name);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 
@@ -168,7 +169,7 @@ extern void rollback_index_file(struct cache_file *);
 /* Environment bits from configuration mechanism */
 extern int trust_executable_bit;
 extern int assume_unchanged;
-extern int only_use_symrefs;
+extern int prefer_symlink_refs;
 extern int warn_ambiguous_refs;
 extern int diff_rename_limit_default;
 extern int shared_repository;
@@ -362,4 +363,8 @@ extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 /* pager.c */
 extern void setup_pager(void);
 
+/* base85 */
+int decode_85(char *dst, char *line, int linelen);
+void encode_85(char *buf, unsigned char *data, int bytes);
+
 #endif /* CACHE_H */
index 628f6ca..7413fee 100644 (file)
@@ -103,8 +103,10 @@ int main(int argc, char **argv)
 
        setup_git_directory();
        git_config(git_default_config);
-       if (argc != 3 || get_sha1(argv[2], sha1))
+       if (argc != 3)
                usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
+       if (get_sha1(argv[2], sha1))
+               die("Not a valid object name %s", argv[2]);
 
        opt = 0;
        if ( argv[1][0] == '-' ) {
@@ -133,8 +135,7 @@ int main(int argc, char **argv)
                return !has_sha1_file(sha1);
 
        case 'p':
-               if (get_sha1(argv[2], sha1) ||
-                   sha1_object_info(sha1, type, NULL))
+               if (sha1_object_info(sha1, type, NULL))
                        die("Not a valid object name %s", argv[2]);
 
                /* custom pretty-print here */
index dd6a2d8..cc3a745 100644 (file)
@@ -269,12 +269,16 @@ int main(int argc, char **argv)
        /* Check out named files first */
        for ( ; i < argc; i++) {
                const char *arg = argv[i];
+               const char *p;
 
                if (all)
                        die("git-checkout-index: don't mix '--all' and explicit filenames");
                if (read_from_stdin)
                        die("git-checkout-index: don't mix '--stdin' and explicit filenames");
-               checkout_file(prefix_path(prefix, prefix_length, arg));
+               p = prefix_path(prefix, prefix_length, arg);
+               checkout_file(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
        }
 
        if (read_from_stdin) {
@@ -284,6 +288,8 @@ int main(int argc, char **argv)
                strbuf_init(&buf);
                while (1) {
                        char *path_name;
+                       const char *p;
+
                        read_line(&buf, stdin, line_termination);
                        if (buf.eof)
                                break;
@@ -291,7 +297,10 @@ int main(int argc, char **argv)
                                path_name = unquote_c_style(buf.buf, NULL);
                        else
                                path_name = buf.buf;
-                       checkout_file(prefix_path(prefix, prefix_length, path_name));
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       checkout_file(p);
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char *)p);
                        if (path_name != buf.buf)
                                free(path_name);
                }
index ca36f5d..8a8fe38 100644 (file)
@@ -831,15 +831,16 @@ void show_combined_diff(struct combine_diff_path *p,
        }
 }
 
-void diff_tree_combined_merge(const unsigned char *sha1,
-                            int dense, struct rev_info *rev)
+void diff_tree_combined(const unsigned char *sha1,
+                       const unsigned char parent[][20],
+                       int num_parent,
+                       int dense,
+                       struct rev_info *rev)
 {
        struct diff_options *opt = &rev->diffopt;
-       struct commit *commit = lookup_commit(sha1);
        struct diff_options diffopts;
-       struct commit_list *parents;
        struct combine_diff_path *p, *paths = NULL;
-       int num_parent, i, num_paths;
+       int i, num_paths;
        int do_diffstat;
 
        do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
@@ -849,17 +850,8 @@ void diff_tree_combined_merge(const unsigned char *sha1,
        diffopts.with_stat = 0;
        diffopts.recursive = 1;
 
-       /* count parents */
-       for (parents = commit->parents, num_parent = 0;
-            parents;
-            parents = parents->next, num_parent++)
-               ; /* nothing */
-
        /* find set of paths that everybody touches */
-       for (parents = commit->parents, i = 0;
-            parents;
-            parents = parents->next, i++) {
-               struct commit *parent = parents->item;
+       for (i = 0; i < num_parent; i++) {
                /* show stat against the first parent even
                 * when doing combined diff.
                 */
@@ -867,8 +859,7 @@ void diff_tree_combined_merge(const unsigned char *sha1,
                        diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
                else
                        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
-               diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
-                              &diffopts);
+               diff_tree_sha1(parent[i], sha1, "", &diffopts);
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
 
@@ -907,3 +898,25 @@ void diff_tree_combined_merge(const unsigned char *sha1,
                free(tmp);
        }
 }
+
+void diff_tree_combined_merge(const unsigned char *sha1,
+                            int dense, struct rev_info *rev)
+{
+       int num_parent;
+       const unsigned char (*parent)[20];
+       struct commit *commit = lookup_commit(sha1);
+       struct commit_list *parents;
+
+       /* count parents */
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               ; /* nothing */
+
+       parent = xmalloc(num_parent * sizeof(*parent));
+       for (parents = commit->parents, num_parent = 0;
+            parents;
+            parents = parents->next, num_parent++)
+               memcpy(parent + num_parent, parents->item->object.sha1, 20);
+       diff_tree_combined(sha1, parent, num_parent, dense, rev);
+}
index bad72e8..0320036 100644 (file)
@@ -91,15 +91,19 @@ int main(int argc, char **argv)
 
        git_config(git_default_config);
 
-       if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0)
+       if (argc < 2)
                usage(commit_tree_usage);
+       if (get_sha1(argv[1], tree_sha1))
+               die("Not a valid object name %s", argv[1]);
 
        check_valid(tree_sha1, tree_type);
        for (i = 2; i < argc; i += 2) {
                char *a, *b;
                a = argv[i]; b = argv[i+1];
-               if (!b || strcmp(a, "-p") || get_sha1(b, parent_sha1[parents]))
+               if (!b || strcmp(a, "-p"))
                        usage(commit_tree_usage);
+               if (get_sha1(b, parent_sha1[parents]))
+                       die("Not a valid object name %s", b);
                check_valid(parent_sha1[parents], commit_type);
                if (new_parent(parents))
                        parents++;
index 4e1f0c2..0f518c9 100644 (file)
--- a/config.c
+++ b/config.c
@@ -60,6 +60,12 @@ static char *parse_value(void)
                        space = 1;
                        continue;
                }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
                if (space) {
                        if (len)
                                value[len++] = ' ';
@@ -93,12 +99,6 @@ static char *parse_value(void)
                        quote = 1-quote;
                        continue;
                }
-               if (!quote) {
-                       if (c == ';' || c == '#') {
-                               comment = 1;
-                               continue;
-                       }
-               }
                value[len++] = c;
        }
 }
@@ -227,8 +227,8 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.symrefsonly")) {
-               only_use_symrefs = git_config_bool(var, value);
+       if (!strcmp(var, "core.prefersymlinkrefs")) {
+               prefer_symlink_refs = git_config_bool(var, value);
                return 0;
        }
 
@@ -335,8 +335,11 @@ static int store_aux(const char* key, const char* value)
                        store.offset[store.seen] = ftell(config_file);
                        store.state = KEY_SEEN;
                        store.seen++;
-               } else if(!strncmp(key, store.key, store.baselen))
-                       store.state = SECTION_SEEN;
+               } else if (strrchr(key, '.') - key == store.baselen &&
+                             !strncmp(key, store.key, store.baselen)) {
+                                       store.state = SECTION_SEEN;
+                                       store.offset[store.seen] = ftell(config_file);
+               }
        }
        return 0;
 }
@@ -419,7 +422,7 @@ int git_config_set_multivar(const char* key, const char* value,
        const char* value_regex, int multi_replace)
 {
        int i;
-       int fd, in_fd;
+       int fd = -1, in_fd;
        int ret;
        char* config_filename = strdup(git_path("config"));
        char* lock_file = strdup(git_path("config.lock"));
@@ -477,15 +480,11 @@ int git_config_set_multivar(const char* key, const char* value,
                if ( ENOENT != errno ) {
                        error("opening %s: %s", config_filename,
                              strerror(errno));
-                       close(fd);
-                       unlink(lock_file);
                        ret = 3; /* same as "invalid config file" */
                        goto out_free;
                }
                /* if nothing to unset, error out */
                if (value == NULL) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -548,8 +547,6 @@ int git_config_set_multivar(const char* key, const char* value,
                /* if nothing to unset, or too many matches, error out */
                if ((store.seen == 0 && value == NULL) ||
                                (store.seen > 1 && multi_replace == 0)) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -598,8 +595,6 @@ int git_config_set_multivar(const char* key, const char* value,
                unlink(config_filename);
        }
 
-       close(fd);
-
        if (rename(lock_file, config_filename) < 0) {
                fprintf(stderr, "Could not rename the lock file?\n");
                ret = 4;
@@ -609,10 +604,14 @@ int git_config_set_multivar(const char* key, const char* value,
        ret = 0;
 
 out_free:
+       if (0 <= fd)
+               close(fd);
        if (config_filename)
                free(config_filename);
-       if (lock_file)
+       if (lock_file) {
+               unlink(lock_file);
                free(lock_file);
+       }
        return ret;
 }
 
index 7c44450..de13a96 100755 (executable)
@@ -8,7 +8,7 @@ use vars qw/    $AUTHOR $VERSION
                $GIT_SVN_INDEX $GIT_SVN
                $GIT_DIR $REV_DIR/;
 $AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
-$VERSION = '0.11.0';
+$VERSION = '1.0.0';
 
 use Cwd qw/abs_path/;
 $GIT_DIR = abs_path($ENV{GIT_DIR} || '.git');
@@ -42,7 +42,8 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
 my %cmd = (
        fetch => [ \&fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision, %fc_opts } ],
-       init => [ \&init, "Initialize and fetch (import)", { } ],
+       init => [ \&init, "Initialize a repo for tracking" .
+                         " (requires URL argument)", { } ],
        commit => [ \&commit, "Commit git revisions to SVN",
                        {       'stdin|' => \$_stdin,
                                'edit|e' => \$_edit,
@@ -220,7 +221,8 @@ when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
 }
 
 sub init {
-       $SVN_URL = shift or croak "SVN repository location required\n";
+       $SVN_URL = shift or die "SVN repository location required " .
+                               "as a command-line argument\n";
        unless (-d $GIT_DIR) {
                sys('git-init-db');
        }
index e18fcaf..f7d3de4 100644 (file)
@@ -36,17 +36,22 @@ COMMANDS
 --------
 init::
        Creates an empty git repository with additional metadata
-       directories for git-svn.  The SVN_URL must be specified
-       at this point.
+       directories for git-svn.  The Subversion URL must be specified
+       as a command-line argument.
 
 fetch::
-       Fetch unfetched revisions from the SVN_URL we are tracking.
-       refs/heads/remotes/git-svn will be updated to the latest revision.
+       Fetch unfetched revisions from the Subversion URL we are
+       tracking.  refs/remotes/git-svn will be updated to the
+       latest revision.
 
-       Note: You should never attempt to modify the remotes/git-svn branch
-       outside of git-svn.  Instead, create a branch from remotes/git-svn
-       and work on that branch.  Use the 'commit' command (see below)
-       to write git commits back to remotes/git-svn.
+       Note: You should never attempt to modify the remotes/git-svn
+       branch outside of git-svn.  Instead, create a branch from
+       remotes/git-svn and work on that branch.  Use the 'commit'
+       command (see below) to write git commits back to
+       remotes/git-svn.
+
+       See 'Additional Fetch Arguments' if you are interested in
+       manually joining branches on commit.
 
 commit::
        Commit specified commit or tree objects to SVN.  This relies on
@@ -62,9 +67,9 @@ rebuild::
        tracked with git-svn.  Unfortunately, git-clone does not clone
        git-svn metadata and the svn working tree that git-svn uses for
        its operations.  This rebuilds the metadata so git-svn can
-       resume fetch operations.  SVN_URL may be optionally specified if
-       the directory/repository you're tracking has moved or changed
-       protocols.
+       resume fetch operations.  A Subversion URL may be optionally
+       specified at the command-line if the directory/repository you're
+       tracking has moved or changed protocols.
 
 show-ignore::
        Recursively finds and lists the svn:ignore property on
@@ -123,6 +128,24 @@ OPTIONS
        repo-config key: svn.l
        repo-config key: svn.findcopiesharder
 
+-A<filename>::
+--authors-file=<filename>::
+
+       Syntax is compatible with the files used by git-svnimport and
+       git-cvsimport:
+
+------------------------------------------------------------------------
+loginname = Joe User <user@example.com>
+------------------------------------------------------------------------
+
+       If this option is specified and git-svn encounters an SVN
+       committer name that does not exist in the authors-file, git-svn
+       will abort operation. The user will then have to add the
+       appropriate entry.  Re-running the previous git-svn command
+       after the authors-file is modified should continue operation.
+
+       repo-config key: svn.authors-file
+
 ADVANCED OPTIONS
 ----------------
 -b<refname>::
index 12aacef..a67d6b4 100644 (file)
@@ -321,8 +321,10 @@ int main(int argc, char **argv)
 
        setup_git_directory();
 
-       if (argc != 2 || get_sha1(argv[1], sha1))
+       if (argc != 2)
                usage("git-convert-objects <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        entry = convert_entry(sha1);
        printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
diff --git a/delta.h b/delta.h
index 9464f3e..727ae30 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -1,12 +1,73 @@
 #ifndef DELTA_H
 #define DELTA_H
 
-/* handling of delta buffers */
-extern void *diff_delta(void *from_buf, unsigned long from_size,
-                       void *to_buf, unsigned long to_size,
-                       unsigned long *delta_size, unsigned long max_size);
-extern void *patch_delta(void *src_buf, unsigned long src_size,
-                        void *delta_buf, unsigned long delta_size,
+/* opaque object for delta index */
+struct delta_index;
+
+/*
+ * create_delta_index: compute index data from given buffer
+ *
+ * This returns a pointer to a struct delta_index that should be passed to
+ * subsequent create_delta() calls, or to free_delta_index().  A NULL pointer
+ * is returned on failure.  The given buffer must not be freed nor altered
+ * before free_delta_index() is called.  The returned pointer must be freed
+ * using free_delta_index().
+ */
+extern struct delta_index *
+create_delta_index(const void *buf, unsigned long bufsize);
+
+/*
+ * free_delta_index: free the index created by create_delta_index()
+ */
+extern void free_delta_index(struct delta_index *index);
+
+/*
+ * create_delta: create a delta from given index for the given buffer
+ *
+ * This function may be called multiple times with different buffers using
+ * the same delta_index pointer.  If max_delta_size is non-zero and the
+ * resulting delta is to be larger than max_delta_size then NULL is returned.
+ * On success, a non-NULL pointer to the buffer with the delta data is
+ * returned and *delta_size is updated with its size.  The returned buffer
+ * must be freed by the caller.
+ */
+extern void *
+create_delta(const struct delta_index *index,
+            const void *buf, unsigned long bufsize,
+            unsigned long *delta_size, unsigned long max_delta_size);
+
+/*
+ * diff_delta: create a delta from source buffer to target buffer
+ *
+ * If max_delta_size is non-zero and the resulting delta is to be larger
+ * than max_delta_size then NULL is returned.  On success, a non-NULL
+ * pointer to the buffer with the delta data is returned and *delta_size is
+ * updated with its size.  The returned buffer must be freed by the caller.
+ */
+static inline void *
+diff_delta(const void *src_buf, unsigned long src_bufsize,
+          const void *trg_buf, unsigned long trg_bufsize,
+          unsigned long *delta_size, unsigned long max_delta_size)
+{
+       struct delta_index *index = create_delta_index(src_buf, src_bufsize);
+       if (index) {
+               void *delta = create_delta(index, trg_buf, trg_bufsize,
+                                          delta_size, max_delta_size);
+               free_delta_index(index);
+               return delta;
+       }
+       return NULL;
+}
+
+/*
+ * patch_delta: recreate target buffer given source buffer and delta data
+ *
+ * On success, a non-NULL pointer to the target buffer is returned and
+ * *trg_bufsize is updated with its size.  On failure a NULL pointer is
+ * returned.  The returned buffer must be freed by the caller.
+ */
+extern void *patch_delta(const void *src_buf, unsigned long src_size,
+                        const void *delta_buf, unsigned long delta_size,
                         unsigned long *dst_size);
 
 /* the smallest possible delta size is 4 bytes */
@@ -14,7 +75,7 @@ extern void *patch_delta(void *src_buf, unsigned long src_size,
 
 /*
  * This must be called twice on the delta data buffer, first to get the
- * expected reference buffer size, and again to get the result buffer size.
+ * expected source buffer size, and again to get the target buffer size.
  */
 static inline unsigned long get_delta_hdr_size(const unsigned char **datap,
                                               const unsigned char *top)
index ff65742..8a9cd5d 100644 (file)
@@ -105,11 +105,11 @@ static void describe(char *arg, int last_one)
        static int initialized = 0;
        struct commit_name *n;
 
-       if (get_sha1(arg, sha1) < 0)
-               usage(describe_usage);
+       if (get_sha1(arg, sha1))
+               die("Not a valid object name %s", arg);
        cmit = lookup_commit_reference(sha1);
        if (!cmit)
-               usage(describe_usage);
+               die("%s is not a valid '%s' object", arg, commit_type);
 
        if (!initialized) {
                initialized = 1;
index 1188b31..c618875 100644 (file)
 
 #include <stdlib.h>
 #include <string.h>
-#include <zlib.h>
 #include "delta.h"
 
 
-/* block size: min = 16, max = 64k, power of 2 */
-#define BLK_SIZE 16
-
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
+/* maximum hash entry list for the same hash bucket */
+#define HASH_LIMIT 64
+
+#define RABIN_SHIFT 23
+#define RABIN_WINDOW 16
+
+static const unsigned int T[256] = {
+       0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
+       0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
+       0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
+       0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
+       0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
+       0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
+       0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
+       0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
+       0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
+       0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
+       0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
+       0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
+       0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
+       0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
+       0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
+       0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
+       0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
+       0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
+       0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
+       0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
+       0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
+       0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
+       0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
+       0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
+       0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
+       0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
+       0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
+       0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
+       0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
+       0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
+       0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
+       0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
+       0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
+       0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
+       0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
+       0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
+       0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
+       0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
+       0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
+       0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
+       0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
+       0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
+       0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
+};
 
-#define GR_PRIME 0x9e370001
-#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
+static const unsigned int U[256] = {
+       0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
+       0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
+       0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
+       0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
+       0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
+       0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
+       0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
+       0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
+       0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
+       0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
+       0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
+       0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
+       0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
+       0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
+       0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
+       0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
+       0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
+       0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
+       0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
+       0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
+       0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
+       0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
+       0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
+       0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
+       0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
+       0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
+       0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
+       0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
+       0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
+       0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
+       0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
+       0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
+       0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
+       0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
+       0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
+       0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
+       0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
+       0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
+       0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
+       0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
+       0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
+       0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
+       0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
+};
 
-struct index {
+struct index_entry {
        const unsigned char *ptr;
        unsigned int val;
-       struct index *next;
+       struct index_entry *next;
+};
+
+struct delta_index {
+       const void *src_buf;
+       unsigned long src_size;
+       unsigned int hash_mask;
+       struct index_entry *hash[0];
 };
 
-static struct index ** delta_index(const unsigned char *buf,
-                                  unsigned long bufsize,
-                                  unsigned long trg_bufsize,
-                                  unsigned int *hash_shift)
+struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
 {
-       unsigned int i, hsize, hshift, hlimit, entries, *hash_count;
-       const unsigned char *data;
-       struct index *entry, **hash;
+       unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
+       const unsigned char *data, *buffer = buf;
+       struct delta_index *index;
+       struct index_entry *entry, **hash;
        void *mem;
+       unsigned long memsize;
+
+       if (!buf || !bufsize)
+               return NULL;
 
-       /* determine index hash size */
-       entries = bufsize  / BLK_SIZE;
+       /* Determine index hash size.  Note that indexing skips the
+          first byte to allow for optimizing the rabin polynomial
+          initialization in create_delta(). */
+       entries = (bufsize - 1)  / RABIN_WINDOW;
        hsize = entries / 4;
        for (i = 4; (1 << i) < hsize && i < 31; i++);
        hsize = 1 << i;
-       hshift = 32 - i;
-       *hash_shift = hshift;
+       hmask = hsize - 1;
 
        /* allocate lookup index */
-       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+       memsize = sizeof(*index) +
+                 sizeof(*hash) * hsize +
+                 sizeof(*entry) * entries;
+       mem = malloc(memsize);
        if (!mem)
                return NULL;
+       index = mem;
+       mem = index + 1;
        hash = mem;
-       entry = mem + hsize * sizeof(*hash);
+       mem = hash + hsize;
+       entry = mem;
+
+       index->src_buf = buf;
+       index->src_size = bufsize;
+       index->hash_mask = hmask;
        memset(hash, 0, hsize * sizeof(*hash));
 
        /* allocate an array to count hash entries */
        hash_count = calloc(hsize, sizeof(*hash_count));
        if (!hash_count) {
-               free(hash);
+               free(index);
                return NULL;
        }
 
        /* then populate the index */
-       data = buf + entries * BLK_SIZE - BLK_SIZE;
-       while (data >= buf) {
-               unsigned int val = adler32(0, data, BLK_SIZE);
-               i = HASH(val, hshift);
-               entry->ptr = data;
-               entry->val = val;
-               entry->next = hash[i];
-               hash[i] = entry++;
-               hash_count[i]++;
-               data -= BLK_SIZE;
-       }
+       prev_val = ~0;
+       for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
+            data >= buffer;
+            data -= RABIN_WINDOW) {
+               unsigned int val = 0;
+               for (i = 1; i <= RABIN_WINDOW; i++)
+                       val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
+               if (val == prev_val) {
+                       /* keep the lowest of consecutive identical blocks */
+                       entry[-1].ptr = data + RABIN_WINDOW;
+               } else {
+                       prev_val = val;
+                       i = val & hmask;
+                       entry->ptr = data + RABIN_WINDOW;
+                       entry->val = val;
+                       entry->next = hash[i];
+                       hash[i] = entry++;
+                       hash_count[i]++;
+                       entries--;
+               }
+       }
 
        /*
         * Determine a limit on the number of entries in the same hash
@@ -91,27 +210,18 @@ static struct index ** delta_index(const unsigned char *buf,
         * bucket that would bring us to O(m*n) computing costs (m and n
         * corresponding to reference and target buffer sizes).
         *
-        * The more the target buffer is large, the more it is important to
-        * have small entry lists for each hash buckets.  With such a limit
-        * the cost is bounded to something more like O(m+n).
-        */
-       hlimit = (1 << 26) / trg_bufsize;
-       if (hlimit < 4*BLK_SIZE)
-               hlimit = 4*BLK_SIZE;
-
-       /*
-        * Now make sure none of the hash buckets has more entries than
+        * Make sure none of the hash buckets has more entries than
         * we're willing to test.  Otherwise we cull the entry list
         * uniformly to still preserve a good repartition across
         * the reference buffer.
         */
        for (i = 0; i < hsize; i++) {
-               if (hash_count[i] < hlimit)
+               if (hash_count[i] < HASH_LIMIT)
                        continue;
                entry = hash[i];
                do {
-                       struct index *keep = entry;
-                       int skip = hash_count[i] / hlimit / 2;
+                       struct index_entry *keep = entry;
+                       int skip = hash_count[i] / HASH_LIMIT / 2;
                        do {
                                entry = entry->next;
                        } while(--skip && entry);
@@ -120,32 +230,35 @@ static struct index ** delta_index(const unsigned char *buf,
        }
        free(hash_count);
 
-       return hash;
+       /* If we didn't use all hash entries, free the unused memory. */
+       if (entries)
+               index = realloc(index, memsize - entries * sizeof(*entry));
+
+       return index;
 }
 
-/* provide the size of the copy opcode given the block offset and size */
-#define COPYOP_SIZE(o, s) \
-    (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
-     !!(s & 0xff) + !!(s & 0xff00) + 1)
+void free_delta_index(struct delta_index *index)
+{
+       free(index);
+}
 
-/* the maximum size for any opcode */
-#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+/*
+ * The maximum size for any opcode sequence, including the initial header
+ * plus rabin window plus biggest copy.
+ */
+#define MAX_OP_SIZE    (5 + 5 + 1 + RABIN_WINDOW + 7)
 
-void *diff_delta(void *from_buf, unsigned long from_size,
-                void *to_buf, unsigned long to_size,
-                unsigned long *delta_size,
-                unsigned long max_size)
+void *
+create_delta(const struct delta_index *index,
+            const void *trg_buf, unsigned long trg_size,
+            unsigned long *delta_size, unsigned long max_size)
 {
-       unsigned int i, outpos, outsize, hash_shift;
+       unsigned int i, outpos, outsize, val;
        int inscnt;
        const unsigned char *ref_data, *ref_top, *data, *top;
        unsigned char *out;
-       struct index *entry, **hash;
 
-       if (!from_size || !to_size)
-               return NULL;
-       hash = delta_index(from_buf, from_size, to_size, &hash_shift);
-       if (!hash)
+       if (!trg_buf || !trg_size)
                return NULL;
 
        outpos = 0;
@@ -153,64 +266,66 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (max_size && outsize >= max_size)
                outsize = max_size + MAX_OP_SIZE + 1;
        out = malloc(outsize);
-       if (!out) {
-               free(hash);
+       if (!out)
                return NULL;
-       }
-
-       ref_data = from_buf;
-       ref_top = from_buf + from_size;
-       data = to_buf;
-       top = to_buf + to_size;
 
        /* store reference buffer size */
-       out[outpos++] = from_size;
-       from_size >>= 7;
-       while (from_size) {
-               out[outpos - 1] |= 0x80;
-               out[outpos++] = from_size;
-               from_size >>= 7;
+       i = index->src_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
        }
+       out[outpos++] = i;
 
        /* store target buffer size */
-       out[outpos++] = to_size;
-       to_size >>= 7;
-       while (to_size) {
-               out[outpos - 1] |= 0x80;
-               out[outpos++] = to_size;
-               to_size >>= 7;
+       i = trg_size;
+       while (i >= 0x80) {
+               out[outpos++] = i | 0x80;
+               i >>= 7;
        }
-
-       inscnt = 0;
+       out[outpos++] = i;
+
+       ref_data = index->src_buf;
+       ref_top = ref_data + index->src_size;
+       data = trg_buf;
+       top = trg_buf + trg_size;
+
+       outpos++;
+       val = 0;
+       for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
+               out[outpos++] = *data;
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+       }
+       inscnt = i;
 
        while (data < top) {
                unsigned int moff = 0, msize = 0;
-               if (data + BLK_SIZE <= top) {
-                       unsigned int val = adler32(0, data, BLK_SIZE);
-                       i = HASH(val, hash_shift);
-                       for (entry = hash[i]; entry; entry = entry->next) {
-                               const unsigned char *ref = entry->ptr;
-                               const unsigned char *src = data;
-                               unsigned int ref_size = ref_top - ref;
-                               if (entry->val != val)
-                                       continue;
-                               if (ref_size > top - src)
-                                       ref_size = top - src;
-                               if (ref_size > 0x10000)
-                                       ref_size = 0x10000;
-                               if (ref_size <= msize)
-                                       break;
-                               while (ref_size-- && *src++ == *ref)
-                                       ref++;
-                               if (msize < ref - entry->ptr) {
-                                       /* this is our best match so far */
-                                       msize = ref - entry->ptr;
-                                       moff = entry->ptr - ref_data;
-                               }
+               struct index_entry *entry;
+               val ^= U[data[-RABIN_WINDOW]];
+               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+               i = val & index->hash_mask;
+               for (entry = index->hash[i]; entry; entry = entry->next) {
+                       const unsigned char *ref = entry->ptr;
+                       const unsigned char *src = data;
+                       unsigned int ref_size = ref_top - ref;
+                       if (entry->val != val)
+                               continue;
+                       if (ref_size > top - src)
+                               ref_size = top - src;
+                       if (ref_size > 0x10000)
+                               ref_size = 0x10000;
+                       if (ref_size <= msize)
+                               break;
+                       while (ref_size-- && *src++ == *ref)
+                               ref++;
+                       if (msize < ref - entry->ptr) {
+                               /* this is our best match so far */
+                               msize = ref - entry->ptr;
+                               moff = entry->ptr - ref_data;
                        }
                }
 
-               if (!msize || msize < COPYOP_SIZE(moff, msize)) {
+               if (msize < 4) {
                        if (!inscnt)
                                outpos++;
                        out[outpos++] = *data++;
@@ -222,6 +337,20 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                } else {
                        unsigned char *op;
 
+                       if (msize >= RABIN_WINDOW) {
+                               const unsigned char *sk;
+                               sk = data + msize - RABIN_WINDOW;
+                               val = 0;
+                               for (i = 0; i < RABIN_WINDOW; i++)
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                       } else {
+                               const unsigned char *sk = data + 1;
+                               for (i = 1; i < msize; i++) {
+                                       val ^= U[sk[-RABIN_WINDOW]];
+                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
+                               }
+                       }
+
                        if (inscnt) {
                                while (moff && ref_data[moff-1] == data[-1]) {
                                        if (msize == 0x10000)
@@ -266,12 +395,10 @@ void *diff_delta(void *from_buf, unsigned long from_size,
                        if (max_size && outsize >= max_size)
                                outsize = max_size + MAX_OP_SIZE + 1;
                        if (max_size && outpos > max_size)
-                               out = NULL;
-                       else
-                               out = realloc(out, outsize);
+                               break;
+                       out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
-                               free(hash);
                                return NULL;
                        }
                }
@@ -280,7 +407,11 @@ void *diff_delta(void *from_buf, unsigned long from_size,
        if (inscnt)
                out[outpos - inscnt - 1] = inscnt;
 
-       free(hash);
+       if (max_size && outpos > max_size) {
+               free(out);
+               return NULL;
+       }
+
        *delta_size = outpos;
        return out;
 }
diff --git a/diff.c b/diff.c
index 6762fce..7a7b839 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -8,6 +8,7 @@
 #include "quote.h"
 #include "diff.h"
 #include "diffcore.h"
+#include "delta.h"
 #include "xdiff-interface.h"
 
 static int use_size_cache;
@@ -296,7 +297,6 @@ static const char minuses[]= "--------------------------------------------------
 
 static void show_stats(struct diffstat_t* data)
 {
-       char *prefix = "";
        int i, len, add, del, total, adds = 0, dels = 0;
        int max, max_change = 0, max_len = 0;
        int total_files = data->nr;
@@ -318,6 +318,7 @@ static void show_stats(struct diffstat_t* data)
        }
 
        for (i = 0; i < data->nr; i++) {
+               char *prefix = "";
                char *name = data->files[i]->name;
                int added = data->files[i]->added;
                int deleted = data->files[i]->deleted;
@@ -391,6 +392,90 @@ static void show_stats(struct diffstat_t* data)
                        total_files, adds, dels);
 }
 
+static unsigned char *deflate_it(char *data,
+                                unsigned long size,
+                                unsigned long *result_size)
+{
+       int bound;
+       unsigned char *deflated;
+       z_stream stream;
+
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       bound = deflateBound(&stream, size);
+       deflated = xmalloc(bound);
+       stream.next_out = deflated;
+       stream.avail_out = bound;
+
+       stream.next_in = (unsigned char *)data;
+       stream.avail_in = size;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               ; /* nothing */
+       deflateEnd(&stream);
+       *result_size = stream.total_out;
+       return deflated;
+}
+
+static void emit_binary_diff(mmfile_t *one, mmfile_t *two)
+{
+       void *cp;
+       void *delta;
+       void *deflated;
+       void *data;
+       unsigned long orig_size;
+       unsigned long delta_size;
+       unsigned long deflate_size;
+       unsigned long data_size;
+
+       printf("GIT binary patch\n");
+       /* We could do deflated delta, or we could do just deflated two,
+        * whichever is smaller.
+        */
+       delta = NULL;
+       deflated = deflate_it(two->ptr, two->size, &deflate_size);
+       if (one->size && two->size) {
+               delta = diff_delta(one->ptr, one->size,
+                                  two->ptr, two->size,
+                                  &delta_size, deflate_size);
+               if (delta) {
+                       void *to_free = delta;
+                       orig_size = delta_size;
+                       delta = deflate_it(delta, delta_size, &delta_size);
+                       free(to_free);
+               }
+       }
+
+       if (delta && delta_size < deflate_size) {
+               printf("delta %lu\n", orig_size);
+               free(deflated);
+               data = delta;
+               data_size = delta_size;
+       }
+       else {
+               printf("literal %lu\n", two->size);
+               free(delta);
+               data = deflated;
+               data_size = deflate_size;
+       }
+
+       /* emit data encoded in base85 */
+       cp = data;
+       while (data_size) {
+               int bytes = (52 < data_size) ? 52 : data_size;
+               char line[70];
+               data_size -= bytes;
+               if (bytes <= 26)
+                       line[0] = bytes + 'A' - 1;
+               else
+                       line[0] = bytes - 26 + 'a' - 1;
+               encode_85(line + 1, cp, bytes);
+               cp += bytes;
+               puts(line);
+       }
+       printf("\n");
+       free(data);
+}
+
 #define FIRST_FEW_BYTES 8000
 static int mmfile_is_binary(mmfile_t *mf)
 {
@@ -407,6 +492,7 @@ static void builtin_diff(const char *name_a,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
+                        struct diff_options *o,
                         int complete_rewrite)
 {
        mmfile_t mf1, mf2;
@@ -451,8 +537,17 @@ static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
-               printf("Binary files %s and %s differ\n", lbl[0], lbl[1]);
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2)) {
+               /* Quite common confusing case */
+               if (mf1.size == mf2.size &&
+                   !memcmp(mf1.ptr, mf2.ptr, mf1.size))
+                       goto free_ab_and_return;
+               if (o->binary)
+                       emit_binary_diff(&mf1, &mf2);
+               else
+                       printf("Binary files %s and %s differ\n",
+                              lbl[0], lbl[1]);
+       }
        else {
                /* Crazy xdl interfaces.. */
                const char *diffopts = getenv("GIT_DIFF_OPTS");
@@ -928,6 +1023,7 @@ static void run_diff_cmd(const char *pgm,
                         struct diff_filespec *one,
                         struct diff_filespec *two,
                         const char *xfrm_msg,
+                        struct diff_options *o,
                         int complete_rewrite)
 {
        if (pgm) {
@@ -937,7 +1033,7 @@ static void run_diff_cmd(const char *pgm,
        }
        if (one && two)
                builtin_diff(name, other ? other : name,
-                            one, two, xfrm_msg, complete_rewrite);
+                            one, two, xfrm_msg, o, complete_rewrite);
        else
                printf("* Unmerged path %s\n", name);
 }
@@ -971,7 +1067,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, 0);
+               run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
                return;
        }
 
@@ -1018,14 +1114,12 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        }
 
        if (memcmp(one->sha1, two->sha1, 20)) {
-               char one_sha1[41];
                int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
-               memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
 
                len += snprintf(msg + len, sizeof(msg) - len,
                                "index %.*s..%.*s",
-                               abbrev, one_sha1, abbrev,
-                               sha1_to_hex(two->sha1));
+                               abbrev, sha1_to_hex(one->sha1),
+                               abbrev, sha1_to_hex(two->sha1));
                if (one->mode == two->mode)
                        len += snprintf(msg + len, sizeof(msg) - len,
                                        " %06o", one->mode);
@@ -1043,14 +1137,14 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                 * needs to be split into deletion and creation.
                 */
                struct diff_filespec *null = alloc_filespec(two->path);
-               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, 0);
+               run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
                free(null);
                null = alloc_filespec(one->path);
-               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, 0);
+               run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
                free(null);
        }
        else
-               run_diff_cmd(pgm, name, other, one, two, xfrm_msg,
+               run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
                             complete_rewrite);
 
        free(name_munged);
@@ -1149,6 +1243,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->rename_limit = strtoul(arg+2, NULL, 10);
        else if (!strcmp(arg, "--full-index"))
                options->full_index = 1;
+       else if (!strcmp(arg, "--binary")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->full_index = options->binary = 1;
+       }
        else if (!strcmp(arg, "--name-only"))
                options->output_format = DIFF_FORMAT_NAME;
        else if (!strcmp(arg, "--name-status"))
diff --git a/diff.h b/diff.h
index 7150b90..d052608 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -28,6 +28,7 @@ struct diff_options {
                 with_raw:1,
                 with_stat:1,
                 tree_in_recursive:1,
+                binary:1,
                 full_index:1,
                 silent_on_remove:1,
                 find_copies_harder:1;
@@ -75,6 +76,8 @@ struct combine_diff_path {
 extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
                              int dense, struct rev_info *);
 
+extern void diff_tree_combined(const unsigned char *sha1, const unsigned char parent[][20], int num_parent, int dense, struct rev_info *rev);
+
 extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
 
 extern void diff_addremove(struct diff_options *,
index 6df6478..444c99e 100644 (file)
@@ -13,7 +13,7 @@ char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
 int trust_executable_bit = 1;
 int assume_unchanged = 0;
-int only_use_symrefs = 0;
+int prefer_symlink_refs = 0;
 int warn_ambiguous_refs = 1;
 int repository_format_version = 0;
 char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
index 872145b..507ae4d 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -14,6 +14,26 @@ stop_here () {
     exit 1
 }
 
+stop_here_user_resolve () {
+    cmdline=$(basename $0)
+    if test '' != "$interactive"
+    then
+        cmdline="$cmdline -i"
+    fi
+    if test '' != "$threeway"
+    then
+        cmdline="$cmdline -3"
+    fi
+    if test '.dotest' != "$dotest"
+    then
+        cmdline="$cmdline -d=$dotest"
+    fi
+    echo "When you have resolved this problem run \"$cmdline --resolved\"."
+    echo "If you would prefer to skip this patch, instead run \"$cmdline --skip\"."
+
+    stop_here $1
+}
+
 go_next () {
        rm -f "$dotest/$msgnum" "$dotest/msg" "$dotest/msg-clean" \
                "$dotest/patch" "$dotest/info"
@@ -374,14 +394,14 @@ do
                if test '' = "$changed"
                then
                        echo "No changes - did you forget update-index?"
-                       stop_here $this
+                       stop_here_user_resolve $this
                fi
                unmerged=$(git-ls-files -u)
                if test -n "$unmerged"
                then
                        echo "You still have unmerged paths in your index"
                        echo "did you forget update-index?"
-                       stop_here $this
+                       stop_here_user_resolve $this
                fi
                apply_status=0
                ;;
@@ -407,7 +427,7 @@ do
        if test $apply_status != 0
        then
                echo Patch failed at $msgnum.
-               stop_here $this
+               stop_here_user_resolve $this
        fi
 
        if test -x "$GIT_DIR"/hooks/pre-applypatch
index 0805168..b785247 100755 (executable)
@@ -265,7 +265,7 @@ yes,yes)
                test -f "$repo/objects/info/alternates" &&
                cat "$repo/objects/info/alternates";
                echo "$repo/objects"
-           } >"$GIT_DIR/objects/info/alternates"
+           } >>"$GIT_DIR/objects/info/alternates"
            ;;
        esac
        git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD"
diff --git a/git-count-objects.sh b/git-count-objects.sh
deleted file mode 100755 (executable)
index 40c58ef..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Junio C Hamano
-#
-
-GIT_DIR=`git-rev-parse --git-dir` || exit $?
-
-dc </dev/null 2>/dev/null || {
-       # This is not a real DC at all -- it just knows how
-       # this script feeds DC and does the computation itself.
-       dc () {
-               while read a b
-               do
-                       case $a,$b in
-                       0,)     acc=0 ;;
-                       *,+)    acc=$(($acc + $a)) ;;
-                       p,)     echo "$acc" ;;
-                       esac
-               done
-       }
-}
-
-echo $(find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null | wc -l) objects, \
-$({
-    echo 0
-    # "no-such" is to help Darwin folks by not using xargs -r.
-    find "$GIT_DIR/objects"/?? -type f -print 2>/dev/null |
-    xargs du -k "$GIT_DIR/objects/no-such" 2>/dev/null |
-    sed -e 's/[        ].*/ +/'
-    echo p
-} | dc) kilobytes
index 11d153c..ffd9c66 100755 (executable)
@@ -2076,14 +2076,15 @@ sub update
     # TODO: log processing is memory bound
     # if we can parse into a 2nd file that is in reverse order
     # we can probably do something really efficient
-    my @git_log_params = ('--parents', '--topo-order');
+    my @git_log_params = ('--pretty', '--parents', '--topo-order');
 
     if (defined $lastcommit) {
         push @git_log_params, "$lastcommit..$self->{module}";
     } else {
         push @git_log_params, $self->{module};
     }
-    open(GITLOG, '-|', 'git-log', @git_log_params) or die "Cannot call git-log: $!";
+    # git-rev-list is the backend / plumbing version of git-log
+    open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
 
     my @commits;
 
diff --git a/git-diff.sh b/git-diff.sh
deleted file mode 100755 (executable)
index 0fe6770..0000000
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Linus Torvalds
-# Copyright (c) 2005 Junio C Hamano
-
-USAGE='[ --diff-options ] <ent>{0,2} [<path>...]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-rev=$(git-rev-parse --revs-only --no-flags --sq "$@") || exit
-flags=$(git-rev-parse --no-revs --flags --sq "$@")
-files=$(git-rev-parse --no-revs --no-flags --sq "$@")
-
-# I often say 'git diff --cached -p' and get scolded by git-diff-files, but
-# obviously I mean 'git diff --cached -p HEAD' in that case.
-case "$rev" in
-'')
-       case " $flags " in
-       *" '--cached' "*)
-               rev='HEAD '
-               ;;
-       esac
-esac
-
-# If we have -[123] --ours --theirs --base, don't do --cc by default.
-case " $flags " in
-*" '-"[123]"' "* | *" '--ours' "* | *" '--base' "* | *" '--theirs' "*)
-       cc_or_p=-p ;;
-*)
-       cc_or_p=--cc ;;
-esac
-
-# If we do not have --name-status, --name-only, -r, -c or --stat,
-# default to --cc.
-case " $flags " in
-*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* | \
-*" '--stat' "*)
-       ;;
-*)
-       flags="$flags'$cc_or_p' " ;;
-esac
-
-# If we do not have -B, -C, -r, nor -p, default to -M.
-case " $flags " in
-*" '-"[BCMrp]* | *" '--find-copies-harder' "*)
-       ;; # something like -M50.
-*)
-       flags="$flags'-M' " ;;
-esac
-
-case "$rev" in
-?*' '?*' '?*)
-       usage
-       ;;
-?*' '^?*)
-       begin=$(expr "$rev" : '.*^.\([0-9a-f]*\).*') &&
-       end=$(expr "$rev" : '.\([0-9a-f]*\). .*') || exit
-       cmd="git-diff-tree $flags $begin $end -- $files"
-       ;;
-?*' '?*)
-       cmd="git-diff-tree $flags $rev -- $files"
-       ;;
-?*' ')
-       cmd="git-diff-index $flags $rev -- $files"
-       ;;
-'')
-       cmd="git-diff-files $flags -- $files"
-       ;;
-*)
-       usage
-       ;;
-esac
-
-eval "$cmd"
index 83143f8..280f62e 100755 (executable)
@@ -270,14 +270,22 @@ fetch_main () {
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
-         remote_name_quoted=$(perl -e '
+         max_depth=5
+         depth=0
+         head="ref: $remote_name"
+         while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
+         do
+           remote_name_quoted=$(perl -e '
              my $u = $ARGV[0];
+              $u =~ s/^ref:\s*//;
              $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
              print "$u";
-         ' "$remote_name")
-         head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
+         ' "$head")
+           head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+           depth=$( expr \( $depth + 1 \) )
+         done
          expr "z$head" : "z$_x40\$" >/dev/null ||
-                 die "Failed to fetch $remote_name from $remote"
+             die "Failed to fetch $remote_name from $remote"
          echo >&2 Fetching "$remote_name from $remote" using http
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
index f7b2b94..9e25902 100755 (executable)
@@ -4,37 +4,51 @@
 #
 
 USAGE='[--onto <newbase>] <upstream> [<branch>]'
-LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
-
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
-
-Assuming the following history:
-
-          A---B---C topic
-         /
-    D---E---F---G master
-
-The result of the following command:
-
-    git-rebase --onto master~1 master topic
-
-  would be:
-
-              A'\''--B'\''--C'\'' topic
-             /
-    D---E---F---G master
+LONG_USAGE='git-rebase replaces <branch> with a new branch of the
+same name.  When the --onto option is provided the new branch starts
+out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
+It then attempts to create a new commit for each commit from the original
+<branch> that does not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run git-rebase --continue.  If you can not resolve the merge failure,
+running git-rebase --abort will restore the original <branch> and remove
+the working files found in the .dotest directory.
+
+Note that if <branch> is not specified on the command line, the
+currently checked out branch is used.  You must be in the top
+directory of your project to start (or continue) a rebase.
+
+Example:       git-rebase master~1 topic
+
+        A---B---C topic                   A'\''--B'\''--C'\'' topic
+       /                   -->           /
+  D---E---F---G master          D---E---F---G master
 '
-
 . git-sh-setup
 
 unset newbase
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
+       --continue)
+               diff=$(git-diff-files)
+               case "$diff" in
+               ?*)     echo "You must edit all merge conflicts and then"
+                       echo "mark them as resolved using git update-index"
+                       exit 1
+                       ;;
+               esac
+               git am --resolved --3way
+               exit
+               ;;
+       --abort)
+               [ -d .dotest ] || die "No rebase in progress?"
+               git reset --hard ORIG_HEAD
+               rm -r .dotest
+               exit
+               ;;
        --onto)
                test 2 -le "$#" || usage
                newbase="$2"
index e0c9f32..4fb3f26 100755 (executable)
@@ -48,15 +48,15 @@ name=$(git-rev-list --objects --all $rev_list 2>&1 |
        exit 1
 if [ -z "$name" ]; then
        echo Nothing new to pack.
-       exit 0
-fi
-echo "Pack pack-$name created."
+else
+       echo "Pack pack-$name created."
 
-mkdir -p "$PACKDIR" || exit
+       mkdir -p "$PACKDIR" || exit
 
-mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
-mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
-exit
+       mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+       mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
+       exit
+fi
 
 if test "$remove_redundant" = t
 then
index ecfa347..703dd1f 100755 (executable)
@@ -291,6 +291,13 @@ sub send_message
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc);
        my $date = strftime('%a, %d %b %Y %H:%M:%S %z', localtime($time++));
+       my $gitversion = '@@GIT_VERSION@@';
+       if ($gitversion =~ m/..GIT_VERSION../) {
+           $gitversion = `git --version`;
+           chomp $gitversion;
+           # keep only what's after the last space
+           $gitversion =~ s/^.* //;
+       }
 
        my $header = "From: $from
 To: $to
@@ -299,7 +306,7 @@ Subject: $subject
 Reply-To: $from
 Date: $date
 Message-Id: $message_id
-X-Mailer: git-send-email @@GIT_VERSION@@
+X-Mailer: git-send-email $gitversion
 ";
        $header .= "In-Reply-To: $reply_to\n" if $reply_to;
 
diff --git a/git-whatchanged.sh b/git-whatchanged.sh
deleted file mode 100755 (executable)
index 1fb9feb..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/sh
-
-USAGE='[-p] [--max-count=<n>] [<since>..<limit>] [--pretty=<format>] [-m] [git-diff-tree options] [git-rev-list options]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-diff_tree_flags=$(git-rev-parse --sq --no-revs --flags "$@") || exit
-case "$0" in
-*whatchanged)
-       count=
-       test -z "$diff_tree_flags" &&
-               diff_tree_flags=$(git-repo-config --get whatchanged.difftree)
-       diff_tree_default_flags='-c -M --abbrev' ;;
-*show)
-       count=-n1
-       test -z "$diff_tree_flags" &&
-               diff_tree_flags=$(git-repo-config --get show.difftree)
-       diff_tree_default_flags='--cc --always' ;;
-esac
-test -z "$diff_tree_flags" &&
-       diff_tree_flags="$diff_tree_default_flags"
-
-rev_list_args=$(git-rev-parse --sq --default HEAD --revs-only "$@") &&
-diff_tree_args=$(git-rev-parse --sq --no-revs --no-flags "$@") &&
-
-eval "git-rev-list $count $rev_list_args" |
-eval "git-diff-tree --stdin --pretty -r $diff_tree_flags $diff_tree_args" |
-LESS="$LESS -S" ${PAGER:-less}
diff --git a/git.c b/git.c
index 01b7e28..49ba518 100644 (file)
--- a/git.c
+++ b/git.c
@@ -46,6 +46,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "log", cmd_log },
                { "whatchanged", cmd_whatchanged },
                { "show", cmd_show },
+               { "push", cmd_push },
+               { "count-objects", cmd_count_objects },
+               { "diff", cmd_diff },
        };
        int i;
 
diff --git a/gitk b/gitk
index 5362b76..4aa57c0 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -16,83 +16,112 @@ proc gitdir {} {
     }
 }
 
-proc start_rev_list {rlargs} {
+proc start_rev_list {view} {
     global startmsecs nextupdate ncmupdate
     global commfd leftover tclencoding datemode
+    global viewargs viewfiles commitidx
 
     set startmsecs [clock clicks -milliseconds]
     set nextupdate [expr {$startmsecs + 100}]
     set ncmupdate 1
-    initlayout
+    set commitidx($view) 0
+    set args $viewargs($view)
+    if {$viewfiles($view) ne {}} {
+       set args [concat $args "--" $viewfiles($view)]
+    }
     set order "--topo-order"
     if {$datemode} {
        set order "--date-order"
     }
     if {[catch {
-       set commfd [open [concat | git-rev-list --header $order \
-                             --parents --boundary --default HEAD $rlargs] r]
+       set fd [open [concat | git-rev-list --header $order \
+                         --parents --boundary --default HEAD $args] r]
     } err]} {
        puts stderr "Error executing git-rev-list: $err"
        exit 1
     }
-    set leftover {}
-    fconfigure $commfd -blocking 0 -translation lf
+    set commfd($view) $fd
+    set leftover($view) {}
+    fconfigure $fd -blocking 0 -translation lf
     if {$tclencoding != {}} {
-       fconfigure $commfd -encoding $tclencoding
+       fconfigure $fd -encoding $tclencoding
+    }
+    fileevent $fd readable [list getcommitlines $fd $view]
+    nowbusy $view
+}
+
+proc stop_rev_list {} {
+    global commfd curview
+
+    if {![info exists commfd($curview)]} return
+    set fd $commfd($curview)
+    catch {
+       set pid [pid $fd]
+       exec kill $pid
     }
-    fileevent $commfd readable [list getcommitlines $commfd]
-    . config -cursor watch
-    settextcursor watch
+    catch {close $fd}
+    unset commfd($curview)
 }
 
-proc getcommits {rargs} {
-    global phase canv mainfont
+proc getcommits {} {
+    global phase canv mainfont curview
 
     set phase getcommits
-    start_rev_list $rargs
-    $canv delete all
-    $canv create text 3 3 -anchor nw -text "Reading commits..." \
-       -font $mainfont -tags textitems
+    initlayout
+    start_rev_list $curview
+    show_status "Reading commits..."
 }
 
-proc getcommitlines {commfd}  {
+proc getcommitlines {fd view}  {
     global commitlisted nextupdate
-    global leftover
+    global leftover commfd
     global displayorder commitidx commitrow commitdata
-    global parentlist childlist children
+    global parentlist childlist children curview hlview
+    global vparentlist vchildlist vdisporder vcmitlisted
 
-    set stuff [read $commfd]
+    set stuff [read $fd]
     if {$stuff == {}} {
-       if {![eof $commfd]} return
+       if {![eof $fd]} return
+       global viewname
+       unset commfd($view)
+       notbusy $view
        # set it blocking so we wait for the process to terminate
-       fconfigure $commfd -blocking 1
-       if {![catch {close $commfd} err]} {
-           after idle finishcommits
-           return
+       fconfigure $fd -blocking 1
+       if {[catch {close $fd} err]} {
+           set fv {}
+           if {$view != $curview} {
+               set fv " for the \"$viewname($view)\" view"
+           }
+           if {[string range $err 0 4] == "usage"} {
+               set err "Gitk: error reading commits$fv:\
+                       bad arguments to git-rev-list."
+               if {$viewname($view) eq "Command line"} {
+                   append err \
+                       "  (Note: arguments to gitk are passed to git-rev-list\
+                        to allow selection of commits to be displayed.)"
+               }
+           } else {
+               set err "Error reading commits$fv: $err"
+           }
+           error_popup $err
        }
-       if {[string range $err 0 4] == "usage"} {
-           set err \
-               "Gitk: error reading commits: bad arguments to git-rev-list.\
-               (Note: arguments to gitk are passed to git-rev-list\
-               to allow selection of commits to be displayed.)"
-       } else {
-           set err "Error reading commits: $err"
+       if {$view == $curview} {
+           after idle finishcommits
        }
-       error_popup $err
-       exit 1
+       return
     }
     set start 0
     set gotsome 0
     while 1 {
        set i [string first "\0" $stuff $start]
        if {$i < 0} {
-           append leftover [string range $stuff $start end]
+           append leftover($view) [string range $stuff $start end]
            break
        }
        if {$start == 0} {
-           set cmit $leftover
+           set cmit $leftover($view)
            append cmit [string range $stuff 0 [expr {$i - 1}]]
-           set leftover {}
+           set leftover($view) {}
        } else {
            set cmit [string range $stuff $start [expr {$i - 1}]]
        }
@@ -125,41 +154,52 @@ proc getcommitlines {commfd}  {
        set id [lindex $ids 0]
        if {$listed} {
            set olds [lrange $ids 1 end]
-           if {[llength $olds] > 1} {
-               set olds [lsort -unique $olds]
-           }
+           set i 0
            foreach p $olds {
-               lappend children($p) $id
+               if {$i == 0 || [lsearch -exact $olds $p] >= $i} {
+                   lappend children($view,$p) $id
+               }
+               incr i
            }
        } else {
            set olds {}
        }
-       lappend parentlist $olds
-       if {[info exists children($id)]} {
-           lappend childlist $children($id)
-       } else {
-           lappend childlist {}
+       if {![info exists children($view,$id)]} {
+           set children($view,$id) {}
        }
        set commitdata($id) [string range $cmit [expr {$j + 1}] end]
-       set commitrow($id) $commitidx
-       incr commitidx
-       lappend displayorder $id
-       lappend commitlisted $listed
+       set commitrow($view,$id) $commitidx($view)
+       incr commitidx($view)
+       if {$view == $curview} {
+           lappend parentlist $olds
+           lappend childlist $children($view,$id)
+           lappend displayorder $id
+           lappend commitlisted $listed
+       } else {
+           lappend vparentlist($view) $olds
+           lappend vchildlist($view) $children($view,$id)
+           lappend vdisporder($view) $id
+           lappend vcmitlisted($view) $listed
+       }
        set gotsome 1
     }
     if {$gotsome} {
-       layoutmore
+       if {$view == $curview} {
+           layoutmore
+       } elseif {[info exists hlview] && $view == $hlview} {
+           highlightmore
+       }
     }
     if {[clock clicks -milliseconds] >= $nextupdate} {
-       doupdate 1
+       doupdate
     }
 }
 
-proc doupdate {reading} {
+proc doupdate {} {
     global commfd nextupdate numcommits ncmupdate
 
-    if {$reading} {
-       fileevent $commfd readable {}
+    foreach v [array names commfd] {
+       fileevent $commfd($v) readable {}
     }
     update
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
@@ -170,8 +210,9 @@ proc doupdate {reading} {
     } else {
        set ncmupdate [expr {$numcommits + 100}]
     }
-    if {$reading} {
-       fileevent $commfd readable [list getcommitlines $commfd]
+    foreach v [array names commfd] {
+       set fd $commfd($v)
+       fileevent $fd readable [list getcommitlines $fd $v]
     }
 }
 
@@ -180,18 +221,23 @@ proc readcommit {id} {
     parsecommit $id $contents 0
 }
 
-proc updatecommits {rargs} {
-    stopfindproc
-    foreach v {colormap selectedline matchinglines treediffs
-       mergefilelist currentid rowtextx commitrow
-       rowidlist rowoffsets idrowranges idrangedrawn iddrawn
-       linesegends crossings cornercrossings} {
-       global $v
-       catch {unset $v}
+proc updatecommits {} {
+    global viewdata curview phase displayorder
+    global children commitrow
+
+    if {$phase ne {}} {
+       stop_rev_list
+       set phase {}
     }
-    allcanvs delete all
+    set n $curview
+    foreach id $displayorder {
+       catch {unset children($n,$id)}
+       catch {unset commitrow($n,$id)}
+    }
+    set curview -1
+    catch {unset viewdata($n)}
     readrefs
-    getcommits $rargs
+    showview $n
 }
 
 proc parsecommit {id contents listed} {
@@ -274,10 +320,16 @@ proc readrefs {} {
            match id path]} {
            continue
        }
+       if {[regexp {^remotes/.*/HEAD$} $path match]} {
+           continue
+       }
        if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
            set type others
            set name $path
        }
+       if {[regexp {^remotes/} $path match]} {
+           set type heads
+       }
        if {$type == "tags"} {
            set tagids($name) $id
            lappend idtags($id) $name
@@ -305,10 +357,7 @@ proc readrefs {} {
     close $refd
 }
 
-proc error_popup msg {
-    set w .error
-    toplevel $w
-    wm transient $w .
+proc show_error {w msg} {
     message $w.m -text $msg -justify center -aspect 400
     pack $w.m -side top -fill x -padx 20 -pady 20
     button $w.ok -text OK -command "destroy $w"
@@ -318,8 +367,16 @@ proc error_popup msg {
     tkwait window $w
 }
 
-proc makewindow {rargs} {
-    global canv canv2 canv3 linespc charspc ctext cflist textfont mainfont uifont
+proc error_popup msg {
+    set w .error
+    toplevel $w
+    wm transient $w .
+    show_error $w $msg
+}
+
+proc makewindow {} {
+    global canv canv2 canv3 linespc charspc ctext cflist
+    global textfont mainfont uifont
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
@@ -329,7 +386,7 @@ proc makewindow {rargs} {
     .bar add cascade -label "File" -menu .bar.file
     .bar configure -font $uifont
     menu .bar.file
-    .bar.file add command -label "Update" -command [list updatecommits $rargs]
+    .bar.file add command -label "Update" -command updatecommits
     .bar.file add command -label "Reread references" -command rereadrefs
     .bar.file add command -label "Quit" -command doquit
     .bar.file configure -font $uifont
@@ -337,6 +394,23 @@ proc makewindow {rargs} {
     .bar add cascade -label "Edit" -menu .bar.edit
     .bar.edit add command -label "Preferences" -command doprefs
     .bar.edit configure -font $uifont
+
+    menu .bar.view -font $uifont
+    menu .bar.view.hl -font $uifont -tearoff 0
+    .bar add cascade -label "View" -menu .bar.view
+    .bar.view add command -label "New view..." -command {newview 0}
+    .bar.view add command -label "Edit view..." -command editview \
+       -state disabled
+    .bar.view add command -label "Delete view" -command delview -state disabled
+    .bar.view add cascade -label "Highlight" -menu .bar.view.hl
+    .bar.view add separator
+    .bar.view add radiobutton -label "All files" -command {showview 0} \
+       -variable selectedview -value 0
+    .bar.view.hl add command -label "New view..." -command {newview 1}
+    .bar.view.hl add command -label "Remove" -command delhighlight \
+       -state disabled
+    .bar.view.hl add separator
+    
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
@@ -447,7 +521,7 @@ proc makewindow {rargs} {
     set ctext .ctop.cdet.left.ctext
     text $ctext -bg white -state disabled -font $textfont \
        -width $geometry(ctextw) -height $geometry(ctexth) \
-       -yscrollcommand ".ctop.cdet.left.sb set" -wrap none
+       -yscrollcommand {.ctop.cdet.left.sb set} -wrap none
     scrollbar .ctop.cdet.left.sb -command "$ctext yview"
     pack .ctop.cdet.left.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
@@ -480,12 +554,25 @@ proc makewindow {rargs} {
     $ctext tag conf found -back yellow
 
     frame .ctop.cdet.right
+    frame .ctop.cdet.right.mode
+    radiobutton .ctop.cdet.right.mode.patch -text "Patch" \
+       -command reselectline -variable cmitmode -value "patch"
+    radiobutton .ctop.cdet.right.mode.tree -text "Tree" \
+       -command reselectline -variable cmitmode -value "tree"
+    grid .ctop.cdet.right.mode.patch .ctop.cdet.right.mode.tree -sticky ew
+    pack .ctop.cdet.right.mode -side top -fill x
     set cflist .ctop.cdet.right.cfiles
-    listbox $cflist -bg white -selectmode extended -width $geometry(cflistw) \
-       -yscrollcommand ".ctop.cdet.right.sb set" -font $mainfont
+    set indent [font measure $mainfont "nn"]
+    text $cflist -width $geometry(cflistw) -background white -font $mainfont \
+       -tabs [list $indent [expr {2 * $indent}]] \
+       -yscrollcommand ".ctop.cdet.right.sb set" \
+       -cursor [. cget -cursor] \
+       -spacing1 1 -spacing3 1
     scrollbar .ctop.cdet.right.sb -command "$cflist yview"
     pack .ctop.cdet.right.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
     .ctop.cdet add .ctop.cdet.right
     bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
@@ -537,12 +624,14 @@ proc makewindow {rargs} {
     bind . <Control-KP_Add> {incrfont 1}
     bind . <Control-minus> {incrfont -1}
     bind . <Control-KP_Subtract> {incrfont -1}
-    bind $cflist <<ListboxSelect>> listboxsel
     bind . <Destroy> {savestuff %W}
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
     bind $sha1entry <<PasteSelection>> clearsha1
+    bind $cflist <1> {sel_flist %W %x %y; break}
+    bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
+    bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -606,6 +695,8 @@ proc savestuff {w} {
     global canv canv2 canv3 ctext cflist mainfont textfont uifont
     global stuffsaved findmergefiles maxgraphpct
     global maxwidth
+    global viewname viewfiles viewargs viewperm nextviewnum
+    global cmitmode
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -617,6 +708,7 @@ proc savestuff {w} {
        puts $f [list set findmergefiles $findmergefiles]
        puts $f [list set maxgraphpct $maxgraphpct]
        puts $f [list set maxwidth $maxwidth]
+       puts $f [list set cmitmode $cmitmode]
        puts $f "set geometry(width) [winfo width .ctop]"
        puts $f "set geometry(height) [winfo height .ctop]"
        puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
@@ -629,6 +721,13 @@ proc savestuff {w} {
        set wid [expr {([winfo width $cflist] - 11) \
                           / [font measure [$cflist cget -font] "0"]}]
        puts $f "set geometry(cflistw) $wid"
+       puts -nonewline $f "set permviews {"
+       for {set v 0} {$v < $nextviewnum} {incr v} {
+           if {$viewperm($v)} {
+               puts $f "{[list $viewname($v) $viewfiles($v) $viewargs($v)]}"
+           }
+       }
+       puts $f "}"
        close $f
        file rename -force "~/.gitk-new" "~/.gitk"
     }
@@ -770,6 +869,754 @@ f         Scroll diff view to next file
     pack $w.ok -side bottom
 }
 
+# Procedures for manipulating the file list window at the
+# bottom right of the overall window.
+
+proc treeview {w l openlevs} {
+    global treecontents treediropen treeheight treeparent treeindex
+
+    set ix 0
+    set treeindex() 0
+    set lev 0
+    set prefix {}
+    set prefixend -1
+    set prefendstack {}
+    set htstack {}
+    set ht 0
+    set treecontents() {}
+    $w conf -state normal
+    foreach f $l {
+       while {[string range $f 0 $prefixend] ne $prefix} {
+           if {$lev <= $openlevs} {
+               $w mark set e:$treeindex($prefix) "end -1c"
+               $w mark gravity e:$treeindex($prefix) left
+           }
+           set treeheight($prefix) $ht
+           incr ht [lindex $htstack end]
+           set htstack [lreplace $htstack end end]
+           set prefixend [lindex $prefendstack end]
+           set prefendstack [lreplace $prefendstack end end]
+           set prefix [string range $prefix 0 $prefixend]
+           incr lev -1
+       }
+       set tail [string range $f [expr {$prefixend+1}] end]
+       while {[set slash [string first "/" $tail]] >= 0} {
+           lappend htstack $ht
+           set ht 0
+           lappend prefendstack $prefixend
+           incr prefixend [expr {$slash + 1}]
+           set d [string range $tail 0 $slash]
+           lappend treecontents($prefix) $d
+           set oldprefix $prefix
+           append prefix $d
+           set treecontents($prefix) {}
+           set treeindex($prefix) [incr ix]
+           set treeparent($prefix) $oldprefix
+           set tail [string range $tail [expr {$slash+1}] end]
+           if {$lev <= $openlevs} {
+               set ht 1
+               set treediropen($prefix) [expr {$lev < $openlevs}]
+               set bm [expr {$lev == $openlevs? "tri-rt": "tri-dn"}]
+               $w mark set d:$ix "end -1c"
+               $w mark gravity d:$ix left
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w image create end -align center -image $bm -padx 1 \
+                   -name a:$ix
+               $w insert end $d
+               $w mark set s:$ix "end -1c"
+               $w mark gravity s:$ix left
+           }
+           incr lev
+       }
+       if {$tail ne {}} {
+           if {$lev <= $openlevs} {
+               incr ht
+               set str "\n"
+               for {set i 0} {$i < $lev} {incr i} {append str "\t"}
+               $w insert end $str
+               $w insert end $tail
+           }
+           lappend treecontents($prefix) $tail
+       }
+    }
+    while {$htstack ne {}} {
+       set treeheight($prefix) $ht
+       incr ht [lindex $htstack end]
+       set htstack [lreplace $htstack end end]
+    }
+    $w conf -state disabled
+}
+
+proc linetoelt {l} {
+    global treeheight treecontents
+
+    set y 2
+    set prefix {}
+    while {1} {
+       foreach e $treecontents($prefix) {
+           if {$y == $l} {
+               return "$prefix$e"
+           }
+           set n 1
+           if {[string index $e end] eq "/"} {
+               set n $treeheight($prefix$e)
+               if {$y + $n > $l} {
+                   append prefix $e
+                   incr y
+                   break
+               }
+           }
+           incr y $n
+       }
+    }
+}
+
+proc treeclosedir {w dir} {
+    global treediropen treeheight treeparent treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w delete s:$ix e:$ix
+    set treediropen($dir) 0
+    $w image configure a:$ix -image tri-rt
+    $w conf -state disabled
+    set n [expr {1 - $treeheight($dir)}]
+    while {$dir ne {}} {
+       incr treeheight($dir) $n
+       set dir $treeparent($dir)
+    }
+}
+
+proc treeopendir {w dir} {
+    global treediropen treeheight treeparent treecontents treeindex
+
+    set ix $treeindex($dir)
+    $w conf -state normal
+    $w image configure a:$ix -image tri-dn
+    $w mark set e:$ix s:$ix
+    $w mark gravity e:$ix right
+    set lev 0
+    set str "\n"
+    set n [llength $treecontents($dir)]
+    for {set x $dir} {$x ne {}} {set x $treeparent($x)} {
+       incr lev
+       append str "\t"
+       incr treeheight($x) $n
+    }
+    foreach e $treecontents($dir) {
+       if {[string index $e end] eq "/"} {
+           set de $dir$e
+           set iy $treeindex($de)
+           $w mark set d:$iy e:$ix
+           $w mark gravity d:$iy left
+           $w insert e:$ix $str
+           set treediropen($de) 0
+           $w image create e:$ix -align center -image tri-rt -padx 1 \
+               -name a:$iy
+           $w insert e:$ix $e
+           $w mark set s:$iy e:$ix
+           $w mark gravity s:$iy left
+           set treeheight($de) 1
+       } else {
+           $w insert e:$ix $str
+           $w insert e:$ix $e
+       }
+    }
+    $w mark gravity e:$ix left
+    $w conf -state disabled
+    set treediropen($dir) 1
+    set top [lindex [split [$w index @0,0] .] 0]
+    set ht [$w cget -height]
+    set l [lindex [split [$w index s:$ix] .] 0]
+    if {$l < $top} {
+       $w yview $l.0
+    } elseif {$l + $n + 1 > $top + $ht} {
+       set top [expr {$l + $n + 2 - $ht}]
+       if {$l < $top} {
+           set top $l
+       }
+       $w yview $top.0
+    }
+}
+
+proc treeclick {w x y} {
+    global treediropen cmitmode ctext cflist cflist_top
+
+    if {$cmitmode ne "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+       return
+    }
+    set e [linetoelt $l]
+    if {[string index $e end] ne "/"} {
+       showfile $e
+    } elseif {$treediropen($e)} {
+       treeclosedir $w $e
+    } else {
+       treeopendir $w $e
+    }
+}
+
+proc setfilelist {id} {
+    global treefilelist cflist
+
+    treeview $cflist $treefilelist($id) 0
+}
+
+image create bitmap tri-rt -background black -foreground blue -data {
+    #define tri-rt_width 13
+    #define tri-rt_height 13
+    static unsigned char tri-rt_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xf0, 0x00,
+       0xf0, 0x01, 0xf0, 0x00, 0x70, 0x00, 0x30, 0x00, 0x10, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-rt-mask_width 13
+    #define tri-rt-mask_height 13
+    static unsigned char tri-rt-mask_bits[] = {
+       0x08, 0x00, 0x18, 0x00, 0x38, 0x00, 0x78, 0x00, 0xf8, 0x00, 0xf8, 0x01,
+       0xf8, 0x03, 0xf8, 0x01, 0xf8, 0x00, 0x78, 0x00, 0x38, 0x00, 0x18, 0x00,
+       0x08, 0x00};
+}
+image create bitmap tri-dn -background black -foreground blue -data {
+    #define tri-dn_width 13
+    #define tri-dn_height 13
+    static unsigned char tri-dn_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xf8, 0x03,
+       0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+} -maskdata {
+    #define tri-dn-mask_width 13
+    #define tri-dn-mask_height 13
+    static unsigned char tri-dn-mask_bits[] = {
+       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x1f, 0xfe, 0x0f, 0xfc, 0x07,
+       0xf8, 0x03, 0xf0, 0x01, 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+       0x00, 0x00};
+}
+
+proc init_flist {first} {
+    global cflist cflist_top selectedline difffilestart
+
+    $cflist conf -state normal
+    $cflist delete 0.0 end
+    if {$first ne {}} {
+       $cflist insert end $first
+       set cflist_top 1
+       $cflist tag add highlight 1.0 "1.0 lineend"
+    } else {
+       catch {unset cflist_top}
+    }
+    $cflist conf -state disabled
+    set difffilestart {}
+}
+
+proc add_flist {fl} {
+    global flistmode cflist
+
+    $cflist conf -state normal
+    if {$flistmode eq "flat"} {
+       foreach f $fl {
+           $cflist insert end "\n$f"
+       }
+    }
+    $cflist conf -state disabled
+}
+
+proc sel_flist {w x y} {
+    global flistmode ctext difffilestart cflist cflist_top cmitmode
+
+    if {$cmitmode eq "tree"} return
+    if {![info exists cflist_top]} return
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    $cflist tag remove highlight $cflist_top.0 "$cflist_top.0 lineend"
+    $cflist tag add highlight $l.0 "$l.0 lineend"
+    set cflist_top $l
+    if {$l == 1} {
+       $ctext yview 1.0
+    } else {
+       catch {$ctext yview [lindex $difffilestart [expr {$l - 2}]]}
+    }
+}
+
+# Functions for adding and removing shell-type quoting
+
+proc shellquote {str} {
+    if {![string match "*\['\"\\ \t]*" $str]} {
+       return $str
+    }
+    if {![string match "*\['\"\\]*" $str]} {
+       return "\"$str\""
+    }
+    if {![string match "*'*" $str]} {
+       return "'$str'"
+    }
+    return "\"[string map {\" \\\" \\ \\\\} $str]\""
+}
+
+proc shellarglist {l} {
+    set str {}
+    foreach a $l {
+       if {$str ne {}} {
+           append str " "
+       }
+       append str [shellquote $a]
+    }
+    return $str
+}
+
+proc shelldequote {str} {
+    set ret {}
+    set used -1
+    while {1} {
+       incr used
+       if {![regexp -start $used -indices "\['\"\\\\ \t]" $str first]} {
+           append ret [string range $str $used end]
+           set used [string length $str]
+           break
+       }
+       set first [lindex $first 0]
+       set ch [string index $str $first]
+       if {$first > $used} {
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+       }
+       if {$ch eq " " || $ch eq "\t"} break
+       incr used
+       if {$ch eq "'"} {
+           set first [string first "'" $str $used]
+           if {$first < 0} {
+               error "unmatched single-quote"
+           }
+           append ret [string range $str $used [expr {$first - 1}]]
+           set used $first
+           continue
+       }
+       if {$ch eq "\\"} {
+           if {$used >= [string length $str]} {
+               error "trailing backslash"
+           }
+           append ret [string index $str $used]
+           continue
+       }
+       # here ch == "\""
+       while {1} {
+           if {![regexp -start $used -indices "\[\"\\\\]" $str first]} {
+               error "unmatched double-quote"
+           }
+           set first [lindex $first 0]
+           set ch [string index $str $first]
+           if {$first > $used} {
+               append ret [string range $str $used [expr {$first - 1}]]
+               set used $first
+           }
+           if {$ch eq "\""} break
+           incr used
+           append ret [string index $str $used]
+           incr used
+       }
+    }
+    return [list $used $ret]
+}
+
+proc shellsplit {str} {
+    set l {}
+    while {1} {
+       set str [string trimleft $str]
+       if {$str eq {}} break
+       set dq [shelldequote $str]
+       set n [lindex $dq 0]
+       set word [lindex $dq 1]
+       set str [string range $str $n end]
+       lappend l $word
+    }
+    return $l
+}
+
+# Code to implement multiple views
+
+proc newview {ishighlight} {
+    global nextviewnum newviewname newviewperm uifont newishighlight
+    global newviewargs revtreeargs
+
+    set newishighlight $ishighlight
+    set top .gitkview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($nextviewnum) "View $nextviewnum"
+    set newviewperm($nextviewnum) 0
+    set newviewargs($nextviewnum) [shellarglist $revtreeargs]
+    vieweditor $top $nextviewnum "Gitk view definition" 
+}
+
+proc editview {} {
+    global curview
+    global viewname viewperm newviewname newviewperm
+    global viewargs newviewargs
+
+    set top .gitkvedit-$curview
+    if {[winfo exists $top]} {
+       raise $top
+       return
+    }
+    set newviewname($curview) $viewname($curview)
+    set newviewperm($curview) $viewperm($curview)
+    set newviewargs($curview) [shellarglist $viewargs($curview)]
+    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
+}
+
+proc vieweditor {top n title} {
+    global newviewname newviewperm viewfiles
+    global uifont
+
+    toplevel $top
+    wm title $top $title
+    label $top.nl -text "Name" -font $uifont
+    entry $top.name -width 20 -textvariable newviewname($n)
+    grid $top.nl $top.name -sticky w -pady 5
+    checkbutton $top.perm -text "Remember this view" -variable newviewperm($n)
+    grid $top.perm - -pady 5 -sticky w
+    message $top.al -aspect 1000 -font $uifont \
+       -text "Commits to include (arguments to git-rev-list):"
+    grid $top.al - -sticky w -pady 5
+    entry $top.args -width 50 -textvariable newviewargs($n) \
+       -background white
+    grid $top.args - -sticky ew -padx 5
+    message $top.l -aspect 1000 -font $uifont \
+       -text "Enter files and directories to include, one per line:"
+    grid $top.l - -sticky w
+    text $top.t -width 40 -height 10 -background white
+    if {[info exists viewfiles($n)]} {
+       foreach f $viewfiles($n) {
+           $top.t insert end $f
+           $top.t insert end "\n"
+       }
+       $top.t delete {end - 1c} end
+       $top.t mark set insert 0.0
+    }
+    grid $top.t - -sticky ew -padx 5
+    frame $top.buts
+    button $top.buts.ok -text "OK" -command [list newviewok $top $n]
+    button $top.buts.can -text "Cancel" -command [list destroy $top]
+    grid $top.buts.ok $top.buts.can
+    grid columnconfigure $top.buts 0 -weight 1 -uniform a
+    grid columnconfigure $top.buts 1 -weight 1 -uniform a
+    grid $top.buts - -pady 10 -sticky ew
+    focus $top.t
+}
+
+proc doviewmenu {m first cmd op args} {
+    set nmenu [$m index end]
+    for {set i $first} {$i <= $nmenu} {incr i} {
+       if {[$m entrycget $i -command] eq $cmd} {
+           eval $m $op $i $args
+           break
+       }
+    }
+}
+
+proc allviewmenus {n op args} {
+    doviewmenu .bar.view 7 [list showview $n] $op $args
+    doviewmenu .bar.view.hl 3 [list addhighlight $n] $op $args
+}
+
+proc newviewok {top n} {
+    global nextviewnum newviewperm newviewname newishighlight
+    global viewname viewfiles viewperm selectedview curview
+    global viewargs newviewargs
+
+    if {[catch {
+       set newargs [shellsplit $newviewargs($n)]
+    } err]} {
+       error_popup "Error in commit selection arguments: $err"
+       wm raise $top
+       focus $top
+       return
+    }
+    set files {}
+    foreach f [split [$top.t get 0.0 end] "\n"] {
+       set ft [string trim $f]
+       if {$ft ne {}} {
+           lappend files $ft
+       }
+    }
+    if {![info exists viewfiles($n)]} {
+       # creating a new view
+       incr nextviewnum
+       set viewname($n) $newviewname($n)
+       set viewperm($n) $newviewperm($n)
+       set viewfiles($n) $files
+       set viewargs($n) $newargs
+       addviewmenu $n
+       if {!$newishighlight} {
+           after idle showview $n
+       } else {
+           after idle addhighlight $n
+       }
+    } else {
+       # editing an existing view
+       set viewperm($n) $newviewperm($n)
+       if {$newviewname($n) ne $viewname($n)} {
+           set viewname($n) $newviewname($n)
+           allviewmenus $n entryconf -label $viewname($n)
+       }
+       if {$files ne $viewfiles($n) || $newargs ne $viewargs($n)} {
+           set viewfiles($n) $files
+           set viewargs($n) $newargs
+           if {$curview == $n} {
+               after idle updatecommits
+           }
+       }
+    }
+    catch {destroy $top}
+}
+
+proc delview {} {
+    global curview viewdata viewperm
+
+    if {$curview == 0} return
+    allviewmenus $curview delete
+    set viewdata($curview) {}
+    set viewperm($curview) 0
+    showview 0
+}
+
+proc addviewmenu {n} {
+    global viewname
+
+    .bar.view add radiobutton -label $viewname($n) \
+       -command [list showview $n] -variable selectedview -value $n
+    .bar.view.hl add radiobutton -label $viewname($n) \
+       -command [list addhighlight $n] -variable selectedhlview -value $n
+}
+
+proc flatten {var} {
+    global $var
+
+    set ret {}
+    foreach i [array names $var] {
+       lappend ret $i [set $var\($i\)]
+    }
+    return $ret
+}
+
+proc unflatten {var l} {
+    global $var
+
+    catch {unset $var}
+    foreach {i v} $l {
+       set $var\($i\) $v
+    }
+}
+
+proc showview {n} {
+    global curview viewdata viewfiles
+    global displayorder parentlist childlist rowidlist rowoffsets
+    global colormap rowtextx commitrow nextcolor canvxmax
+    global numcommits rowrangelist commitlisted idrowranges
+    global selectedline currentid canv canvy0
+    global matchinglines treediffs
+    global pending_select phase
+    global commitidx rowlaidout rowoptim linesegends
+    global commfd nextupdate
+    global selectedview hlview selectedhlview
+    global vparentlist vchildlist vdisporder vcmitlisted
+
+    if {$n == $curview} return
+    set selid {}
+    if {[info exists selectedline]} {
+       set selid $currentid
+       set y [yc $selectedline]
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set span [$canv yview]
+       set ytop [expr {[lindex $span 0] * $ymax}]
+       set ybot [expr {[lindex $span 1] * $ymax}]
+       if {$ytop < $y && $y < $ybot} {
+           set yscreen [expr {$y - $ytop}]
+       } else {
+           set yscreen [expr {($ybot - $ytop) / 2}]
+       }
+    }
+    unselectline
+    normalline
+    stopfindproc
+    if {$curview >= 0} {
+       set vparentlist($curview) $parentlist
+       set vchildlist($curview) $childlist
+       set vdisporder($curview) $displayorder
+       set vcmitlisted($curview) $commitlisted
+       if {$phase ne {}} {
+           set viewdata($curview) \
+               [list $phase $rowidlist $rowoffsets $rowrangelist \
+                    [flatten idrowranges] [flatten idinlist] \
+                    $rowlaidout $rowoptim $numcommits $linesegends]
+       } elseif {![info exists viewdata($curview)]
+                 || [lindex $viewdata($curview) 0] ne {}} {
+           set viewdata($curview) \
+               [list {} $rowidlist $rowoffsets $rowrangelist]
+       }
+    }
+    catch {unset matchinglines}
+    catch {unset treediffs}
+    clear_display
+
+    set curview $n
+    set selectedview $n
+    set selectedhlview -1
+    .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
+    catch {unset hlview}
+    .bar.view.hl entryconf 1 -state disabled
+
+    if {![info exists viewdata($n)]} {
+       set pending_select $selid
+       getcommits
+       return
+    }
+
+    set v $viewdata($n)
+    set phase [lindex $v 0]
+    set displayorder $vdisporder($n)
+    set parentlist $vparentlist($n)
+    set childlist $vchildlist($n)
+    set commitlisted $vcmitlisted($n)
+    set rowidlist [lindex $v 1]
+    set rowoffsets [lindex $v 2]
+    set rowrangelist [lindex $v 3]
+    if {$phase eq {}} {
+       set numcommits [llength $displayorder]
+       catch {unset idrowranges}
+    } else {
+       unflatten idrowranges [lindex $v 4]
+       unflatten idinlist [lindex $v 5]
+       set rowlaidout [lindex $v 6]
+       set rowoptim [lindex $v 7]
+       set numcommits [lindex $v 8]
+       set linesegends [lindex $v 9]
+    }
+
+    catch {unset colormap}
+    catch {unset rowtextx}
+    set nextcolor 0
+    set canvxmax [$canv cget -width]
+    set curview $n
+    set row 0
+    setcanvscroll
+    set yf 0
+    set row 0
+    if {$selid ne {} && [info exists commitrow($n,$selid)]} {
+       set row $commitrow($n,$selid)
+       # try to get the selected row in the same position on the screen
+       set ymax [lindex [$canv cget -scrollregion] 3]
+       set ytop [expr {[yc $row] - $yscreen}]
+       if {$ytop < 0} {
+           set ytop 0
+       }
+       set yf [expr {$ytop * 1.0 / $ymax}]
+    }
+    allcanvs yview moveto $yf
+    drawvisible
+    selectline $row 0
+    if {$phase ne {}} {
+       if {$phase eq "getcommits"} {
+           show_status "Reading commits..."
+       }
+       if {[info exists commfd($n)]} {
+           layoutmore
+       } else {
+           finishcommits
+       }
+    } elseif {$numcommits == 0} {
+       show_status "No commits selected"
+    }
+}
+
+proc addhighlight {n} {
+    global hlview curview viewdata highlighted highlightedrows
+    global selectedhlview
+
+    if {[info exists hlview]} {
+       delhighlight
+    }
+    set hlview $n
+    set selectedhlview $n
+    .bar.view.hl entryconf 1 -state normal
+    set highlighted($n) 0
+    set highlightedrows {}
+    if {$n != $curview && ![info exists viewdata($n)]} {
+       set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
+       set vparentlist($n) {}
+       set vchildlist($n) {}
+       set vdisporder($n) {}
+       set vcmitlisted($n) {}
+       start_rev_list $n
+    } else {
+       highlightmore
+    }
+}
+
+proc delhighlight {} {
+    global hlview highlightedrows canv linehtag mainfont
+    global selectedhlview selectedline
+
+    if {![info exists hlview]} return
+    unset hlview
+    set selectedhlview {}
+    .bar.view.hl entryconf 1 -state disabled
+    foreach l $highlightedrows {
+       $canv itemconf $linehtag($l) -font $mainfont
+       if {$l == $selectedline} {
+           $canv delete secsel
+           set t [eval $canv create rect [$canv bbox $linehtag($l)] \
+                      -outline {{}} -tags secsel \
+                      -fill [$canv cget -selectbackground]]
+           $canv lower $t
+       }
+    }
+}
+
+proc highlightmore {} {
+    global hlview highlighted commitidx highlightedrows linehtag mainfont
+    global displayorder vdisporder curview canv commitrow selectedline
+
+    set font [concat $mainfont bold]
+    set max $commitidx($hlview)
+    if {$hlview == $curview} {
+       set disp $displayorder
+    } else {
+       set disp $vdisporder($hlview)
+    }
+    for {set i $highlighted($hlview)} {$i < $max} {incr i} {
+       set id [lindex $disp $i]
+       if {[info exists commitrow($curview,$id)]} {
+           set row $commitrow($curview,$id)
+           if {[info exists linehtag($row)]} {
+               $canv itemconf $linehtag($row) -font $font
+               lappend highlightedrows $row
+               if {$row == $selectedline} {
+                   $canv delete secsel
+                   set t [eval $canv create rect \
+                              [$canv bbox $linehtag($row)] \
+                              -outline {{}} -tags secsel \
+                              -fill [$canv cget -selectbackground]]
+                   $canv lower $t
+               }
+           }
+       }
+    }
+    set highlighted($hlview) $max
+}
+
+# Graph layout functions
+
 proc shortids {ids} {
     set res {}
     foreach id $ids {
@@ -805,20 +1652,21 @@ proc ntimes {n o} {
 }
 
 proc usedinrange {id l1 l2} {
-    global children commitrow
+    global children commitrow childlist curview
 
-    if {[info exists commitrow($id)]} {
-       set r $commitrow($id)
+    if {[info exists commitrow($curview,$id)]} {
+       set r $commitrow($curview,$id)
        if {$l1 <= $r && $r <= $l2} {
            return [expr {$r - $l1 + 1}]
        }
+       set kids [lindex $childlist $r]
+    } else {
+       set kids $children($curview,$id)
     }
-    foreach c $children($id) {
-       if {[info exists commitrow($c)]} {
-           set r $commitrow($c)
-           if {$l1 <= $r && $r <= $l2} {
-               return [expr {$r - $l1 + 1}]
-           }
+    foreach c $kids {
+       set r $commitrow($curview,$c)
+       if {$l1 <= $r && $r <= $l2} {
+           return [expr {$r - $l1 + 1}]
        }
     }
     return 0
@@ -886,18 +1734,19 @@ proc makeuparrow {oid x y z} {
 proc initlayout {} {
     global rowidlist rowoffsets displayorder commitlisted
     global rowlaidout rowoptim
-    global idinlist rowchk
-    global commitidx numcommits canvxmax canv
+    global idinlist rowchk rowrangelist idrowranges
+    global numcommits canvxmax canv
     global nextcolor
     global parentlist childlist children
+    global colormap rowtextx
+    global linesegends
 
-    set commitidx 0
     set numcommits 0
     set displayorder {}
     set commitlisted {}
     set parentlist {}
     set childlist {}
-    catch {unset children}
+    set rowrangelist {}
     set nextcolor 0
     set rowidlist {{}}
     set rowoffsets {{}}
@@ -906,6 +1755,10 @@ proc initlayout {} {
     set rowlaidout 0
     set rowoptim 0
     set canvxmax [$canv cget -width]
+    catch {unset colormap}
+    catch {unset rowtextx}
+    catch {unset idrowranges}
+    set linesegends {}
 }
 
 proc setcanvscroll {} {
@@ -938,13 +1791,12 @@ proc visiblerows {} {
 
 proc layoutmore {} {
     global rowlaidout rowoptim commitidx numcommits optim_delay
-    global uparrowlen
+    global uparrowlen curview
 
     set row $rowlaidout
-    set rowlaidout [layoutrows $row $commitidx 0]
+    set rowlaidout [layoutrows $row $commitidx($curview) 0]
     set orow [expr {$rowlaidout - $uparrowlen - 1}]
     if {$orow > $rowoptim} {
-       checkcrossings $rowoptim $orow
        optimize_rows $rowoptim 0 $orow
        set rowoptim $orow
     }
@@ -955,8 +1807,8 @@ proc layoutmore {} {
 }
 
 proc showstuff {canshow} {
-    global numcommits
-    global linesegends idrowranges idrangedrawn
+    global numcommits commitrow pending_select selectedline
+    global linesegends idrowranges idrangedrawn curview
 
     if {$numcommits == 0} {
        global phase
@@ -969,17 +1821,16 @@ proc showstuff {canshow} {
     set rows [visiblerows]
     set r0 [lindex $rows 0]
     set r1 [lindex $rows 1]
+    set selrow -1
     for {set r $row} {$r < $canshow} {incr r} {
-       if {[info exists linesegends($r)]} {
-           foreach id $linesegends($r) {
-               set i -1
-               foreach {s e} $idrowranges($id) {
-                   incr i
-                   if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
-                       && ![info exists idrangedrawn($id,$i)]} {
-                       drawlineseg $id $i
-                       set idrangedrawn($id,$i) 1
-                   }
+       foreach id [lindex $linesegends [expr {$r+1}]] {
+           set i -1
+           foreach {s e} [rowranges $id] {
+               incr i
+               if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
+                   && ![info exists idrangedrawn($id,$i)]} {
+                   drawlineseg $id $i
+                   set idrangedrawn($id,$i) 1
                }
            }
        }
@@ -991,6 +1842,14 @@ proc showstuff {canshow} {
        drawcmitrow $row
        incr row
     }
+    if {[info exists pending_select] &&
+       [info exists commitrow($curview,$pending_select)] &&
+       $commitrow($curview,$pending_select) < $numcommits} {
+       selectline $commitrow($curview,$pending_select) 1
+    }
+    if {![info exists selectedline] && ![info exists pending_select]} {
+       selectline 0 1
+    }
 }
 
 proc layoutrows {row endrow last} {
@@ -998,8 +1857,8 @@ proc layoutrows {row endrow last} {
     global uparrowlen downarrowlen maxwidth mingaplen
     global childlist parentlist
     global idrowranges linesegends
-    global commitidx
-    global idinlist rowchk
+    global commitidx curview
+    global idinlist rowchk rowrangelist
 
     set idlist [lindex $rowidlist $row]
     set offs [lindex $rowoffsets $row]
@@ -1014,10 +1873,12 @@ proc layoutrows {row endrow last} {
                lappend oldolds $p
            }
        }
+       set lse {}
        set nev [expr {[llength $idlist] + [llength $newolds]
                       + [llength $oldolds] - $maxwidth + 1}]
        if {$nev > 0} {
-           if {!$last && $row + $uparrowlen + $mingaplen >= $commitidx} break
+           if {!$last &&
+               $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
            for {set x [llength $idlist]} {[incr x -1] >= 0} {} {
                set i [lindex $idlist $x]
                if {![info exists rowchk($i)] || $row >= $rowchk($i)} {
@@ -1029,7 +1890,7 @@ proc layoutrows {row endrow last} {
                        set offs [incrange $offs $x 1]
                        set idinlist($i) 0
                        set rm1 [expr {$row - 1}]
-                       lappend linesegends($rm1) $i
+                       lappend lse $i
                        lappend idrowranges($i) $rm1
                        if {[incr nev -1] <= 0} break
                        continue
@@ -1040,6 +1901,7 @@ proc layoutrows {row endrow last} {
            lset rowidlist $row $idlist
            lset rowoffsets $row $offs
        }
+       lappend linesegends $lse
        set col [lsearch -exact $idlist $id]
        if {$col < 0} {
            set col [llength $idlist]
@@ -1058,9 +1920,13 @@ proc layoutrows {row endrow last} {
        } else {
            unset idinlist($id)
        }
+       set ranges {}
        if {[info exists idrowranges($id)]} {
-           lappend idrowranges($id) $row
+           set ranges $idrowranges($id)
+           lappend ranges $row
+           unset idrowranges($id)
        }
+       lappend rowrangelist $ranges
        incr row
        set offs [ntimes [llength $idlist] 0]
        set l [llength $newolds]
@@ -1101,29 +1967,28 @@ proc layoutrows {row endrow last} {
 proc addextraid {id row} {
     global displayorder commitrow commitinfo
     global commitidx commitlisted
-    global parentlist childlist children
+    global parentlist childlist children curview
 
-    incr commitidx
+    incr commitidx($curview)
     lappend displayorder $id
     lappend commitlisted 0
     lappend parentlist {}
-    set commitrow($id) $row
+    set commitrow($curview,$id) $row
     readcommit $id
     if {![info exists commitinfo($id)]} {
        set commitinfo($id) {"No commit information available"}
     }
-    if {[info exists children($id)]} {
-       lappend childlist $children($id)
-    } else {
-       lappend childlist {}
+    if {![info exists children($curview,$id)]} {
+       set children($curview,$id) {}
     }
+    lappend childlist $children($curview,$id)
 }
 
 proc layouttail {} {
-    global rowidlist rowoffsets idinlist commitidx
-    global idrowranges
+    global rowidlist rowoffsets idinlist commitidx curview
+    global idrowranges rowrangelist
 
-    set row $commitidx
+    set row $commitidx($curview)
     set idlist [lindex $rowidlist $row]
     while {$idlist ne {}} {
        set col [expr {[llength $idlist] - 1}]
@@ -1131,6 +1996,8 @@ proc layouttail {} {
        addextraid $id $row
        unset idinlist($id)
        lappend idrowranges($id) $row
+       lappend rowrangelist $idrowranges($id)
+       unset idrowranges($id)
        incr row
        set offs [ntimes $col 0]
        set idlist [lreplace $idlist $col $col]
@@ -1144,6 +2011,8 @@ proc layouttail {} {
        lset rowoffsets $row 0
        makeuparrow $id 0 $row 0
        lappend idrowranges($id) $row
+       lappend rowrangelist $idrowranges($id)
+       unset idrowranges($id)
        incr row
        lappend rowidlist {}
        lappend rowoffsets {}
@@ -1160,7 +2029,7 @@ proc insert_pad {row col npad} {
 }
 
 proc optimize_rows {row col endrow} {
-    global rowidlist rowoffsets idrowranges linesegends displayorder
+    global rowidlist rowoffsets idrowranges displayorder
 
     for {} {$row < $endrow} {incr row} {
        set idlist [lindex $rowidlist $row]
@@ -1179,8 +2048,8 @@ proc optimize_rows {row col endrow} {
            set z0 [lindex $rowoffsets $y0 $x0]
            if {$z0 eq {}} {
                set id [lindex $idlist $col]
-               if {[info exists idrowranges($id)] &&
-                   $y0 > [lindex $idrowranges($id) 0]} {
+               set ranges [rowranges $id]
+               if {$ranges ne {} && $y0 > [lindex $ranges 0]} {
                    set isarrow 1
                }
            }
@@ -1238,8 +2107,8 @@ proc optimize_rows {row col endrow} {
                if {$o eq {}} {
                    # check if this is the link to the first child
                    set id [lindex $idlist $col]
-                   if {[info exists idrowranges($id)] &&
-                       $row == [lindex $idrowranges($id) 0]} {
+                   set ranges [rowranges $id]
+                   if {$ranges ne {} && $row == [lindex $ranges 0]} {
                        # it is, work out offset to child
                        set y0 [expr {$row - 1}]
                        set id [lindex $displayorder $y0]
@@ -1293,13 +2162,36 @@ proc linewidth {id} {
     return $wid
 }
 
+proc rowranges {id} {
+    global phase idrowranges commitrow rowlaidout rowrangelist curview
+
+    set ranges {}
+    if {$phase eq {} ||
+       ([info exists commitrow($curview,$id)]
+        && $commitrow($curview,$id) < $rowlaidout)} {
+       set ranges [lindex $rowrangelist $commitrow($curview,$id)]
+    } elseif {[info exists idrowranges($id)]} {
+       set ranges $idrowranges($id)
+    }
+    return $ranges
+}
+
 proc drawlineseg {id i} {
-    global rowoffsets rowidlist idrowranges
+    global rowoffsets rowidlist
     global displayorder
     global canv colormap linespc
+    global numcommits commitrow curview
 
-    set startrow [lindex $idrowranges($id) [expr {2 * $i}]]
-    set row [lindex $idrowranges($id) [expr {2 * $i + 1}]]
+    set ranges [rowranges $id]
+    set downarrow 1
+    if {[info exists commitrow($curview,$id)]
+       && $commitrow($curview,$id) < $numcommits} {
+       set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
+    } else {
+       set downarrow 1
+    }
+    set startrow [lindex $ranges [expr {2 * $i}]]
+    set row [lindex $ranges [expr {2 * $i + 1}]]
     if {$startrow == $row} return
     assigncolor $id
     set coords {}
@@ -1343,8 +2235,7 @@ proc drawlineseg {id i} {
        }
     }
     if {[llength $coords] < 4} return
-    set last [expr {[llength $idrowranges($id)] / 2 - 1}]
-    if {$i < $last} {
+    if {$downarrow} {
        # This line has an arrow at the lower end: check if the arrow is
        # on a diagonal segment, and if so, work around the Tk 8.4
        # refusal to draw arrows on diagonal lines.
@@ -1364,7 +2255,7 @@ proc drawlineseg {id i} {
            }
        }
     }
-    set arrow [expr {2 * ($i > 0) + ($i < $last)}]
+    set arrow [expr {2 * ($i > 0) + $downarrow}]
     set arrow [lindex {none first last both} $arrow]
     set t [$canv create line $coords -width [linewidth $id] \
               -fill $colormap($id) -tags lines.$id -arrow $arrow]
@@ -1373,7 +2264,7 @@ proc drawlineseg {id i} {
 }
 
 proc drawparentlinks {id row col olds} {
-    global rowidlist canv colormap idrowranges
+    global rowidlist canv colormap
 
     set row2 [expr {$row + 1}]
     set x [xc $row $col]
@@ -1392,9 +2283,9 @@ proc drawparentlinks {id row col olds} {
        if {$x2 > $rmx} {
            set rmx $x2
        }
-       if {[info exists idrowranges($p)] &&
-           $row2 == [lindex $idrowranges($p) 0] &&
-           $row2 < [lindex $idrowranges($p) 1]} {
+       set ranges [rowranges $p]
+       if {$ranges ne {} && $row2 == [lindex $ranges 0]
+           && $row2 < [lindex $ranges 1]} {
            # drawlineseg will do this one for us
            continue
        }
@@ -1417,19 +2308,19 @@ proc drawparentlinks {id row col olds} {
 
 proc drawlines {id} {
     global colormap canv
-    global idrowranges idrangedrawn
-    global childlist iddrawn commitrow rowidlist
+    global idrangedrawn
+    global children iddrawn commitrow rowidlist curview
 
     $canv delete lines.$id
-    set nr [expr {[llength $idrowranges($id)] / 2}]
+    set nr [expr {[llength [rowranges $id]] / 2}]
     for {set i 0} {$i < $nr} {incr i} {
        if {[info exists idrangedrawn($id,$i)]} {
            drawlineseg $id $i
        }
     }
-    foreach child [lindex $childlist $commitrow($id)] {
+    foreach child $children($curview,$id) {
        if {[info exists iddrawn($child)]} {
-           set row $commitrow($child)
+           set row $commitrow($curview,$child)
            set col [lsearch -exact [lindex $rowidlist $row] $child]
            if {$col >= 0} {
                drawparentlinks $child $row $col [list $id]
@@ -1443,7 +2334,8 @@ proc drawcmittext {id row col rmx} {
     global commitlisted commitinfo rowidlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag
-    global mainfont namefont canvxmax
+    global mainfont canvxmax
+    global hlview commitrow highlightedrows
 
     set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
     set x [xc $row $col]
@@ -1468,11 +2360,16 @@ proc drawcmittext {id row col rmx} {
     set name [lindex $commitinfo($id) 1]
     set date [lindex $commitinfo($id) 2]
     set date [formatdate $date]
+    set font $mainfont
+    if {[info exists hlview] && [info exists commitrow($hlview,$id)]} {
+       lappend font bold
+       lappend highlightedrows $row
+    }
     set linehtag($row) [$canv create text $xt $y -anchor w \
-                           -text $headline -font $mainfont ]
+                           -text $headline -font $font]
     $canv bind $linehtag($row) <Button-3> "rowmenu %X %Y $id"
     set linentag($row) [$canv2 create text 3 $y -anchor w \
-                           -text $name -font $namefont]
+                           -text $name -font $mainfont]
     set linedtag($row) [$canv3 create text 3 $y -anchor w \
                            -text $date -font $mainfont]
     set xr [expr {$xt + [font measure $mainfont $headline]}]
@@ -1484,14 +2381,14 @@ proc drawcmittext {id row col rmx} {
 
 proc drawcmitrow {row} {
     global displayorder rowidlist
-    global idrowranges idrangedrawn iddrawn
+    global idrangedrawn iddrawn
     global commitinfo parentlist numcommits
 
     if {$row >= $numcommits} return
     foreach id [lindex $rowidlist $row] {
-       if {![info exists idrowranges($id)]} continue
+       if {$id eq {}} continue
        set i -1
-       foreach {s e} $idrowranges($id) {
+       foreach {s e} [rowranges $id] {
            incr i
            if {$row < $s} continue
            if {$e eq {}} break
@@ -1560,62 +2457,90 @@ proc clear_display {} {
     catch {unset idrangedrawn}
 }
 
+proc findcrossings {id} {
+    global rowidlist parentlist numcommits rowoffsets displayorder
+
+    set cross {}
+    set ccross {}
+    foreach {s e} [rowranges $id] {
+       if {$e >= $numcommits} {
+           set e [expr {$numcommits - 1}]
+       }
+       if {$e <= $s} continue
+       set x [lsearch -exact [lindex $rowidlist $e] $id]
+       if {$x < 0} {
+           puts "findcrossings: oops, no [shortids $id] in row $e"
+           continue
+       }
+       for {set row $e} {[incr row -1] >= $s} {} {
+           set olds [lindex $parentlist $row]
+           set kid [lindex $displayorder $row]
+           set kidx [lsearch -exact [lindex $rowidlist $row] $kid]
+           if {$kidx < 0} continue
+           set nextrow [lindex $rowidlist [expr {$row + 1}]]
+           foreach p $olds {
+               set px [lsearch -exact $nextrow $p]
+               if {$px < 0} continue
+               if {($kidx < $x && $x < $px) || ($px < $x && $x < $kidx)} {
+                   if {[lsearch -exact $ccross $p] >= 0} continue
+                   if {$x == $px + ($kidx < $px? -1: 1)} {
+                       lappend ccross $p
+                   } elseif {[lsearch -exact $cross $p] < 0} {
+                       lappend cross $p
+                   }
+               }
+           }
+           set inc [lindex $rowoffsets $row $x]
+           if {$inc eq {}} break
+           incr x $inc
+       }
+    }
+    return [concat $ccross {{}} $cross]
+}
+
 proc assigncolor {id} {
     global colormap colors nextcolor
-    global commitrow parentlist children childlist
-    global cornercrossings crossings
+    global commitrow parentlist children children curview
 
     if {[info exists colormap($id)]} return
     set ncolors [llength $colors]
-    if {[info exists commitrow($id)]} {
-       set kids [lindex $childlist $commitrow($id)]
-    } elseif {[info exists children($id)]} {
-       set kids $children($id)
+    if {[info exists children($curview,$id)]} {
+       set kids $children($curview,$id)
     } else {
        set kids {}
     }
     if {[llength $kids] == 1} {
        set child [lindex $kids 0]
        if {[info exists colormap($child)]
-           && [llength [lindex $parentlist $commitrow($child)]] == 1} {
+           && [llength [lindex $parentlist $commitrow($curview,$child)]] == 1} {
            set colormap($id) $colormap($child)
            return
        }
     }
     set badcolors {}
-    if {[info exists cornercrossings($id)]} {
-       foreach x $cornercrossings($id) {
-           if {[info exists colormap($x)]
-               && [lsearch -exact $badcolors $colormap($x)] < 0} {
-               lappend badcolors $colormap($x)
-           }
+    set origbad {}
+    foreach x [findcrossings $id] {
+       if {$x eq {}} {
+           # delimiter between corner crossings and other crossings
+           if {[llength $badcolors] >= $ncolors - 1} break
+           set origbad $badcolors
        }
-       if {[llength $badcolors] >= $ncolors} {
-           set badcolors {}
+       if {[info exists colormap($x)]
+           && [lsearch -exact $badcolors $colormap($x)] < 0} {
+           lappend badcolors $colormap($x)
        }
     }
-    set origbad $badcolors
-    if {[llength $badcolors] < $ncolors - 1} {
-       if {[info exists crossings($id)]} {
-           foreach x $crossings($id) {
-               if {[info exists colormap($x)]
-                   && [lsearch -exact $badcolors $colormap($x)] < 0} {
-                   lappend badcolors $colormap($x)
-               }
-           }
-           if {[llength $badcolors] >= $ncolors} {
-               set badcolors $origbad
-           }
-       }
-       set origbad $badcolors
+    if {[llength $badcolors] >= $ncolors} {
+       set badcolors $origbad
     }
+    set origbad $badcolors
     if {[llength $badcolors] < $ncolors - 1} {
        foreach child $kids {
            if {[info exists colormap($child)]
                && [lsearch -exact $badcolors $colormap($child)] < 0} {
                lappend badcolors $colormap($child)
            }
-           foreach p [lindex $parentlist $commitrow($child)] {
+           foreach p [lindex $parentlist $commitrow($curview,$child)] {
                if {[info exists colormap($p)]
                    && [lsearch -exact $badcolors $colormap($p)] < 0} {
                    lappend badcolors $colormap($p)
@@ -1648,7 +2573,7 @@ proc bindline {t id} {
 proc drawtags {id x xt y1} {
     global idtags idheads idotherrefs
     global linespc lthickness
-    global canv mainfont commitrow rowtextx
+    global canv mainfont commitrow rowtextx curview
 
     set marks {}
     set ntags 0
@@ -1691,7 +2616,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($id)) [expr {$xr + $linespc}]
+           set rowtextx($commitrow($curview,$id)) [expr {$xr + $linespc}]
        } else {
            # draw a head or other ref
            if {[incr nheads -1] >= 0} {
@@ -1702,6 +2627,14 @@ proc drawtags {id x xt y1} {
            set xl [expr {$xl - $delta/2}]
            $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
                -width 1 -outline black -fill $col -tags tag.$id
+           if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} {
+               set rwid [font measure $mainfont $remoteprefix]
+               set xi [expr {$x + 1}]
+               set yti [expr {$yt + 1}]
+               set xri [expr {$x + $rwid}]
+               $canv create polygon $xi $yti $xri $yti $xri $yb $xi $yb \
+                       -width 0 -fill "#ffddaa" -tags tag.$id
+           }
        }
        set t [$canv create text $xl $y1 -anchor w -text $tag \
                   -font $mainfont -tags tag.$id]
@@ -1712,55 +2645,6 @@ proc drawtags {id x xt y1} {
     return $xt
 }
 
-proc checkcrossings {row endrow} {
-    global displayorder parentlist rowidlist
-
-    for {} {$row < $endrow} {incr row} {
-       set id [lindex $displayorder $row]
-       set i [lsearch -exact [lindex $rowidlist $row] $id]
-       if {$i < 0} continue
-       set idlist [lindex $rowidlist [expr {$row+1}]]
-       foreach p [lindex $parentlist $row] {
-           set j [lsearch -exact $idlist $p]
-           if {$j > 0} {
-               if {$j < $i - 1} {
-                   notecrossings $row $p $j $i [expr {$j+1}]
-               } elseif {$j > $i + 1} {
-                   notecrossings $row $p $i $j [expr {$j-1}]
-               }
-           }
-       }
-    }
-}
-
-proc notecrossings {row id lo hi corner} {
-    global rowidlist crossings cornercrossings
-
-    for {set i $lo} {[incr i] < $hi} {} {
-       set p [lindex [lindex $rowidlist $row] $i]
-       if {$p == {}} continue
-       if {$i == $corner} {
-           if {![info exists cornercrossings($id)]
-               || [lsearch -exact $cornercrossings($id) $p] < 0} {
-               lappend cornercrossings($id) $p
-           }
-           if {![info exists cornercrossings($p)]
-               || [lsearch -exact $cornercrossings($p) $id] < 0} {
-               lappend cornercrossings($p) $id
-           }
-       } else {
-           if {![info exists crossings($id)]
-               || [lsearch -exact $crossings($id) $p] < 0} {
-               lappend crossings($id) $p
-           }
-           if {![info exists crossings($p)]
-               || [lsearch -exact $crossings($p) $id] < 0} {
-               lappend crossings($p) $id
-           }
-       }
-    }
-}
-
 proc xcoord {i level ln} {
     global canvx0 xspc1 xspc2
 
@@ -1773,23 +2657,25 @@ proc xcoord {i level ln} {
     return $x
 }
 
+proc show_status {msg} {
+    global canv mainfont
+
+    clear_display
+    $canv create text 3 3 -anchor nw -text $msg -font $mainfont -tags textitems
+}
+
 proc finishcommits {} {
-    global commitidx phase
+    global commitidx phase curview
     global canv mainfont ctext maincursor textcursor
-    global findinprogress
+    global findinprogress pending_select
 
-    if {$commitidx > 0} {
+    if {$commitidx($curview) > 0} {
        drawrest
     } else {
-       $canv delete all
-       $canv create text 3 3 -anchor nw -text "No commits selected" \
-           -font $mainfont -tags textitems
-    }
-    if {![info exists findinprogress]} {
-       . config -cursor $maincursor
-       settextcursor $textcursor
+       show_status "No commits selected"
     }
     set phase {}
+    catch {unset pending_select}
 }
 
 # Don't change the text pane cursor if it is currently the hand cursor,
@@ -1803,17 +2689,41 @@ proc settextcursor {c} {
     set curtextcursor $c
 }
 
+proc nowbusy {what} {
+    global isbusy
+
+    if {[array names isbusy] eq {}} {
+       . config -cursor watch
+       settextcursor watch
+    }
+    set isbusy($what) 1
+}
+
+proc notbusy {what} {
+    global isbusy maincursor textcursor
+
+    catch {unset isbusy($what)}
+    if {[array names isbusy] eq {}} {
+       . config -cursor $maincursor
+       settextcursor $textcursor
+    }
+}
+
 proc drawrest {} {
     global numcommits
     global startmsecs
     global canvy0 numcommits linespc
-    global rowlaidout commitidx
+    global rowlaidout commitidx curview
+    global pending_select
 
     set row $rowlaidout
-    layoutrows $rowlaidout $commitidx 1
+    layoutrows $rowlaidout $commitidx($curview) 1
     layouttail
-    optimize_rows $row 0 $commitidx
-    showstuff $commitidx
+    optimize_rows $row 0 $commitidx($curview)
+    showstuff $commitidx($curview)
+    if {[info exists pending_select]} {
+       selectline 0 1
+    }
 
     set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
     #puts "overall $drawmsecs ms for $numcommits commits"
@@ -1842,7 +2752,7 @@ proc findmatches {f} {
 proc dofind {} {
     global findtype findloc findstring markedmatches commitinfo
     global numcommits displayorder linehtag linentag linedtag
-    global mainfont namefont canv canv2 canv3 selectedline
+    global mainfont canv canv2 canv3 selectedline
     global matchinglines foundstring foundstrlen matchstring
     global commitdata
 
@@ -1903,7 +2813,7 @@ proc dofind {} {
                markmatches $canv $l $f $linehtag($l) $matches $mainfont
            } elseif {$ty == "Author"} {
                drawcmitrow $l
-               markmatches $canv2 $l $f $linentag($l) $matches $namefont
+               markmatches $canv2 $l $f $linentag($l) $matches $mainfont
            } elseif {$ty == "Date"} {
                drawcmitrow $l
                markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
@@ -2001,13 +2911,8 @@ proc stopfindproc {{done 0}} {
        catch {close $findprocfile}
        unset findprocpid
     }
-    if {[info exists findinprogress]} {
-       unset findinprogress
-       if {$phase != "incrdraw"} {
-           . config -cursor $maincursor
-           settextcursor $textcursor
-       }
-    }
+    catch {unset findinprogress}
+    notbusy find
 }
 
 proc findpatches {} {
@@ -2047,14 +2952,13 @@ proc findpatches {} {
     fconfigure $f -blocking 0
     fileevent $f readable readfindproc
     set finddidsel 0
-    . config -cursor watch
-    settextcursor watch
+    nowbusy find
     set findinprogress 1
 }
 
 proc readfindproc {} {
     global findprocfile finddidsel
-    global commitrow matchinglines findinsertpos
+    global commitrow matchinglines findinsertpos curview
 
     set n [gets $findprocfile line]
     if {$n < 0} {
@@ -2071,11 +2975,11 @@ proc readfindproc {} {
        stopfindproc
        return
     }
-    if {![info exists commitrow($id)]} {
+    if {![info exists commitrow($curview,$id)]} {
        puts stderr "spurious id: $id"
        return
     }
-    set l $commitrow($id)
+    set l $commitrow($curview,$id)
     insertmatch $l $id
 }
 
@@ -2149,8 +3053,7 @@ proc findfiles {} {
     set finddidsel 0
     set findinsertpos end
     set id [lindex $displayorder $l]
-    . config -cursor watch
-    settextcursor watch
+    nowbusy find
     set findinprogress 1
     findcont
     update
@@ -2320,7 +3223,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} {
-    global ctext commitrow linknum
+    global ctext commitrow linknum curview
 
     set start [$ctext index "end - 1c"]
     $ctext insert end $text
@@ -2330,11 +3233,12 @@ proc appendwithlinks {text} {
        set s [lindex $l 0]
        set e [lindex $l 1]
        set linkid [string range $text $s $e]
-       if {![info exists commitrow($linkid)]} continue
+       if {![info exists commitrow($curview,$linkid)]} continue
        incr e
        $ctext tag add link "$start + $s c" "$start + $e c"
        $ctext tag add link$linknum "$start + $s c" "$start + $e c"
-       $ctext tag bind link$linknum <1> [list selectline $commitrow($linkid) 1]
+       $ctext tag bind link$linknum <1> \
+           [list selectline $commitrow($curview,$linkid) 1]
        incr linknum
     }
     $ctext tag conf link -foreground blue -underline 1
@@ -2362,10 +3266,12 @@ proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global displayorder linehtag linentag linedtag
     global canvy0 linespc parentlist childlist
-    global cflist currentid sha1entry
+    global currentid sha1entry
     global commentend idtags linknum
-    global mergemax numcommits
+    global mergemax numcommits pending_select
+    global cmitmode
 
+    catch {unset pending_select}
     $canv delete hover
     normalline
     if {$l < 0 || $l >= $numcommits} return
@@ -2435,8 +3341,6 @@ proc selectline {l isnew} {
     $ctext conf -state normal
     $ctext delete 0.0 end
     set linknum 0
-    $ctext mark set fmark.0 0.0
-    $ctext mark gravity fmark.0 left
     set info $commitinfo($id)
     set date [formatdate [lindex $info 2]]
     $ctext insert end "Author: [lindex $info 1]  $date\n"
@@ -2484,9 +3388,10 @@ proc selectline {l isnew} {
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
 
-    $cflist delete 0 end
-    $cflist insert end "Comments"
-    if {[llength $olds] <= 1} {
+    init_flist "Comments"
+    if {$cmitmode eq "tree"} {
+       gettree $id
+    } elseif {[llength $olds] <= 1} {
        startdiff $id
     } else {
        mergediff $id $l
@@ -2533,24 +3438,34 @@ proc selnextpage {dir} {
 }
 
 proc unselectline {} {
-    global selectedline
+    global selectedline currentid
 
     catch {unset selectedline}
+    catch {unset currentid}
     allcanvs delete secsel
 }
 
+proc reselectline {} {
+    global selectedline
+
+    if {[info exists selectedline]} {
+       selectline $selectedline 0
+    }
+}
+
 proc addtohistory {cmd} {
-    global history historyindex
+    global history historyindex curview
 
+    set elt [list $curview $cmd]
     if {$historyindex > 0
-       && [lindex $history [expr {$historyindex - 1}]] == $cmd} {
+       && [lindex $history [expr {$historyindex - 1}]] == $elt} {
        return
     }
 
     if {$historyindex < [llength $history]} {
-       set history [lreplace $history $historyindex end $cmd]
+       set history [lreplace $history $historyindex end $elt]
     } else {
-       lappend history $cmd
+       lappend history $elt
     }
     incr historyindex
     if {$historyindex > 1} {
@@ -2561,13 +3476,23 @@ proc addtohistory {cmd} {
     .ctop.top.bar.rightbut conf -state disabled
 }
 
+proc godo {elt} {
+    global curview
+
+    set view [lindex $elt 0]
+    set cmd [lindex $elt 1]
+    if {$curview != $view} {
+       showview $view
+    }
+    eval $cmd
+}
+
 proc goback {} {
     global history historyindex
 
     if {$historyindex > 1} {
        incr historyindex -1
-       set cmd [lindex $history [expr {$historyindex - 1}]]
-       eval $cmd
+       godo [lindex $history [expr {$historyindex - 1}]]
        .ctop.top.bar.rightbut conf -state normal
     }
     if {$historyindex <= 1} {
@@ -2581,7 +3506,7 @@ proc goforw {} {
     if {$historyindex < [llength $history]} {
        set cmd [lindex $history $historyindex]
        incr historyindex
-       eval $cmd
+       godo $cmd
        .ctop.top.bar.leftbut conf -state normal
     }
     if {$historyindex >= [llength $history]} {
@@ -2589,14 +3514,101 @@ proc goforw {} {
     }
 }
 
+proc gettree {id} {
+    global treefilelist treeidlist diffids diffmergeid treepending
+
+    set diffids $id
+    catch {unset diffmergeid}
+    if {![info exists treefilelist($id)]} {
+       if {![info exists treepending]} {
+           if {[catch {set gtf [open [concat | git-ls-tree -r $id] r]}]} {
+               return
+           }
+           set treepending $id
+           set treefilelist($id) {}
+           set treeidlist($id) {}
+           fconfigure $gtf -blocking 0
+           fileevent $gtf readable [list gettreeline $gtf $id]
+       }
+    } else {
+       setfilelist $id
+    }
+}
+
+proc gettreeline {gtf id} {
+    global treefilelist treeidlist treepending cmitmode diffids
+
+    while {[gets $gtf line] >= 0} {
+       if {[lindex $line 1] ne "blob"} continue
+       set sha1 [lindex $line 2]
+       set fname [lindex $line 3]
+       lappend treefilelist($id) $fname
+       lappend treeidlist($id) $sha1
+    }
+    if {![eof $gtf]} return
+    close $gtf
+    unset treepending
+    if {$cmitmode ne "tree"} {
+       if {![info exists diffmergeid]} {
+           gettreediffs $diffids
+       }
+    } elseif {$id ne $diffids} {
+       gettree $diffids
+    } else {
+       setfilelist $id
+    }
+}
+
+proc showfile {f} {
+    global treefilelist treeidlist diffids
+    global ctext commentend
+
+    set i [lsearch -exact $treefilelist($diffids) $f]
+    if {$i < 0} {
+       puts "oops, $f not in list for id $diffids"
+       return
+    }
+    set blob [lindex $treeidlist($diffids) $i]
+    if {[catch {set bf [open [concat | git-cat-file blob $blob] r]} err]} {
+       puts "oops, error reading blob $blob: $err"
+       return
+    }
+    fconfigure $bf -blocking 0
+    fileevent $bf readable [list getblobline $bf $diffids]
+    $ctext config -state normal
+    $ctext delete $commentend end
+    $ctext insert end "\n"
+    $ctext insert end "$f\n" filesep
+    $ctext config -state disabled
+    $ctext yview $commentend
+}
+
+proc getblobline {bf id} {
+    global diffids cmitmode ctext
+
+    if {$id ne $diffids || $cmitmode ne "tree"} {
+       catch {close $bf}
+       return
+    }
+    $ctext config -state normal
+    while {[gets $bf line] >= 0} {
+       $ctext insert end "$line\n"
+    }
+    if {[eof $bf]} {
+       # delete last newline
+       $ctext delete "end - 2c" "end - 1c"
+       close $bf
+    }
+    $ctext config -state disabled
+}
+
 proc mergediff {id l} {
     global diffmergeid diffopts mdifffd
-    global difffilestart diffids
+    global diffids
     global parentlist
 
     set diffmergeid $id
     set diffids $id
-    catch {unset difffilestart}
     # this doesn't seem to actually affect anything...
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id --cc $id]
@@ -2631,11 +3643,8 @@ proc getmergediffline {mdf id np} {
        # start of a new file
        $ctext insert end "\n"
        set here [$ctext index "end - 1c"]
-       set i [$cflist index end]
-       $ctext mark set fmark.$i $here
-       $ctext mark gravity fmark.$i left
-       set difffilestart([expr {$i-1}]) $here
-       $cflist insert end $fname
+       lappend difffilestart $here
+       add_flist [list $fname]
        set l [expr {(78 - [string length $fname]) / 2}]
        set pad [string range "----------------------------------------" 1 $l]
        $ctext insert end "$pad $fname $pad\n" filesep
@@ -2705,9 +3714,7 @@ proc startdiff {ids} {
 
 proc addtocflist {ids} {
     global treediffs cflist
-    foreach f $treediffs($ids) {
-       $cflist insert end $f
-    }
+    add_flist $treediffs($ids)
     getblobdiffs $ids
 }
 
@@ -2724,6 +3731,7 @@ proc gettreediffs {ids} {
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
+    global cmitmode
 
     set n [gets $gdtf line]
     if {$n < 0} {
@@ -2731,7 +3739,9 @@ proc gettreediffline {gdtf ids} {
        close $gdtf
        set treediffs($ids) $treediff
        unset treepending
-       if {$ids != $diffids} {
+       if {$cmitmode eq "tree"} {
+           gettree $diffids
+       } elseif {$ids != $diffids} {
            if {![info exists diffmergeid]} {
                gettreediffs $diffids
            }
@@ -2746,7 +3756,7 @@ proc gettreediffline {gdtf ids} {
 
 proc getblobdiffs {ids} {
     global diffopts blobdifffd diffids env curdifftag curtagstart
-    global difffilestart nextupdate diffinhdr treediffs
+    global nextupdate diffinhdr treediffs
 
     set env(GIT_DIFF_OPTS) $diffopts
     set cmd [concat | git-diff-tree --no-commit-id -r -p -C $ids]
@@ -2759,11 +3769,23 @@ proc getblobdiffs {ids} {
     set blobdifffd($ids) $bdf
     set curdifftag Comments
     set curtagstart 0.0
-    catch {unset difffilestart}
     fileevent $bdf readable [list getblobdiffline $bdf $diffids]
     set nextupdate [expr {[clock clicks -milliseconds] + 100}]
 }
 
+proc setinlist {var i val} {
+    global $var
+
+    while {[llength [set $var]] < $i} {
+       lappend $var {}
+    }
+    if {[llength [set $var]] == $i} {
+       lappend $var $val
+    } else {
+       lset $var $i $val
+    }
+}
+
 proc getblobdiffline {bdf ids} {
     global diffids blobdifffd ctext curdifftag curtagstart
     global diffnexthead diffnextnote difffilestart
@@ -2787,23 +3809,17 @@ proc getblobdiffline {bdf ids} {
        # start of a new file
        $ctext insert end "\n"
        $ctext tag add $curdifftag $curtagstart end
-       set curtagstart [$ctext index "end - 1c"]
-       set header $newname
        set here [$ctext index "end - 1c"]
-       set i [lsearch -exact $treediffs($diffids) $fname]
+       set curtagstart $here
+       set header $newname
+       set i [lsearch -exact $treediffs($ids) $fname]
        if {$i >= 0} {
-           set difffilestart($i) $here
-           incr i
-           $ctext mark set fmark.$i $here
-           $ctext mark gravity fmark.$i left
+           setinlist difffilestart $i $here
        }
-       if {$newname != $fname} {
-           set i [lsearch -exact $treediffs($diffids) $newname]
+       if {$newname ne $fname} {
+           set i [lsearch -exact $treediffs($ids) $newname]
            if {$i >= 0} {
-               set difffilestart($i) $here
-               incr i
-               $ctext mark set fmark.$i $here
-               $ctext mark gravity fmark.$i left
+               setinlist difffilestart $i $here
            }
        }
        set curdifftag "f:$fname"
@@ -2853,26 +3869,11 @@ proc getblobdiffline {bdf ids} {
 proc nextfile {} {
     global difffilestart ctext
     set here [$ctext index @0,0]
-    for {set i 0} {[info exists difffilestart($i)]} {incr i} {
-       if {[$ctext compare $difffilestart($i) > $here]} {
-           if {![info exists pos]
-               || [$ctext compare $difffilestart($i) < $pos]} {
-               set pos $difffilestart($i)
-           }
+    foreach loc $difffilestart {
+       if {[$ctext compare $loc > $here]} {
+           $ctext yview $loc
        }
     }
-    if {[info exists pos]} {
-       $ctext yview $pos
-    }
-}
-
-proc listboxsel {} {
-    global ctext cflist currentid
-    if {![info exists currentid]} return
-    set sel [lsort [$cflist curselection]]
-    if {$sel eq {}} return
-    set first [lindex $sel 0]
-    catch {$ctext yview fmark.$first}
 }
 
 proc setcoords {} {
@@ -2905,11 +3906,10 @@ proc redisplay {} {
 }
 
 proc incrfont {inc} {
-    global mainfont namefont textfont ctext canv phase
+    global mainfont textfont ctext canv phase
     global stopped entries
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
-    set namefont [lreplace $namefont 1 1 [expr {[lindex $namefont 1] + $inc}]]
     set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
     setcoords
     $ctext conf -font $textfont
@@ -2917,7 +3917,7 @@ proc incrfont {inc} {
     foreach e $entries {
        $e conf -font $mainfont
     }
-    if {$phase == "getcommits"} {
+    if {$phase eq "getcommits"} {
        $canv itemconf textitems -font $mainfont
     }
     redisplay
@@ -2948,7 +3948,7 @@ proc sha1change {n1 n2 op} {
 
 proc gotocommit {} {
     global sha1string currentid commitrow tagids headids
-    global displayorder numcommits
+    global displayorder numcommits curview
 
     if {$sha1string == {}
        || ([info exists currentid] && $sha1string == $currentid)} return
@@ -2974,8 +3974,8 @@ proc gotocommit {} {
            }
        }
     }
-    if {[info exists commitrow($id)]} {
-       selectline $commitrow($id) 1
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
        return
     }
     if {[regexp {^[0-9a-fA-F]{4,}$} $sha1string]} {
@@ -3050,12 +4050,13 @@ proc linehover {} {
 }
 
 proc clickisonarrow {id y} {
-    global lthickness idrowranges
+    global lthickness
 
+    set ranges [rowranges $id]
     set thresh [expr {2 * $lthickness + 6}]
-    set n [expr {[llength $idrowranges($id)] - 1}]
+    set n [expr {[llength $ranges] - 1}]
     for {set i 1} {$i < $n} {incr i} {
-       set row [lindex $idrowranges($id) $i]
+       set row [lindex $ranges $i]
        if {abs([yc $row] - $y) < $thresh} {
            return $i
        }
@@ -3064,11 +4065,11 @@ proc clickisonarrow {id y} {
 }
 
 proc arrowjump {id n y} {
-    global idrowranges canv
+    global canv
 
     # 1 <-> 2, 3 <-> 4, etc...
     set n [expr {(($n - 1) ^ 1) + 1}]
-    set row [lindex $idrowranges($id) $n]
+    set row [lindex [rowranges $id] $n]
     set yt [yc $row]
     set ymax [lindex [$canv cget -scrollregion] 3]
     if {$ymax eq {} || $ymax <= 0} return
@@ -3082,7 +4083,7 @@ proc arrowjump {id n y} {
 }
 
 proc lineclick {x y id isnew} {
-    global ctext commitinfo childlist commitrow cflist canv thickerline
+    global ctext commitinfo children canv thickerline curview
 
     if {![info exists commitinfo($id)] && ![getcommit $id]} return
     unmarkmatches
@@ -3121,7 +4122,7 @@ proc lineclick {x y id isnew} {
     $ctext insert end "\tAuthor:\t[lindex $info 1]\n"
     set date [formatdate [lindex $info 2]]
     $ctext insert end "\tDate:\t$date\n"
-    set kids [lindex $childlist $commitrow($id)]
+    set kids $children($curview,$id)
     if {$kids ne {}} {
        $ctext insert end "\nChildren:"
        set i 0
@@ -3139,8 +4140,7 @@ proc lineclick {x y id isnew} {
        }
     }
     $ctext conf -state disabled
-
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc normalline {} {
@@ -3153,9 +4153,9 @@ proc normalline {} {
 }
 
 proc selbyid {id} {
-    global commitrow
-    if {[info exists commitrow($id)]} {
-       selectline $commitrow($id) 1
+    global commitrow curview
+    if {[info exists commitrow($curview,$id)]} {
+       selectline $commitrow($curview,$id) 1
     }
 }
 
@@ -3168,9 +4168,10 @@ proc mstime {} {
 }
 
 proc rowmenu {x y id} {
-    global rowctxmenu commitrow selectedline rowmenuid
+    global rowctxmenu commitrow selectedline rowmenuid curview
 
-    if {![info exists selectedline] || $commitrow($id) eq $selectedline} {
+    if {![info exists selectedline]
+       || $commitrow($curview,$id) eq $selectedline} {
        set state disabled
     } else {
        set state normal
@@ -3198,15 +4199,12 @@ proc diffvssel {dirn} {
 }
 
 proc doseldiff {oldid newid} {
-    global ctext cflist
+    global ctext
     global commitinfo
 
     $ctext conf -state normal
     $ctext delete 0.0 end
-    $ctext mark set fmark.0 0.0
-    $ctext mark gravity fmark.0 left
-    $cflist delete 0 end
-    $cflist insert end "Top"
+    init_flist "Top"
     $ctext insert end "From "
     $ctext tag conf link -foreground blue -underline 1
     $ctext tag bind link <Enter> { %W configure -cursor hand2 }
@@ -3373,14 +4371,15 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag commitrow idpos selectedline
+    global canv linehtag commitrow idpos selectedline curview
 
-    if {![info exists commitrow($id)]} return
-    drawcmitrow $commitrow($id)
+    if {![info exists commitrow($curview,$id)]} return
+    drawcmitrow $commitrow($curview,$id)
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
-    $canv coords $linehtag($commitrow($id)) $xt [lindex $idpos($id) 2]
-    if {[info exists selectedline] && $selectedline == $commitrow($id)} {
+    $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
+    if {[info exists selectedline]
+       && $selectedline == $commitrow($curview,$id)} {
        selectline $selectedline 0
     }
 }
@@ -3492,7 +4491,7 @@ proc rereadrefs {} {
 }
 
 proc showtag {tag isnew} {
-    global ctext cflist tagcontents tagids linknum
+    global ctext tagcontents tagids linknum
 
     if {$isnew} {
        addtohistory [list showtag $tag 0]
@@ -3507,7 +4506,7 @@ proc showtag {tag isnew} {
     }
     appendwithlinks $text
     $ctext conf -state disabled
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc doquit {} {
@@ -3889,13 +4888,13 @@ set fastdate 0
 set uparrowlen 7
 set downarrowlen 7
 set mingaplen 30
+set flistmode "flat"
+set cmitmode "patch"
 
 set colors {green red blue magenta darkgrey brown orange}
 
 catch {source ~/.gitk}
 
-set namefont $mainfont
-
 font create optionfont -family sans-serif -size -12
 
 set revtreeargs {}
@@ -3912,19 +4911,77 @@ foreach arg $argv {
 # check that we can find a .git directory somewhere...
 set gitdir [gitdir]
 if {![file isdirectory $gitdir]} {
-    error_popup "Cannot find the git directory \"$gitdir\"."
+    show_error . "Cannot find the git directory \"$gitdir\"."
     exit 1
 }
 
+set cmdline_files {}
+set i [lsearch -exact $revtreeargs "--"]
+if {$i >= 0} {
+    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
+    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
+} elseif {$revtreeargs ne {}} {
+    if {[catch {
+       set f [eval exec git-rev-parse --no-revs --no-flags $revtreeargs]
+       set cmdline_files [split $f "\n"]
+       set n [llength $cmdline_files]
+       set revtreeargs [lrange $revtreeargs 0 end-$n]
+    } err]} {
+       # unfortunately we get both stdout and stderr in $err,
+       # so look for "fatal:".
+       set i [string first "fatal:" $err]
+       if {$i > 0} {
+           set err [string range [expr {$i + 6}] end]
+       }
+       show_error . "Bad arguments to gitk:\n$err"
+       exit 1
+    }
+}
+
 set history {}
 set historyindex 0
 
 set optim_delay 16
 
+set nextviewnum 1
+set curview 0
+set selectedview 0
+set selectedhlview {}
+set viewfiles(0) {}
+set viewperm(0) 0
+set viewargs(0) {}
+
+set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
 setcoords
-makewindow $revtreeargs
+makewindow
 readrefs
-getcommits $revtreeargs
+
+if {$cmdline_files ne {} || $revtreeargs ne {}} {
+    # create a view for the files/dirs specified on the command line
+    set curview 1
+    set selectedview 1
+    set nextviewnum 2
+    set viewname(1) "Command line"
+    set viewfiles(1) $cmdline_files
+    set viewargs(1) $revtreeargs
+    set viewperm(1) 0
+    addviewmenu 1
+    .bar.view entryconf 2 -state normal
+    .bar.view entryconf 3 -state normal
+}
+
+if {[info exists permviews]} {
+    foreach v $permviews {
+       set n $nextviewnum
+       incr nextviewnum
+       set viewname($n) [lindex $v 0]
+       set viewfiles($n) [lindex $v 1]
+       set viewargs($n) [lindex $v 2]
+       set viewperm($n) 1
+       addviewmenu $n
+    }
+}
+getcommits
index 9634c46..b90ba67 100644 (file)
@@ -3,6 +3,15 @@
 #include "commit.h"
 #include "log-tree.h"
 
+static void show_parents(struct commit *commit, int abbrev)
+{
+       struct commit_list *p;
+       for (p = commit->parents; p ; p = p->next) {
+               struct commit *parent = p->item;
+               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+       }
+}
+
 void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 {
        static char this_header[16384];
@@ -14,7 +23,10 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
-               puts(sha1_to_hex(commit->object.sha1));
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               putchar('\n');
                return;
        }
 
@@ -40,7 +52,9 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        printf("%s%s",
                opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
                diff_unique_abbrev(commit->object.sha1, abbrev_commit));
-       if (parent) 
+       if (opt->parents)
+               show_parents(commit, abbrev_commit);
+       if (parent)
                printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
        putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
 
index e4ef200..f2b3bc1 100644 (file)
--- a/ls-tree.c
+++ b/ls-tree.c
@@ -142,8 +142,8 @@ int main(int argc, const char **argv)
 
        if (argc < 2)
                usage(ls_tree_usage);
-       if (get_sha1(argv[1], sha1) < 0)
-               usage(ls_tree_usage);
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        pathspec = get_pathspec(prefix, argv + 2);
        tree = parse_tree_indirect(sha1);
index 07f5ab4..f0dc06e 100644 (file)
@@ -247,10 +247,12 @@ int main(int argc, char **argv)
                        usage(merge_base_usage);
                argc--; argv++;
        }
-       if (argc != 3 ||
-           get_sha1(argv[1], rev1key) ||
-           get_sha1(argv[2], rev2key))
+       if (argc != 3)
                usage(merge_base_usage);
+       if (get_sha1(argv[1], rev1key))
+               die("Not a valid object name %s", argv[1]);
+       if (get_sha1(argv[2], rev2key))
+               die("Not a valid object name %s", argv[2]);
        rev1 = lookup_commit_reference(rev1key);
        rev2 = lookup_commit_reference(rev2key);
        if (!rev1 || !rev2)
index 50528d5..9dcaab7 100644 (file)
@@ -24,16 +24,14 @@ static const char *sha1_to_hex_zero(const unsigned char *sha1)
 
 static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
 {
-       char branch1_sha1[50];
-
        /* If it's already branch1, don't bother showing it */
        if (!branch1)
                return;
-       memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
 
        printf("0 %06o->%06o %s->%s %s%s\n",
                branch1->mode, result->mode,
-               branch1_sha1, sha1_to_hex_zero(result->sha1),
+               sha1_to_hex_zero(branch1->sha1),
+               sha1_to_hex_zero(result->sha1),
                base, result->path);
 }
 
@@ -151,7 +149,7 @@ static void *get_tree_descriptor(struct tree_desc *desc, const char *rev)
        unsigned char sha1[20];
        void *buf;
 
-       if (get_sha1(rev, sha1) < 0)
+       if (get_sha1(rev, sha1))
                die("unknown rev %s", rev);
        buf = fill_tree_descriptor(desc, sha1);
        if (!buf)
index c0acc46..523a1c7 100644 (file)
@@ -994,6 +994,7 @@ static int type_size_sort(const struct object_entry *a, const struct object_entr
 struct unpacked {
        struct object_entry *entry;
        void *data;
+       struct delta_index *index;
 };
 
 /*
@@ -1004,64 +1005,56 @@ struct unpacked {
  * more importantly, the bigger file is likely the more recent
  * one.
  */
-static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_depth)
+static int try_delta(struct unpacked *trg, struct unpacked *src,
+                    struct delta_index *src_index, unsigned max_depth)
 {
-       struct object_entry *cur_entry = cur->entry;
-       struct object_entry *old_entry = old->entry;
-       unsigned long size, oldsize, delta_size, sizediff;
-       long max_size;
+       struct object_entry *trg_entry = trg->entry;
+       struct object_entry *src_entry = src->entry;
+       unsigned long size, src_size, delta_size, sizediff, max_size;
        void *delta_buf;
 
        /* Don't bother doing diffs between different types */
-       if (cur_entry->type != old_entry->type)
+       if (trg_entry->type != src_entry->type)
                return -1;
 
        /* We do not compute delta to *create* objects we are not
         * going to pack.
         */
-       if (cur_entry->preferred_base)
+       if (trg_entry->preferred_base)
                return -1;
 
-       /* If the current object is at pack edge, take the depth the
+       /*
+        * If the current object is at pack edge, take the depth the
         * objects that depend on the current object into account --
         * otherwise they would become too deep.
         */
-       if (cur_entry->delta_child) {
-               if (max_depth <= cur_entry->delta_limit)
+       if (trg_entry->delta_child) {
+               if (max_depth <= trg_entry->delta_limit)
                        return 0;
-               max_depth -= cur_entry->delta_limit;
+               max_depth -= trg_entry->delta_limit;
        }
-
-       size = cur_entry->size;
-       oldsize = old_entry->size;
-       sizediff = oldsize > size ? oldsize - size : size - oldsize;
-
-       if (size < 50)
-               return -1;
-       if (old_entry->depth >= max_depth)
+       if (src_entry->depth >= max_depth)
                return 0;
 
-       /*
-        * NOTE!
-        *
-        * We always delta from the bigger to the smaller, since that's
-        * more space-efficient (deletes don't have to say _what_ they
-        * delete).
-        */
+       /* Now some size filtering euristics. */
+       size = trg_entry->size;
        max_size = size / 2 - 20;
-       if (cur_entry->delta)
-               max_size = cur_entry->delta_size-1;
+       if (trg_entry->delta)
+               max_size = trg_entry->delta_size-1;
+       src_size = src_entry->size;
+       sizediff = src_size < size ? size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
-       delta_buf = diff_delta(old->data, oldsize,
-                              cur->data, size, &delta_size, max_size);
+
+       delta_buf = create_delta(src_index, trg->data, size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
-       cur_entry->delta = old_entry;
-       cur_entry->delta_size = delta_size;
-       cur_entry->depth = old_entry->depth + 1;
+
+       trg_entry->delta = src_entry;
+       trg_entry->delta_size = delta_size;
+       trg_entry->depth = src_entry->depth + 1;
        free(delta_buf);
-       return 0;
+       return 1;
 }
 
 static void progress_interval(int signum)
@@ -1109,11 +1102,19 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                         */
                        continue;
 
+               if (entry->size < 50)
+                       continue;
+               if (n->index)
+                       free_delta_index(n->index);
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
                if (size != entry->size)
-                       die("object %s inconsistent object length (%lu vs %lu)", sha1_to_hex(entry->sha1), size, entry->size);
+                       die("object %s inconsistent object length (%lu vs %lu)",
+                           sha1_to_hex(entry->sha1), size, entry->size);
+               n->index = create_delta_index(n->data, size);
+               if (!n->index)
+                       die("out of memory");
 
                j = window;
                while (--j > 0) {
@@ -1124,7 +1125,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, depth) < 0)
+                       if (try_delta(n, m, m->index, depth) < 0)
                                break;
                }
 #if 0
@@ -1144,8 +1145,11 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        if (progress)
                fputc('\n', stderr);
 
-       for (i = 0; i < window; ++i)
+       for (i = 0; i < window; ++i) {
+               if (array[i].index)
+                       free_delta_index(array[i].index);
                free(array[i].data);
+       }
        free(array);
 }
 
@@ -1239,6 +1243,7 @@ int main(int argc, char **argv)
 
        setup_git_directory();
 
+       progress = isatty(2);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
@@ -1269,6 +1274,10 @@ int main(int argc, char **argv)
                                        usage(pack_usage);
                                continue;
                        }
+                       if (!strcmp("--progress", arg)) {
+                               progress = 1;
+                               continue;
+                       }
                        if (!strcmp("-q", arg)) {
                                progress = 0;
                                continue;
index d95f0d9..8f318ed 100644 (file)
@@ -13,8 +13,8 @@
 #include <string.h>
 #include "delta.h"
 
-void *patch_delta(void *src_buf, unsigned long src_size,
-                 void *delta_buf, unsigned long delta_size,
+void *patch_delta(const void *src_buf, unsigned long src_size,
+                 const void *delta_buf, unsigned long delta_size,
                  unsigned long *dst_size)
 {
        const unsigned char *data, *top;
index f97f92d..a917ab0 100644 (file)
@@ -552,7 +552,7 @@ int read_cache(void)
 
        active_nr = ntohl(hdr->hdr_entries);
        active_alloc = alloc_nr(active_nr);
-       active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
+       active_cache = xcalloc(active_alloc, sizeof(struct cache_entry *));
 
        offset = sizeof(*hdr);
        for (i = 0; i < active_nr; i++) {
index 26f4f7e..e926e4c 100644 (file)
@@ -794,8 +794,8 @@ int main(int argc, char **argv)
                if (1 < index_only + update)
                        usage(read_tree_usage);
 
-               if (get_sha1(arg, sha1) < 0)
-                       usage(read_tree_usage);
+               if (get_sha1(arg, sha1))
+                       die("Not a valid object name %s", arg);
                if (list_tree(sha1) < 0)
                        die("failed to unpack tree object %s", arg);
                stage++;
diff --git a/refs.c b/refs.c
index 03398cc..275b914 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -76,8 +76,8 @@ int create_symref(const char *git_HEAD, const char *refs_heads_master)
        char ref[1000];
        int fd, len, written;
 
-#ifdef USE_SYMLINK_HEAD
-       if (!only_use_symrefs) {
+#ifndef NO_SYMLINK_HEAD
+       if (prefer_symlink_refs) {
                unlink(git_HEAD);
                if (!symlink(refs_heads_master, git_HEAD))
                        return 0;
index fa8aba7..63eda1b 100644 (file)
@@ -2,11 +2,13 @@
 #include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char* key = NULL;
-static char* value = NULL;
+static regex_t* key_regexp = NULL;
 static regex_t* regexp = NULL;
+static int show_keys = 0;
+static int use_key_regexp = 0;
 static int do_all = 0;
 static int do_not_match = 0;
 static int seen = 0;
@@ -23,34 +25,40 @@ static int show_all_config(const char *key_, const char *value_)
 
 static int show_config(const char* key_, const char* value_)
 {
+       char value[256];
+       const char *vptr = value;
+       int dup_error = 0;
+
        if (value_ == NULL)
                value_ = "";
 
-       if (!strcmp(key_, key) &&
-                       (regexp == NULL ||
+       if (!use_key_regexp && strcmp(key_, key))
+               return 0;
+       if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0))
+               return 0;
+       if (regexp != NULL &&
                         (do_not_match ^
-                         !regexec(regexp, value_, 0, NULL, 0)))) {
-               if (do_all) {
-                       printf("%s\n", value_);
-                       return 0;
-               }
-               if (seen > 0) {
-                       fprintf(stderr, "More than one value: %s\n", value);
-                       free(value);
-               }
+                         regexec(regexp, value_, 0, NULL, 0)))
+               return 0;
 
-               if (type == T_INT) {
-                       value = malloc(256);
-                       sprintf(value, "%d", git_config_int(key_, value_));
-               } else if (type == T_BOOL) {
-                       value = malloc(256);
-                       sprintf(value, "%s", git_config_bool(key_, value_)
-                                            ? "true" : "false");
-               } else {
-                       value = strdup(value_);
-               }
-               seen++;
+       if (show_keys)
+               printf("%s ", key_);
+       if (seen && !do_all)
+               dup_error = 1;
+       if (type == T_INT)
+               sprintf(value, "%d", git_config_int(key_, value_));
+       else if (type == T_BOOL)
+               vptr = git_config_bool(key_, value_) ? "true" : "false";
+       else
+               vptr = value_;
+       seen++;
+       if (dup_error) {
+               error("More than one value for the key %s: %s",
+                               key_, vptr);
        }
+       else
+               printf("%s\n", vptr);
+
        return 0;
 }
 
@@ -63,6 +71,14 @@ static int get_value(const char* key_, const char* regex_)
                key[i] = tolower(key_[i]);
        key[i] = 0;
 
+       if (use_key_regexp) {
+               key_regexp = (regex_t*)malloc(sizeof(regex_t));
+               if (regcomp(key_regexp, key, REG_EXTENDED)) {
+                       fprintf(stderr, "Invalid key pattern: %s\n", key_);
+                       return -1;
+               }
+       }
+
        if (regex_) {
                if (regex_[0] == '!') {
                        do_not_match = 1;
@@ -77,10 +93,6 @@ static int get_value(const char* key_, const char* regex_)
        }
 
        git_config(show_config);
-       if (value) {
-               printf("%s\n", value);
-               free(value);
-       }
        free(key);
        if (regexp) {
                regfree(regexp);
@@ -88,9 +100,9 @@ static int get_value(const char* key_, const char* regex_)
        }
 
        if (do_all)
-               return 0;
+               return !seen;
 
-       return seen == 1 ? 0 : 1;
+       return (seen == 1) ? 0 : 1;
 }
 
 int main(int argc, const char **argv)
@@ -102,15 +114,14 @@ int main(int argc, const char **argv)
                        type = T_INT;
                else if (!strcmp(argv[1], "--bool"))
                        type = T_BOOL;
+               else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
+                       return git_config(show_all_config);
                else
                        break;
                argc--;
                argv++;
        }
 
-       if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l"))
-               return git_config(show_all_config);
-
        switch (argc) {
        case 2:
                return get_value(argv[1], NULL);
@@ -124,6 +135,11 @@ int main(int argc, const char **argv)
                else if (!strcmp(argv[1], "--get-all")) {
                        do_all = 1;
                        return get_value(argv[2], NULL);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], NULL);
                } else
 
                        return git_config_set(argv[1], argv[2]);
@@ -137,6 +153,11 @@ int main(int argc, const char **argv)
                else if (!strcmp(argv[1], "--get-all")) {
                        do_all = 1;
                        return get_value(argv[2], argv[3]);
+               } else if (!strcmp(argv[1], "--get-regexp")) {
+                       show_keys = 1;
+                       use_key_regexp = 1;
+                       do_all = 1;
+                       return get_value(argv[2], argv[3]);
                } else if (!strcmp(argv[1], "--replace-all"))
 
                        return git_config_set_multivar(argv[2], argv[3], NULL, 1);
index f2a9f25..2294b16 100644 (file)
@@ -477,6 +477,36 @@ static void handle_all(struct rev_info *revs, unsigned flags)
        for_each_ref(handle_one_ref);
 }
 
+static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
+{
+       unsigned char sha1[20];
+       struct object *it;
+       struct commit *commit;
+       struct commit_list *parents;
+
+       if (*arg == '^') {
+               flags ^= UNINTERESTING;
+               arg++;
+       }
+       if (get_sha1(arg, sha1))
+               return 0;
+       while (1) {
+               it = get_reference(revs, arg, sha1, 0);
+               if (strcmp(it->type, tag_type))
+                       break;
+               memcpy(sha1, ((struct tag*)it)->tagged->sha1, 20);
+       }
+       if (strcmp(it->type, commit_type))
+               return 0;
+       commit = (struct commit *)it;
+       for (parents = commit->parents; parents; parents = parents->next) {
+               it = &parents->item->object;
+               it->flags |= flags;
+               add_pending_object(revs, it, arg);
+       }
+       return 1;
+}
+
 void init_revisions(struct rev_info *revs)
 {
        memset(revs, 0, sizeof(*revs));
@@ -544,7 +574,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
-                       /* accept -<digit>, like traditilnal "head" */
+                       /* accept -<digit>, like traditional "head" */
                        if ((*arg == '-') && isdigit(arg[1])) {
                                revs->max_count = atoi(arg + 1);
                                continue;
@@ -664,6 +694,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        }
                        if (!strcmp(arg, "-c")) {
                                revs->diff = 1;
+                               revs->dense_combined_merges = 0;
                                revs->combine_merges = 1;
                                continue;
                        }
@@ -740,37 +771,56 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                include = get_reference(revs, next, sha1, flags);
                                if (!exclude || !include)
                                        die("Invalid revision range %s..%s", arg, next);
+
+                               if (!seen_dashdash) {
+                                       *dotdot = '.';
+                                       verify_non_filename(revs->prefix, arg);
+                               }
                                add_pending_object(revs, exclude, this);
                                add_pending_object(revs, include, next);
                                continue;
                        }
                        *dotdot = '.';
                }
+               dotdot = strstr(arg, "^@");
+               if (dotdot && !dotdot[2]) {
+                       *dotdot = 0;
+                       if (add_parents_only(revs, arg, flags))
+                               continue;
+                       *dotdot = '^';
+               }
                local_flags = 0;
                if (*arg == '^') {
                        local_flags = UNINTERESTING;
                        arg++;
                }
-               if (get_sha1(arg, sha1) < 0) {
+               if (get_sha1(arg, sha1)) {
                        int j;
 
                        if (seen_dashdash || local_flags)
                                die("bad revision '%s'", arg);
 
-                       /* If we didn't have a "--", all filenames must exist */
+                       /* If we didn't have a "--":
+                        * (1) all filenames must exist;
+                        * (2) all rev-args must not be interpretable
+                        *     as a valid filename.
+                        * but the latter we have checked in the main loop.
+                        */
                        for (j = i; j < argc; j++)
                                verify_filename(revs->prefix, argv[j]);
 
                        revs->prune_data = get_pathspec(revs->prefix, argv + i);
                        break;
                }
+               if (!seen_dashdash)
+                       verify_non_filename(revs->prefix, arg);
                object = get_reference(revs, arg, sha1, flags ^ local_flags);
                add_pending_object(revs, object, arg);
        }
        if (def && !revs->pending_objects) {
                unsigned char sha1[20];
                struct object *object;
-               if (get_sha1(def, sha1) < 0)
+               if (get_sha1(def, sha1))
                        die("bad default revision '%s'", def);
                object = get_reference(revs, def, sha1, 0);
                add_pending_object(revs, object, def);
diff --git a/setup.c b/setup.c
index cce9bb8..fe7f884 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -80,11 +80,31 @@ void verify_filename(const char *prefix, const char *arg)
        if (!lstat(name, &st))
                return;
        if (errno == ENOENT)
-               die("ambiguous argument '%s': unknown revision or filename\n"
-                   "Use '--' to separate filenames from revisions", arg);
+               die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+                   "Use '--' to separate paths from revisions", arg);
        die("'%s': %s", arg, strerror(errno));
 }
 
+/*
+ * Opposite of the above: the command line did not have -- marker
+ * and we parsed the arg as a refname.  It should not be interpretable
+ * as a filename.
+ */
+void verify_non_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               return; /* flag */
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               die("ambiguous argument '%s': both revision and filename\n"
+                   "Use '--' to separate filenames from revisions", arg);
+       if (errno != ENOENT)
+               die("'%s': %s", arg, strerror(errno));
+}
+
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
index f2d33af..5464828 100644 (file)
@@ -108,9 +108,10 @@ int safe_create_leading_directories(char *path)
 
 char * sha1_to_hex(const unsigned char *sha1)
 {
-       static char buffer[50];
+       static int bufno;
+       static char hexbuffer[4][50];
        static const char hex[] = "0123456789abcdef";
-       char *buf = buffer;
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
        int i;
 
        for (i = 0; i < 20; i++) {
index 24efb65..268c57b 100644 (file)
@@ -5,7 +5,7 @@
 #include "refs.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+"git-show-branch [--dense] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
 
 static int default_num = 0;
 static int default_alloc = 0;
@@ -527,6 +527,27 @@ static int git_show_branch_config(const char *var, const char *value)
        return git_default_config(var, value);
 }
 
+static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
+{
+       /* If the commit is tip of the named branches, do not
+        * omit it.
+        * Otherwise, if it is a merge that is reachable from only one
+        * tip, it is not that interesting.
+        */
+       int i, flag, count;
+       for (i = 0; i < n; i++)
+               if (rev[i] == commit)
+                       return 0;
+       flag = commit->object.flags;
+       for (i = count = 0; i < n; i++) {
+               if (flag & (1u << (i + REV_SHIFT)))
+                       count++;
+       }
+       if (count == 1)
+               return 1;
+       return 0;
+}
+
 int main(int ac, char **av)
 {
        struct commit *rev[MAX_REVS], *commit;
@@ -548,6 +569,7 @@ int main(int ac, char **av)
        int with_current_branch = 0;
        int head_at = -1;
        int topics = 0;
+       int dense = 1;
 
        setup_git_directory();
        git_config(git_show_branch_config);
@@ -590,6 +612,8 @@ int main(int ac, char **av)
                        lifo = 1;
                else if (!strcmp(arg, "--topics"))
                        topics = 1;
+               else if (!strcmp(arg, "--sparse"))
+                       dense = 0;
                else if (!strcmp(arg, "--date-order"))
                        lifo = 0;
                else
@@ -732,12 +756,15 @@ int main(int ac, char **av)
                shown_merge_point |= is_merge_point;
 
                if (1 < num_rev) {
-                       int is_merge = !!(commit->parents && commit->parents->next);
+                       int is_merge = !!(commit->parents &&
+                                         commit->parents->next);
                        if (topics &&
                            !is_merge_point &&
                            (this_flag & (1u << REV_SHIFT)))
                                continue;
-
+                       if (dense && is_merge &&
+                           omit_in_dense(commit, rev, num_rev))
+                               continue;
                        for (i = 0; i < num_rev; i++) {
                                int mark;
                                if (!(this_flag & (1u << (i + REV_SHIFT))))
index b675a0b..2da6661 100644 (file)
@@ -134,7 +134,7 @@ int main(int argc, char **argv)
        commit_id = argv[arg];
        url = argv[arg + 1];
        if (get_sha1(commit_id, sha1))
-               usage(ssh_push_usage);
+               die("Not a valid object name %s", commit_id);
        memcpy(hex, sha1_to_hex(sha1), sizeof(hex));
        argv[arg] = hex;
 
index ab4dd5c..7090ea9 100755 (executable)
@@ -247,6 +247,24 @@ EOF
 
 test_expect_success 'hierarchical section value' 'cmp .git/config expect'
 
+cat > expect << EOF
+beta.noindent=sillyValue
+nextsection.nonewline=wow2 for me
+123456.a123=987
+1.2.3.alpha=beta
+EOF
+
+test_expect_success 'working --list' \
+       'git-repo-config --list > output && cmp output expect'
+
+cat > expect << EOF
+beta.noindent sillyValue
+nextsection.nonewline wow2 for me
+EOF
+
+test_expect_success '--get-regexp' \
+       'git-repo-config --get-regexp in > output && cmp output expect'
+
 cat > .git/config << EOF
 [novalue]
        variable
@@ -255,5 +273,41 @@ EOF
 test_expect_success 'get variable with no value' \
        'git-repo-config --get novalue.variable ^$'
 
+git-repo-config > output 2>&1
+
+test_expect_success 'no arguments, but no crash' \
+       "test $? = 129 && grep usage output"
+
+cat > .git/config << EOF
+[a.b]
+       c = d
+EOF
+
+git-repo-config a.x y
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+EOF
+
+test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
+
+git-repo-config b.x y
+git-repo-config a.b c
+
+cat > expect << EOF
+[a.b]
+       c = d
+[a]
+       x = y
+       b = c
+[b]
+       x = y
+EOF
+
+test_expect_success 'new variable inserts into proper section' 'cmp .git/config expect'
+
 test_done
 
diff --git a/t/t2101-update-index-reupdate.sh b/t/t2101-update-index-reupdate.sh
new file mode 100755 (executable)
index 0000000..77aed8d
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='git-update-index --again test.
+'
+
+. ./test-lib.sh
+
+test_expect_success 'update-index --add' \
+       'echo hello world >file1 &&
+        echo goodbye people >file2 &&
+        git-update-index --add file1 file2 &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF'
+
+test_expect_success 'update-index --again' \
+       'rm -f file1 &&
+       echo hello everybody >file2 &&
+       if git-update-index --again
+       then
+               echo should have refused to remove file1
+               exit 1
+       else
+               echo happy - failed as expected
+       fi &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0      file1
+100644 9db8893856a8a02eaa73470054b7c1c5a7c82e47 0      file2
+EOF'
+
+test_expect_success 'update-index --remove --again' \
+       'git-update-index --remove --again &&
+        git-ls-files -s >current &&
+        cmp current - <<\EOF
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'first commit' 'git-commit -m initial'
+
+test_expect_success 'update-index again' \
+       'mkdir -p dir1 &&
+       echo hello world >dir1/file3 &&
+       echo goodbye people >file2 &&
+       git-update-index --add file2 dir1/file3 &&
+       echo hello everybody >file2
+       echo happy >dir1/file3 &&
+       git-update-index --again &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 53ab446c3f4e42ce9bb728a0ccb283a101be4979 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'update-index --update from subdir' \
+       'echo not so happy >file2 &&
+       cd dir1 &&
+       cat ../file2 >file3 &&
+       git-update-index --again &&
+       cd .. &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 d7fb3f695f06c759dbf3ab00046e7cc2da22d10f 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_expect_success 'update-index --update with pathspec' \
+       'echo very happy >file2 &&
+       cat file2 >dir1/file3 &&
+       git-update-index --again dir1/ &&
+       git-ls-files -s >current &&
+       cmp current - <<\EOF
+100644 594fb5bb1759d90998e2bf2a38261ae8e243c760 0      dir1/file3
+100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
+EOF'
+
+test_done
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh
new file mode 100755 (executable)
index 0000000..bdd95c0
--- /dev/null
@@ -0,0 +1,85 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='Binary diff and apply
+'
+
+. ./test-lib.sh
+
+test_expect_success 'prepare repository' \
+       'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
+        git-update-index --add a b c d &&
+        echo git >a &&
+        cat ../test4012.png >b &&
+        echo git >c &&
+        cat b b >d'
+
+test_expect_success 'diff without --binary' \
+       'git-diff | git-apply --stat --summary >current &&
+        cmp current - <<\EOF
+ a |    2 +-
+ b |  Bin
+ c |    2 +-
+ d |  Bin
+ 4 files changed, 2 insertions(+), 2 deletions(-)
+EOF'
+
+test_expect_success 'diff with --binary' \
+       'git-diff --binary | git-apply --stat --summary >current &&
+        cmp current - <<\EOF
+ a |    2 +-
+ b |  Bin
+ c |    2 +-
+ d |  Bin
+ 4 files changed, 2 insertions(+), 2 deletions(-)
+EOF'
+
+# apply needs to be able to skip the binary material correctly
+# in order to report the line number of a corrupt patch.
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'apply detecting corrupt patch correctly' \
+       'git-diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
+        if git-apply --stat --summary broken 2>detected
+        then
+               echo unhappy - should have detected an error
+               (exit 1)
+        else
+               echo happy
+        fi &&
+        detected=`cat detected` &&
+        detected=`expr "$detected" : "fatal.*at line \\([0-9]*\\)\$"` &&
+        detected=`sed -ne "${detected}p" broken` &&
+        test "$detected" = xCIT'
+
+test_expect_success 'initial commit' 'git-commit -a -m initial'
+
+# Try removal (b), modification (d), and creation (e).
+test_expect_success 'diff-index with --binary' \
+       'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
+        git-update-index --add --remove a b c d e &&
+        tree0=`git-write-tree` &&
+        git-diff --cached --binary >current &&
+        git-apply --stat --summary current'
+
+test_expect_success 'apply binary patch' \
+       'git-reset --hard &&
+        git-apply --binary --index <current &&
+        tree1=`git-write-tree` &&
+        test "$tree1" = "$tree0"'
+
+test_done
diff --git a/t/t5700-clone-reference.sh b/t/t5700-clone-reference.sh
new file mode 100755 (executable)
index 0000000..916ee15
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=`pwd`
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo first > file1 &&
+git add file1 &&
+git commit -m initial'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone A B && cd B &&
+echo second > file2 &&
+git add file2 &&
+git commit -m addition &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'cloning with reference' \
+'git clone -l -s --reference B A C'
+
+cd "$base_dir"
+
+test_expect_success 'existance of info/alternates' \
+'test `wc -l <C/.git/objects/info/alternates` = 2'
+
+cd "$base_dir"
+
+test_expect_success 'pulling from reference' \
+'cd C &&
+git pull ../B'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used' \
+'cd C &&
+echo "0 objects, 0 kilobytes" > expected &&
+git count-objects > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_expect_success 'updating origin' \
+'cd A &&
+echo third > file3 &&
+git add file3 &&
+git commit -m update &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'pulling changes from origin' \
+'cd C &&
+git pull origin'
+
+cd "$base_dir"
+
+# the 2 local objects are commit and tree from the merge
+test_expect_success 'that alternate to origin gets used' \
+'cd C &&
+echo "2 objects" > expected &&
+git count-objects | cut -d, -f1 > current &&
+diff expected current'
+
+cd "$base_dir"
+
+test_done
diff --git a/t/test4012.png b/t/test4012.png
new file mode 100644 (file)
index 0000000..7b181d1
Binary files /dev/null and b/t/test4012.png differ
index fc60a90..3308736 100644 (file)
@@ -321,8 +321,8 @@ int main(int argc, char **argv)
                strbuf_append_string(&current_path, "/");
                /* FALLTHROUGH */
        case 2:
-               if (get_sha1(argv[1], sha1) < 0)
-                       usage(tar_tree_usage);
+               if (get_sha1(argv[1], sha1))
+                       die("Not a valid object name %s", argv[1]);
                break;
        default:
                usage(tar_tree_usage);
index 23a8562..ccddf1d 100644 (file)
@@ -27,8 +27,10 @@ int main(int argc, char **argv)
 {
        unsigned char sha1[20];
 
-       if (argc != 2 || get_sha1(argv[1], sha1))
+       if (argc != 2)
                usage("git-unpack-file <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
 
        setup_git_directory();
        git_config(git_default_config);
index facec8d..3d7e02d 100644 (file)
@@ -364,23 +364,26 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
        const char *p = prefix_path(prefix, prefix_length, path);
        if (!verify_path(p)) {
                fprintf(stderr, "Ignoring path %s\n", path);
-               return;
+               goto free_return;
        }
        if (mark_valid_only) {
                if (mark_valid(p))
                        die("Unable to mark file %s", path);
-               return;
+               goto free_return;
        }
 
        if (force_remove) {
                if (remove_file_from_cache(p))
                        die("git-update-index: unable to remove %s", path);
                report("remove '%s'", path);
-               return;
+               goto free_return;
        }
        if (add_file_to_cache(p))
                die("Unable to process file %s", path);
        report("add '%s'", path);
+ free_return:
+       if (p < path || p > path + strlen(path))
+               free((char*)p);
 }
 
 static void read_index_info(int line_termination)
@@ -473,7 +476,7 @@ static void read_index_info(int line_termination)
 }
 
 static const char update_index_usage[] =
-"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--again] [--ignore-missing] [-z] [--verbose] [--] <file>...";
 
 static unsigned char head_sha1[20];
 static unsigned char merge_head_sha1[20];
@@ -488,11 +491,13 @@ static struct cache_entry *read_one_ent(const char *which,
        struct cache_entry *ce;
 
        if (get_tree_entry(ent, path, sha1, &mode)) {
-               error("%s: not in %s branch.", path, which);
+               if (which)
+                       error("%s: not in %s branch.", path, which);
                return NULL;
        }
        if (mode == S_IFDIR) {
-               error("%s: not a blob in %s branch.", path, which);
+               if (which)
+                       error("%s: not a blob in %s branch.", path, which);
                return NULL;
        }
        size = cache_entry_size(namelen);
@@ -576,7 +581,8 @@ static void read_head_pointers(void)
        }
 }
 
-static int do_unresolve(int ac, const char **av)
+static int do_unresolve(int ac, const char **av,
+                       const char *prefix, int prefix_length)
 {
        int i;
        int err = 0;
@@ -588,11 +594,57 @@ static int do_unresolve(int ac, const char **av)
 
        for (i = 1; i < ac; i++) {
                const char *arg = av[i];
-               err |= unresolve_one(arg);
+               const char *p = prefix_path(prefix, prefix_length, arg);
+               err |= unresolve_one(p);
+               if (p < arg || p > arg + strlen(arg))
+                       free((char*)p);
        }
        return err;
 }
 
+static int do_reupdate(int ac, const char **av,
+                      const char *prefix, int prefix_length)
+{
+       /* Read HEAD and run update-index on paths that are
+        * merged and already different between index and HEAD.
+        */
+       int pos;
+       int has_head = 1;
+       const char **pathspec = get_pathspec(prefix, av + 1);
+
+       if (read_ref(git_path("HEAD"), head_sha1))
+               /* If there is no HEAD, that means it is an initial
+                * commit.  Update everything in the index.
+                */
+               has_head = 0;
+ redo:
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               struct cache_entry *old = NULL;
+               int save_nr;
+
+               if (ce_stage(ce) || !ce_path_match(ce, pathspec))
+                       continue;
+               if (has_head)
+                       old = read_one_ent(NULL, head_sha1,
+                                          ce->name, ce_namelen(ce), 0);
+               if (old && ce->ce_mode == old->ce_mode &&
+                   !memcmp(ce->sha1, old->sha1, 20)) {
+                       free(old);
+                       continue; /* unchanged */
+               }
+               /* Be careful.  The working tree may not have the
+                * path anymore, in which case, under 'allow_remove',
+                * or worse yet 'allow_replace', active_nr may decrease.
+                */
+               save_nr = active_nr;
+               update_one(ce->name + prefix_length, prefix, prefix_length);
+               if (save_nr != active_nr)
+                       goto redo;
+       }
+       return 0;
+}
+
 int main(int argc, const char **argv)
 {
        int i, newfd, entries, has_errors = 0, line_termination = '\n';
@@ -704,7 +756,15 @@ int main(int argc, const char **argv)
                                break;
                        }
                        if (!strcmp(path, "--unresolve")) {
-                               has_errors = do_unresolve(argc - i, argv + i);
+                               has_errors = do_unresolve(argc - i, argv + i,
+                                                         prefix, prefix_length);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
+                       if (!strcmp(path, "--again")) {
+                               has_errors = do_reupdate(argc - i, argv + i,
+                                                        prefix, prefix_length);
                                if (has_errors)
                                        active_cache_changed = 0;
                                goto finish;
@@ -730,6 +790,7 @@ int main(int argc, const char **argv)
                strbuf_init(&buf);
                while (1) {
                        char *path_name;
+                       const char *p;
                        read_line(&buf, stdin, line_termination);
                        if (buf.eof)
                                break;
@@ -737,11 +798,12 @@ int main(int argc, const char **argv)
                                path_name = unquote_c_style(buf.buf, NULL);
                        else
                                path_name = buf.buf;
-                       update_one(path_name, prefix, prefix_length);
-                       if (set_executable_bit) {
-                               const char *p = prefix_path(prefix, prefix_length, path_name);
+                       p = prefix_path(prefix, prefix_length, path_name);
+                       update_one(p, NULL, 0);
+                       if (set_executable_bit)
                                chmod_path(set_executable_bit, p);
-                       }
+                       if (p < path_name || p > path_name + strlen(path_name))
+                               free((char*) p);
                        if (path_name != buf.buf)
                                free(path_name);
                }
index ba4bf51..fd48742 100644 (file)
@@ -32,10 +32,10 @@ int main(int argc, char **argv)
        refname = argv[1];
        value = argv[2];
        oldval = argv[3];
-       if (get_sha1(value, sha1) < 0)
+       if (get_sha1(value, sha1))
                die("%s: not a valid SHA1", value);
        memset(oldsha1, 0, 20);
-       if (oldval && get_sha1(oldval, oldsha1) < 0)
+       if (oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
        path = resolve_ref(git_path("%s", refname), currsha1, !!oldval);