Merge branch 'jc/grep' into next
authorJunio C Hamano <junkio@cox.net>
Wed, 3 May 2006 05:04:16 +0000 (22:04 -0700)
committerJunio C Hamano <junkio@cox.net>
Wed, 3 May 2006 05:04:16 +0000 (22:04 -0700)
* jc/grep:
builtin-grep: tighten path wildcard vs tree traversal.

47 files changed:
.gitignore
Documentation/git-count-objects.txt
Documentation/git-rebase.txt
Documentation/git-repo-config.txt
Makefile
apply.c
builtin-count.c [new file with mode: 0644]
builtin-diff.c [new file with mode: 0644]
builtin-log.c
builtin-push.c [new file with mode: 0644]
builtin.h
cache-tree.c [new file with mode: 0644]
cache-tree.h [new file with mode: 0644]
cache.h
checkout-index.c
combine-diff.c
commit.c
commit.h
config.c
date.c
delta.h
diff-delta.c
diff.h
dump-cache-tree.c [new file with mode: 0644]
fsck-objects.c
git-am.sh
git-count-objects.sh [deleted file]
git-diff.sh [deleted file]
git-fetch.sh
git-rebase.sh
git-send-email.perl
git-whatchanged.sh [deleted file]
git.c
gitk
log-tree.c
pack-objects.c
patch-delta.c
read-cache.c
read-tree.c
repo-config.c
revision.c
setup.c
sha1_name.c
show-branch.c
t/t1300-repo-config.sh
update-index.c
write-tree.c
index b5959d63116aa1e3e900f43a137295b47376e1e0..7906909b306f80ed74d6e53bb8514a889057194c 100644 (file)
@@ -123,6 +123,7 @@ git-write-tree
 git-core-*/?*
 test-date
 test-delta
+test-dump-cache-tree
 common-cmds.h
 *.tar.gz
 *.dsc
index 47216f488b444e2d9f37c302a71b9ac496b928a5..198ce77a8a541ba33855bf5236b4791e2b395557 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 4a7e67a4d2241a82a564734d64789e6ee13c8330..1b482abecdc2e953db1f29cb3c531f665ea8011c 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 566cfa183640a629ee6dcfca714169ec48f28948..ddcf52364ca0d096eef99b11621d5e7f819502bd 100644 (file)
@@ -49,7 +49,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 +59,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 8d5122bdd518e6d85d15304f7e7638b84c9b00e7..237433519c259894da98d25a7b3a50a3f70ead0c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -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 cache-tree.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,8 @@ LIB_OBJS = \
        $(DIFF_OBJS)
 
 BUILTIN_OBJS = \
-       builtin-log.o builtin-help.o builtin-grep.o
+       builtin-log.o builtin-help.o builtin-count.o builtin-diff.o \
+       builtin-push.o builtin-grep.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
@@ -505,9 +507,6 @@ $(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
 git-cherry-pick: git-revert
        cp $< $@
 
-git-show: git-whatchanged
-       cp $< $@
-
 git-status: git-commit
        cp $< $@
 
@@ -609,7 +608,10 @@ 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) $^
+
+test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
 check:
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
diff --git a/apply.c b/apply.c
index 269210a578262b22fbf50bbdd9bf9fbccec3202b..acecf8de54e0446c16da2291de6d90b4606f3287 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -8,6 +8,7 @@
  */
 #include <fnmatch.h>
 #include "cache.h"
+#include "cache-tree.h"
 #include "quote.h"
 #include "blob.h"
 
@@ -1717,6 +1718,7 @@ static void remove_file(struct patch *patch)
        if (write_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        die("unable to remove %s from index", patch->old_name);
+               cache_tree_invalidate_path(active_cache_tree, patch->old_name);
        }
        unlink(patch->old_name);
 }
@@ -1813,8 +1815,9 @@ static void create_file(struct patch *patch)
 
        if (!mode)
                mode = S_IFREG | 0644;
-       create_one_file(path, mode, buf, size); 
+       create_one_file(path, mode, buf, size);
        add_index_file(path, mode, buf, size);
+       cache_tree_invalidate_path(active_cache_tree, path);
 }
 
 static void write_out_one_result(struct patch *patch)
diff --git a/builtin-count.c b/builtin-count.c
new file mode 100644 (file)
index 0000000..0256369
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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;
+               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..b6114ce
--- /dev/null
@@ -0,0 +1,369 @@
+/*
+ * 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
+                       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);
+}
index 69f2911cb4739ba211ac692ded2aa0a6d31cb989..a39aed6d86d77026c6178556ee59a05981d70776 100644 (file)
@@ -9,6 +9,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "log-tree.h"
+#include "builtin.h"
 
 static int cmd_log_wc(int argc, const char **argv, char **envp,
                      struct rev_info *rev)
@@ -67,3 +68,41 @@ int cmd_log(int argc, const char **argv, char **envp)
        rev.diffopt.recursive = 1;
        return cmd_log_wc(argc, argv, envp, &rev);
 }
+
+int cmd_format_patch(int argc, const char **argv, char **envp)
+{
+       struct commit *commit;
+       struct commit **list = NULL;
+       struct rev_info rev;
+       int nr = 0;
+
+       init_revisions(&rev);
+       rev.commit_format = CMIT_FMT_EMAIL;
+       rev.verbose_header = 1;
+       rev.diff = 1;
+       rev.diffopt.with_raw = 0;
+       rev.diffopt.with_stat = 1;
+       rev.combine_merges = 0;
+       rev.ignore_merges = 1;
+       rev.diffopt.output_format = DIFF_FORMAT_PATCH;
+       argc = setup_revisions(argc, argv, &rev, "HEAD");
+
+       prepare_revision_walk(&rev);
+       while ((commit = get_revision(&rev)) != NULL) {
+               nr++;
+               list = realloc(list, nr * sizeof(list[0]));
+               list[nr - 1] = commit;
+       }
+       while (0 <= --nr) {
+               int shown;
+               commit = list[nr];
+               shown = log_tree_commit(&rev, commit);
+               free(commit->buffer);
+               commit->buffer = NULL;
+               if (shown)
+                       printf("-- \n%s\n\n", git_version_string);
+       }
+       free(list);
+       return 0;
+}
+
diff --git a/builtin-push.c b/builtin-push.c
new file mode 100644 (file)
index 0000000..06d06ff
--- /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;
+
+       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;
+
+       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 cf5de3b9311569293399f2abf38417711b9ff7ce..7f06812b8674fdfe5f54d901677320d7f8717a97 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -19,6 +19,10 @@ 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_format_patch(int argc, const char **argv, char **envp);
+extern int cmd_count_objects(int argc, const char **argv, char **envp);
+extern int cmd_diff(int argc, const char **argv, char **envp);
+extern int cmd_push(int argc, const char **argv, char **envp);
 extern int cmd_grep(int argc, const char **argv, char **envp);
 
 #endif
diff --git a/cache-tree.c b/cache-tree.c
new file mode 100644 (file)
index 0000000..e452238
--- /dev/null
@@ -0,0 +1,527 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+#define DEBUG 0
+
+struct cache_tree *cache_tree(void)
+{
+       struct cache_tree *it = xcalloc(1, sizeof(struct cache_tree));
+       it->entry_count = -1;
+       return it;
+}
+
+void cache_tree_free(struct cache_tree **it_p)
+{
+       int i;
+       struct cache_tree *it = *it_p;
+
+       if (!it)
+               return;
+       for (i = 0; i < it->subtree_nr; i++)
+               if (it->down[i])
+                       cache_tree_free(&it->down[i]->cache_tree);
+       free(it->down);
+       free(it);
+       *it_p = NULL;
+}
+
+static int subtree_name_cmp(const char *one, int onelen,
+                           const char *two, int twolen)
+{
+       if (onelen < twolen)
+               return -1;
+       if (twolen < onelen)
+               return 1;
+       return memcmp(one, two, onelen);
+}
+
+static int subtree_pos(struct cache_tree *it, const char *path, int pathlen)
+{
+       struct cache_tree_sub **down = it->down;
+       int lo, hi;
+       lo = 0;
+       hi = it->subtree_nr;
+       while (lo < hi) {
+               int mi = (lo + hi) / 2;
+               struct cache_tree_sub *mdl = down[mi];
+               int cmp = subtree_name_cmp(path, pathlen,
+                                          mdl->name, mdl->namelen);
+               if (!cmp)
+                       return mi;
+               if (cmp < 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+       }
+       return -lo-1;
+}
+
+static struct cache_tree_sub *find_subtree(struct cache_tree *it,
+                                          const char *path,
+                                          int pathlen,
+                                          int create)
+{
+       struct cache_tree_sub *down;
+       int pos = subtree_pos(it, path, pathlen);
+       if (0 <= pos)
+               return it->down[pos];
+       if (!create)
+               return NULL;
+
+       pos = -pos-1;
+       if (it->subtree_alloc <= it->subtree_nr) {
+               it->subtree_alloc = alloc_nr(it->subtree_alloc);
+               it->down = xrealloc(it->down, it->subtree_alloc *
+                                   sizeof(*it->down));
+       }
+       it->subtree_nr++;
+
+       down = xmalloc(sizeof(*down) + pathlen + 1);
+       down->cache_tree = NULL;
+       down->namelen = pathlen;
+       memcpy(down->name, path, pathlen);
+       down->name[pathlen] = 0;
+
+       if (pos < it->subtree_nr)
+               memmove(it->down + pos + 1,
+                       it->down + pos,
+                       sizeof(down) * (it->subtree_nr - pos - 1));
+       it->down[pos] = down;
+       return down;
+}
+
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *it, const char *path)
+{
+       int pathlen = strlen(path);
+       return find_subtree(it, path, pathlen, 1);
+}
+
+void cache_tree_invalidate_path(struct cache_tree *it, const char *path)
+{
+       /* a/b/c
+        * ==> invalidate self
+        * ==> find "a", have it invalidate "b/c"
+        * a
+        * ==> invalidate self
+        * ==> if "a" exists as a subtree, remove it.
+        */
+       const char *slash;
+       int namelen;
+       struct cache_tree_sub *down;
+
+       if (!it)
+               return;
+       slash = strchr(path, '/');
+       it->entry_count = -1;
+       if (!slash) {
+               int pos;
+               namelen = strlen(path);
+               pos = subtree_pos(it, path, namelen);
+               if (0 <= pos) {
+                       cache_tree_free(&it->down[pos]->cache_tree);
+                       free(it->down[pos]);
+                       /* 0 1 2 3 4 5
+                        *       ^     ^subtree_nr = 6
+                        *       pos
+                        * move 4 and 5 up one place (2 entries)
+                        * 2 = 6 - 3 - 1 = subtree_nr - pos - 1
+                        */
+                       memmove(it->down+pos, it->down+pos+1,
+                               sizeof(struct cache_tree_sub *) *
+                               (it->subtree_nr - pos - 1));
+                       it->subtree_nr--;
+               }
+               return;
+       }
+       namelen = slash - path;
+       down = find_subtree(it, path, namelen, 0);
+       if (down)
+               cache_tree_invalidate_path(down->cache_tree, slash + 1);
+}
+
+static int verify_cache(struct cache_entry **cache,
+                       int entries)
+{
+       int i, funny;
+
+       /* Verify that the tree is merged */
+       funny = 0;
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               if (ce_stage(ce)) {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "%s: unmerged (%s)\n",
+                               ce->name, sha1_to_hex(ce->sha1));
+               }
+       }
+       if (funny)
+               return -1;
+
+       /* Also verify that the cache does not have path and path/file
+        * at the same time.  At this point we know the cache has only
+        * stage 0 entries.
+        */
+       funny = 0;
+       for (i = 0; i < entries - 1; i++) {
+               /* path/file always comes after path because of the way
+                * the cache is sorted.  Also path can appear only once,
+                * which means conflicting one would immediately follow.
+                */
+               const char *this_name = cache[i]->name;
+               const char *next_name = cache[i+1]->name;
+               int this_len = strlen(this_name);
+               if (this_len < strlen(next_name) &&
+                   strncmp(this_name, next_name, this_len) == 0 &&
+                   next_name[this_len] == '/') {
+                       if (10 < ++funny) {
+                               fprintf(stderr, "...\n");
+                               break;
+                       }
+                       fprintf(stderr, "You have both %s and %s\n",
+                               this_name, next_name);
+               }
+       }
+       if (funny)
+               return -1;
+       return 0;
+}
+
+static void discard_unused_subtrees(struct cache_tree *it)
+{
+       struct cache_tree_sub **down = it->down;
+       int nr = it->subtree_nr;
+       int dst, src;
+       for (dst = src = 0; src < nr; src++) {
+               struct cache_tree_sub *s = down[src];
+               if (s->used)
+                       down[dst++] = s;
+               else {
+                       cache_tree_free(&s->cache_tree);
+                       free(s);
+                       it->subtree_nr--;
+               }
+       }
+}
+
+int cache_tree_fully_valid(struct cache_tree *it)
+{
+       int i;
+       if (!it)
+               return 0;
+       if (it->entry_count < 0 || !has_sha1_file(it->sha1))
+               return 0;
+       for (i = 0; i < it->subtree_nr; i++) {
+               if (!cache_tree_fully_valid(it->down[i]->cache_tree))
+                       return 0;
+       }
+       return 1;
+}
+
+static int update_one(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     const char *base,
+                     int baselen,
+                     int missing_ok,
+                     int dryrun)
+{
+       unsigned long size, offset;
+       char *buffer;
+       int i;
+
+       if (0 <= it->entry_count && has_sha1_file(it->sha1))
+               return it->entry_count;
+
+       /*
+        * We first scan for subtrees and update them; we start by
+        * marking existing subtrees -- the ones that are unmarked
+        * should not be in the result.
+        */
+       for (i = 0; i < it->subtree_nr; i++)
+               it->down[i]->used = 0;
+
+       /*
+        * Find the subtrees and update them.
+        */
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, sublen, subcnt;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (!slash)
+                       continue;
+               /*
+                * a/bbb/c (base = a/, slash = /c)
+                * ==>
+                * path+baselen = bbb/c, sublen = 3
+                */
+               sublen = slash - (path + baselen);
+               sub = find_subtree(it, path + baselen, sublen, 1);
+               if (!sub->cache_tree)
+                       sub->cache_tree = cache_tree();
+               subcnt = update_one(sub->cache_tree,
+                                   cache + i, entries - i,
+                                   path,
+                                   baselen + sublen + 1,
+                                   missing_ok,
+                                   dryrun);
+               i += subcnt - 1;
+               sub->used = 1;
+       }
+
+       discard_unused_subtrees(it);
+
+       /*
+        * Then write out the tree object for this level.
+        */
+       size = 8192;
+       buffer = xmalloc(size);
+       offset = 0;
+
+       for (i = 0; i < entries; i++) {
+               struct cache_entry *ce = cache[i];
+               struct cache_tree_sub *sub;
+               const char *path, *slash;
+               int pathlen, entlen;
+               const unsigned char *sha1;
+               unsigned mode;
+
+               path = ce->name;
+               pathlen = ce_namelen(ce);
+               if (pathlen <= baselen || memcmp(base, path, baselen))
+                       break; /* at the end of this level */
+
+               slash = strchr(path + baselen, '/');
+               if (slash) {
+                       entlen = slash - (path + baselen);
+                       sub = find_subtree(it, path + baselen, entlen, 0);
+                       if (!sub)
+                               die("cache-tree.c: '%.*s' in '%s' not found",
+                                   entlen, path + baselen, path);
+                       i += sub->cache_tree->entry_count - 1;
+                       sha1 = sub->cache_tree->sha1;
+                       mode = S_IFDIR;
+               }
+               else {
+                       sha1 = ce->sha1;
+                       mode = ntohl(ce->ce_mode);
+                       entlen = pathlen - baselen;
+               }
+               if (!missing_ok && !has_sha1_file(sha1))
+                       return error("invalid object %s", sha1_to_hex(sha1));
+
+               if (!ce->ce_mode)
+                       continue; /* entry being removed */
+
+               if (size < offset + entlen + 100) {
+                       size = alloc_nr(offset + entlen + 100);
+                       buffer = xrealloc(buffer, size);
+               }
+               offset += sprintf(buffer + offset,
+                                 "%o %.*s", mode, entlen, path + baselen);
+               buffer[offset++] = 0;
+               memcpy(buffer + offset, sha1, 20);
+               offset += 20;
+
+#if DEBUG
+               fprintf(stderr, "cache-tree %o %.*s\n",
+                       mode, entlen, path + baselen);
+#endif
+       }
+
+       if (dryrun) {
+               unsigned char hdr[200];
+               int hdrlen;
+               write_sha1_file_prepare(buffer, offset, tree_type, it->sha1,
+                                       hdr, &hdrlen);
+       }
+       else
+               write_sha1_file(buffer, offset, tree_type, it->sha1);
+       free(buffer);
+       it->entry_count = i;
+#if DEBUG
+       fprintf(stderr, "cache-tree (%d ent, %d subtree) %s\n",
+               it->entry_count, it->subtree_nr,
+               sha1_to_hex(it->sha1));
+#endif
+       return i;
+}
+
+int cache_tree_update(struct cache_tree *it,
+                     struct cache_entry **cache,
+                     int entries,
+                     int missing_ok,
+                     int dryrun)
+{
+       int i;
+       i = verify_cache(cache, entries);
+       if (i)
+               return i;
+       i = update_one(it, cache, entries, "", 0, missing_ok, dryrun);
+       if (i < 0)
+               return i;
+       return 0;
+}
+
+static void *write_one(struct cache_tree *it,
+                      char *path,
+                      int pathlen,
+                      char *buffer,
+                      unsigned long *size,
+                      unsigned long *offset)
+{
+       int i;
+
+       /* One "cache-tree" entry consists of the following:
+        * path (NUL terminated)
+        * entry_count, subtree_nr ("%d %d\n")
+        * tree-sha1 (missing if invalid)
+        * subtree_nr "cache-tree" entries for subtrees.
+        */
+       if (*size < *offset + pathlen + 100) {
+               *size = alloc_nr(*offset + pathlen + 100);
+               buffer = xrealloc(buffer, *size);
+       }
+       *offset += sprintf(buffer + *offset, "%.*s%c%d %d\n",
+                          pathlen, path, 0,
+                          it->entry_count, it->subtree_nr);
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%.*s> (%d ent, %d subtree) %s\n",
+                       pathlen, path, it->entry_count, it->subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%.*s> (%d subtree) invalid\n",
+                       pathlen, path, it->subtree_nr);
+#endif
+
+       if (0 <= it->entry_count) {
+               memcpy(buffer + *offset, it->sha1, 20);
+               *offset += 20;
+       }
+       for (i = 0; i < it->subtree_nr; i++) {
+               struct cache_tree_sub *down = it->down[i];
+               if (i) {
+                       struct cache_tree_sub *prev = it->down[i-1];
+                       if (subtree_name_cmp(down->name, down->namelen,
+                                            prev->name, prev->namelen) <= 0)
+                               die("fatal - unsorted cache subtree");
+               }
+               buffer = write_one(down->cache_tree, down->name, down->namelen,
+                                  buffer, size, offset);
+       }
+       return buffer;
+}
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p)
+{
+       char path[PATH_MAX];
+       unsigned long size = 8192;
+       char *buffer = xmalloc(size);
+
+       *size_p = 0;
+       path[0] = 0;
+       return write_one(root, path, 0, buffer, &size, size_p);
+}
+
+static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
+{
+       const char *buf = *buffer;
+       unsigned long size = *size_p;
+       const char *cp;
+       char *ep;
+       struct cache_tree *it;
+       int i, subtree_nr;
+
+       it = NULL;
+       /* skip name, but make sure name exists */
+       while (size && *buf) {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       it = cache_tree();
+
+       cp = buf;
+       it->entry_count = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       cp = ep;
+       subtree_nr = strtol(cp, &ep, 10);
+       if (cp == ep)
+               goto free_return;
+       while (size && *buf && *buf != '\n') {
+               size--;
+               buf++;
+       }
+       if (!size)
+               goto free_return;
+       buf++; size--;
+       if (0 <= it->entry_count) {
+               if (size < 20)
+                       goto free_return;
+               memcpy(it->sha1, buf, 20);
+               buf += 20;
+               size -= 20;
+       }
+
+#if DEBUG
+       if (0 <= it->entry_count)
+               fprintf(stderr, "cache-tree <%s> (%d ent, %d subtree) %s\n",
+                       *buffer, it->entry_count, subtree_nr,
+                       sha1_to_hex(it->sha1));
+       else
+               fprintf(stderr, "cache-tree <%s> (%d subtrees) invalid\n",
+                       *buffer, subtree_nr);
+#endif
+
+       /*
+        * Just a heuristic -- we do not add directories that often but
+        * we do not want to have to extend it immediately when we do,
+        * hence +2.
+        */
+       it->subtree_alloc = subtree_nr + 2;
+       it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *));
+       for (i = 0; i < subtree_nr; i++) {
+               /* read each subtree */
+               struct cache_tree *sub;
+               struct cache_tree_sub *subtree;
+               const char *name = buf;
+
+               sub = read_one(&buf, &size);
+               if (!sub)
+                       goto free_return;
+               subtree = cache_tree_sub(it, name);
+               subtree->cache_tree = sub;
+       }
+       if (subtree_nr != it->subtree_nr)
+               die("cache-tree: internal error");
+       *buffer = buf;
+       *size_p = size;
+       return it;
+
+ free_return:
+       cache_tree_free(&it);
+       return NULL;
+}
+
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
+{
+       if (buffer[0])
+               return NULL; /* not the whole tree */
+       return read_one(&buffer, &size);
+}
diff --git a/cache-tree.h b/cache-tree.h
new file mode 100644 (file)
index 0000000..72c6480
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef CACHE_TREE_H
+#define CACHE_TREE_H
+
+struct cache_tree;
+struct cache_tree_sub {
+       struct cache_tree *cache_tree;
+       int namelen;
+       int used;
+       char name[FLEX_ARRAY];
+};
+
+struct cache_tree {
+       int entry_count; /* negative means "invalid" */
+       unsigned char sha1[20];
+       int subtree_nr;
+       int subtree_alloc;
+       struct cache_tree_sub **down;
+};
+
+struct cache_tree *cache_tree(void);
+void cache_tree_free(struct cache_tree **);
+void cache_tree_invalidate_path(struct cache_tree *, const char *);
+struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
+
+void *cache_tree_write(struct cache_tree *root, unsigned long *size_p);
+struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
+
+int cache_tree_fully_valid(struct cache_tree *);
+int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int);
+
+#endif
diff --git a/cache.h b/cache.h
index 4d8fabc6d8f3059d12640b66e6cf124c77022d07..a0cf8ca19343824f760bd38595c4cc4a9b35b923 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -114,6 +114,7 @@ static inline unsigned int create_ce_mode(unsigned int mode)
 
 extern struct cache_entry **active_cache;
 extern unsigned int active_nr, active_alloc, active_cache_changed;
+extern struct cache_tree *active_cache_tree;
 
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
@@ -135,6 +136,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)
 
@@ -250,6 +252,7 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned char *sha1_ret);
 
 const char *show_date(unsigned long time, int timezone);
+const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
index dd6a2d86fe59f1c75659cccef7a19c42bd2f721c..e56c354f8c44e9fc06acea7c4b2673b46f2a3a0b 100644 (file)
@@ -39,6 +39,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 
 #define CHECKOUT_ALL 4
 static const char *prefix;
index ca36f5d5e7342e9198675ce0faf11ba49ca67876..8a8fe3863a002ea6b62a760d5413dfe0fd0ffde7 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 2717dd81c346d89bf5d6727e3aa1f5b65ff39aca..42b44bba52b3bcf8545f7e7753d5ebb755155163 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -36,6 +36,8 @@ enum cmit_fmt get_commit_format(const char *arg)
                return CMIT_FMT_FULL;
        if (!strcmp(arg, "=fuller"))
                return CMIT_FMT_FULLER;
+       if (!strcmp(arg, "=email"))
+               return CMIT_FMT_EMAIL;
        if (!strcmp(arg, "=oneline"))
                return CMIT_FMT_ONELINE;
        die("invalid --pretty format");
@@ -428,6 +430,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
 
+       if (fmt == CMIT_FMT_EMAIL) {
+               what = "From";
+               filler = "";
+       }
        ret = sprintf(buf, "%s: %.*s%.*s\n", what,
                      (fmt == CMIT_FMT_FULLER) ? 4 : 0,
                      filler, namelen, line);
@@ -435,6 +441,10 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        case CMIT_FMT_MEDIUM:
                ret += sprintf(buf + ret, "Date:   %s\n", show_date(time, tz));
                break;
+       case CMIT_FMT_EMAIL:
+               ret += sprintf(buf + ret, "Date: %s\n",
+                              show_rfc2822_date(time, tz));
+               break;
        case CMIT_FMT_FULLER:
                ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
                break;
@@ -445,10 +455,12 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const c
        return ret;
 }
 
-static int is_empty_line(const char *line, int len)
+static int is_empty_line(const char *line, int *len_p)
 {
+       int len = *len_p;
        while (len && isspace(line[len-1]))
                len--;
+       *len_p = len;
        return !len;
 }
 
@@ -457,7 +469,8 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
        struct commit_list *parent = commit->parents;
        int offset;
 
-       if ((fmt == CMIT_FMT_ONELINE) || !parent || !parent->next)
+       if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
+           !parent || !parent->next)
                return 0;
 
        offset = sprintf(buf, "Merge:");
@@ -480,9 +493,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
 {
        int hdr = 1, body = 0;
        unsigned long offset = 0;
-       int indent = (fmt == CMIT_FMT_ONELINE) ? 0 : 4;
+       int indent = 4;
        int parents_shown = 0;
        const char *msg = commit->buffer;
+       const char *subject = NULL;
+
+       if (fmt == CMIT_FMT_EMAIL)
+               subject = "Subject: [PATCH] ";
+       if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
+               indent = 0;
 
        for (;;) {
                const char *line = msg;
@@ -506,7 +525,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                if (hdr) {
                        if (linelen == 1) {
                                hdr = 0;
-                               if (fmt != CMIT_FMT_ONELINE)
+                               if ((fmt != CMIT_FMT_ONELINE) && !subject)
                                        buf[offset++] = '\n';
                                continue;
                        }
@@ -544,20 +563,29 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                        continue;
                }
 
-               if (is_empty_line(line, linelen)) {
+               if (is_empty_line(line, &linelen)) {
                        if (!body)
                                continue;
+                       if (subject)
+                               continue;
                        if (fmt == CMIT_FMT_SHORT)
                                break;
                } else {
                        body = 1;
                }
 
+               if (subject) {
+                       int slen = strlen(subject);
+                       memcpy(buf + offset, subject, slen);
+                       offset += slen;
+               }
                memset(buf + offset, ' ', indent);
                memcpy(buf + offset + indent, line, linelen);
                offset += linelen + indent;
+               buf[offset++] = '\n';
                if (fmt == CMIT_FMT_ONELINE)
                        break;
+               subject = NULL;
        }
        while (offset && isspace(buf[offset-1]))
                offset--;
index de142afe733a6ecd3bcec2abd9f71a358b4ae09d..01eec60a1ecc2ff3c73850cb510ba71767e47e03 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -45,6 +45,7 @@ enum cmit_fmt {
        CMIT_FMT_FULL,
        CMIT_FMT_FULLER,
        CMIT_FMT_ONELINE,
+       CMIT_FMT_EMAIL,
 
        CMIT_FMT_UNSPECIFIED,
 };
index 4e1f0c22862b8c8605dbbf3a83ec4e87d9059513..253c48a6130a9268c106170df7205441d4ab36ac 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;
        }
 }
diff --git a/date.c b/date.c
index 034d7228bfefff9ba94ed3fd19fe751f924d7a55..365dc3b14e47826dd7becf19a76e29bfaa6b2eb5 100644 (file)
--- a/date.c
+++ b/date.c
@@ -42,18 +42,24 @@ static const char *weekday_names[] = {
  * thing, which means that tz -0100 is passed in as the integer -100,
  * even though it means "sixty minutes off"
  */
-const char *show_date(unsigned long time, int tz)
+static struct tm *time_to_tm(unsigned long time, int tz)
 {
-       struct tm *tm;
        time_t t;
-       static char timebuf[200];
        int minutes;
 
        minutes = tz < 0 ? -tz : tz;
        minutes = (minutes / 100)*60 + (minutes % 100);
        minutes = tz < 0 ? -minutes : minutes;
        t = time + minutes * 60;
-       tm = gmtime(&t);
+       return gmtime(&t);
+}
+
+const char *show_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
        if (!tm)
                return NULL;
        sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
@@ -65,6 +71,21 @@ const char *show_date(unsigned long time, int tz)
        return timebuf;
 }
 
+const char *show_rfc2822_date(unsigned long time, int tz)
+{
+       struct tm *tm;
+       static char timebuf[200];
+
+       tm = time_to_tm(time, tz);
+       if (!tm)
+               return NULL;
+       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+               weekday_names[tm->tm_wday], tm->tm_mday,
+               month_names[tm->tm_mon], tm->tm_year + 1900,
+               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
+       return timebuf;
+}
+
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
diff --git a/delta.h b/delta.h
index 9464f3e9b08b959d11305688ce194867a4017817..727ae30e9eb973b2bd4a512be78041fee40fa039 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 1188b31cd0f1e2f3a1fc2096a10243a03b439021..c61887518874d550f66d2d52bd7559d023fcb176 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.h b/diff.h
index 7150b90385952373f4fd49eb073d819541cc306d..b3b2c4dd28c4f1a8f14948e5d1262dd71f1e96cf 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -75,6 +75,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 *,
diff --git a/dump-cache-tree.c b/dump-cache-tree.c
new file mode 100644 (file)
index 0000000..fbea263
--- /dev/null
@@ -0,0 +1,65 @@
+#include "cache.h"
+#include "tree.h"
+#include "cache-tree.h"
+
+
+static void dump_one(struct cache_tree *it, const char *pfx, const char *x)
+{
+       if (it->entry_count < 0)
+               printf("%-40s %s%s (%d subtrees)\n",
+                      "invalid", x, pfx, it->subtree_nr);
+       else
+               printf("%s %s%s (%d entries, %d subtrees)\n",
+                      sha1_to_hex(it->sha1), x, pfx,
+                      it->entry_count, it->subtree_nr);
+}
+
+static int dump_cache_tree(struct cache_tree *it,
+                          struct cache_tree *ref,
+                          const char *pfx)
+{
+       int i;
+       int errs = 0;
+
+       if (!it)
+               return;
+       if (!ref)
+               die("internal error");
+
+       if (it->entry_count < 0) {
+               dump_one(it, pfx, "");
+               dump_one(ref, pfx, "#(ref) ");
+               if (it->subtree_nr != ref->subtree_nr)
+                       errs = 1;
+       }
+       else {
+               dump_one(it, pfx, "");
+               if (memcmp(it->sha1, ref->sha1, 20) ||
+                   ref->entry_count != it->entry_count ||
+                   ref->subtree_nr != it->subtree_nr) {
+                       dump_one(ref, pfx, "#(ref) ");
+                       errs = 1;
+               }
+       }
+
+       for (i = 0; i < it->subtree_nr; i++) {
+               char path[PATH_MAX];
+               struct cache_tree_sub *down = it->down[i];
+               struct cache_tree_sub *rdwn;
+
+               rdwn = cache_tree_sub(ref, down->name);
+               sprintf(path, "%s%.*s/", pfx, down->namelen, down->name);
+               if (dump_cache_tree(down->cache_tree, rdwn->cache_tree, path))
+                       errs = 1;
+       }
+       return errs;
+}
+
+int main(int ac, char **av)
+{
+       struct cache_tree *another = cache_tree();
+       if (read_cache() < 0)
+               die("unable to read index file");
+       cache_tree_update(another, active_cache, active_nr, 0, 1);
+       return dump_cache_tree(active_cache_tree, another, "");
+}
index 59b25904cb0f6e87594e2bad20db3ee2aa726aef..98421aab308086edc871001842dea1f8a06af71c 100644 (file)
@@ -8,6 +8,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "pack.h"
+#include "cache-tree.h"
 
 #define REACHABLE 0x0001
 
@@ -438,6 +439,23 @@ static int fsck_head_link(void)
        return 0;
 }
 
+static int fsck_cache_tree(struct cache_tree *it)
+{
+       int i;
+       int err = 0;
+
+       if (0 <= it->entry_count) {
+               struct object *obj = parse_object(it->sha1);
+               mark_reachable(obj, REACHABLE);
+               obj->used = 1;
+               if (obj->type != tree_type)
+                       err |= objerror(obj, "non-tree in cache-tree");
+       }
+       for (i = 0; i < it->subtree_nr; i++)
+               err |= fsck_cache_tree(it->down[i]->cache_tree);
+       return err;
+}
+
 int main(int argc, char **argv)
 {
        int i, heads;
@@ -547,6 +565,8 @@ int main(int argc, char **argv)
                        obj->used = 1;
                        mark_reachable(obj, REACHABLE);
                }
+               if (active_cache_tree)
+                       fsck_cache_tree(active_cache_tree);
        }
 
        check_connectivity();
index 872145b92dc7000eb1c87d548a46fa0d9522870d..507ae4dcb9964e63143661392d7a10d7da4ef33e 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
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
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 83143f82cfa3b92a38a9caa2b00860541d139aa2..280f62e4b7e1fcdf97f306bab6044b5c383dce0d 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 f7b2b9401a90e5aa598daa6049ae5d33e5c5b157..9e259028e087e6eb33bd338855aa12f9ebb10ae9 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 ecfa347b85bb6c3c5e6b1b3a1f83ac1acb069b66..703dd1ff9ee641b58ad592cfc702de7f96c59eed 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 @@ sub send_message
 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 893bddd768674e7389864510f04655615233ebe3..d5f0faa9077b0a769c8b36d47cc08cbf86b9d564 100644 (file)
--- a/git.c
+++ b/git.c
@@ -46,6 +46,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "log", cmd_log },
                { "whatchanged", cmd_whatchanged },
                { "show", cmd_show },
+               { "fmt-patch", cmd_format_patch },
+               { "count-objects", cmd_count_objects },
+               { "diff", cmd_diff },
+               { "push", cmd_push },
                { "grep", cmd_grep },
        };
        int i;
diff --git a/gitk b/gitk
index 5362b76bee5ad0ee13964634ac12e7d73b85cdbb..4aa57c01ce56e505aba5384f716376a68a7fdc7a 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 9634c4677f84e7d88965b47b5af32f23668b2142..aaf2b9423f01243584e15e6c93007a3f42c9d917 100644 (file)
@@ -37,12 +37,20 @@ void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
        /*
         * Print header line of header..
         */
-       printf("%s%s",
-               opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
-               diff_unique_abbrev(commit->object.sha1, abbrev_commit));
-       if (parent) 
-               printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
-       putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+
+       if (opt->commit_format == CMIT_FMT_EMAIL)
+               printf("From %s  Thu Apr 7 15:13:13 2005\n",
+                      sha1_to_hex(commit->object.sha1));
+       else {
+               printf("%s%s",
+                      opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+                      diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+               if (parent) 
+                       printf(" (from %s)",
+                              diff_unique_abbrev(parent->object.sha1,
+                                                 abbrev_commit));
+               putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+       }
 
        /*
         * And then the pretty-printed message itself
@@ -152,15 +160,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
 int log_tree_commit(struct rev_info *opt, struct commit *commit)
 {
        struct log_info log;
+       int shown;
 
        log.commit = commit;
        log.parent = NULL;
        opt->loginfo = &log;
 
-       if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+       shown = log_tree_diff(opt, commit, &log);
+       if (!shown && opt->loginfo && opt->always_show_header) {
                log.parent = NULL;
                show_log(opt, opt->loginfo, "");
+               shown = 1;
        }
        opt->loginfo = NULL;
-       return 0;
+       return shown;
 }
index c0acc460bb9df0313e314eb2cf48363b2458082c..5b2ef9a51387dc01e9d31f08962101cf1323e3e2 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);
 }
 
index d95f0d9721dd87c0d4aff485e32e0a68623ee5cc..8f318ed8aaf180419f97887501317fc1d2b54c09 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 f97f92d90a6d6f8359dcb4c25988fe623fd4dd65..1f71d125780fbaecf613f8b979fcc04b1b630c97 100644 (file)
@@ -4,11 +4,26 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "cache-tree.h"
+
+/* Index extensions.
+ *
+ * The first letter should be 'A'..'Z' for extensions that are not
+ * necessary for a correct operation (i.e. optimization data).
+ * When new extensions are added that _needs_ to be understood in
+ * order to correctly interpret the index file, pick character that
+ * is outside the range, to cause the reader to abort.
+ */
+
+#define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) )
+#define CACHE_EXT_TREE 0x54524545      /* "TREE" */
 
 struct cache_entry **active_cache = NULL;
 static time_t index_file_timestamp;
 unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
 
+struct cache_tree *active_cache_tree = NULL;
+
 /*
  * This only updates the "non-critical" parts of the directory
  * cache, ie the parts that aren't tracked by GIT, and only used
@@ -513,6 +528,22 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size)
        return 0;
 }
 
+static int read_index_extension(const char *ext, void *data, unsigned long sz)
+{
+       switch (CACHE_EXT(ext)) {
+       case CACHE_EXT_TREE:
+               active_cache_tree = cache_tree_read(data, sz);
+               break;
+       default:
+               if (*ext < 'A' || 'Z' < *ext)
+                       return error("index uses %.4s extension, which we do not understand",
+                                    ext);
+               fprintf(stderr, "ignoring %.4s extension\n", ext);
+               break;
+       }
+       return 0;
+}
+
 int read_cache(void)
 {
        int fd, i;
@@ -561,6 +592,22 @@ int read_cache(void)
                active_cache[i] = ce;
        }
        index_file_timestamp = st.st_mtime;
+       while (offset <= size - 20 - 8) {
+               /* After an array of active_nr index entries,
+                * there can be arbitrary number of extended
+                * sections, each of which is prefixed with
+                * extension name (4-byte) and section length
+                * in 4-byte network byte order.
+                */
+               unsigned long extsize;
+               memcpy(&extsize, map + offset + 4, 4);
+               extsize = ntohl(extsize);
+               if (read_index_extension(map + offset,
+                                        map + offset + 8, extsize) < 0)
+                       goto unmap;
+               offset += 8;
+               offset += extsize;
+       }
        return active_nr;
 
 unmap:
@@ -595,6 +642,17 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
        return 0;
 }
 
+static int write_index_ext_header(SHA_CTX *context, int fd,
+                                 unsigned long ext, unsigned long sz)
+{
+       ext = htonl(ext);
+       sz = htonl(sz);
+       if ((ce_write(context, fd, &ext, 4) < 0) ||
+           (ce_write(context, fd, &sz, 4) < 0))
+               return -1;
+       return 0;
+}
+
 static int ce_flush(SHA_CTX *context, int fd)
 {
        unsigned int left = write_buffer_len;
@@ -691,5 +749,19 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
                if (ce_write(&c, newfd, ce, ce_size(ce)) < 0)
                        return -1;
        }
+
+       /* Write extension data here */
+       if (active_cache_tree) {
+               unsigned long sz;
+               void *data = cache_tree_write(active_cache_tree, &sz);
+               if (data &&
+                   !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
+                   !ce_write(&c, newfd, data, sz))
+                       ;
+               else {
+                       free(data);
+                       return -1;
+               }
+       }
        return ce_flush(&c, newfd);
 }
index 26f4f7e32308d6e60d6a6e2c294bda322b34fc05..66c0120f1352f8edf6ea1afcd037c96b373273a8 100644 (file)
@@ -9,6 +9,7 @@
 
 #include "object.h"
 #include "tree.h"
+#include "cache-tree.h"
 #include <sys/time.h>
 #include <signal.h>
 
@@ -421,6 +422,12 @@ static void verify_uptodate(struct cache_entry *ce)
        die("Entry '%s' not uptodate. Cannot merge.", ce->name);
 }
 
+static void invalidate_ce_path(struct cache_entry *ce)
+{
+       if (ce)
+               cache_tree_invalidate_path(active_cache_tree, ce->name);
+}
+
 static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
 {
        merge->ce_flags |= htons(CE_UPDATE);
@@ -436,6 +443,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old)
                        *merge = *old;
                } else {
                        verify_uptodate(old);
+                       invalidate_ce_path(old);
                }
        }
        merge->ce_flags &= ~htons(CE_STAGEMASK);
@@ -449,6 +457,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old)
                verify_uptodate(old);
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
+       invalidate_ce_path(ce);
        return 1;
 }
 
@@ -683,8 +692,10 @@ static int oneway_merge(struct cache_entry **src)
                return error("Cannot do a oneway merge of %d trees",
                             merge_size);
 
-       if (!a)
+       if (!a) {
+               invalidate_ce_path(old);
                return 0;
+       }
        if (old && same(old, a)) {
                return keep_entry(old);
        }
@@ -703,6 +714,7 @@ static int read_cache_unmerged(void)
                struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce)) {
                        deleted++;
+                       invalidate_ce_path(ce);
                        continue;
                }
                if (deleted)
@@ -713,6 +725,39 @@ static int read_cache_unmerged(void)
        return deleted;
 }
 
+static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
+{
+       struct tree_entry_list *ent;
+       int cnt;
+       
+       memcpy(it->sha1, tree->object.sha1, 20);
+       for (cnt = 0, ent = tree->entries; ent; ent = ent->next) {
+               if (!ent->directory)
+                       cnt++;
+               else {
+                       struct cache_tree_sub *sub;
+                       struct tree *subtree = (struct tree *)ent->item.tree;
+                       if (!subtree->object.parsed)
+                               parse_tree(subtree);
+                       sub = cache_tree_sub(it, ent->name);
+                       sub->cache_tree = cache_tree();
+                       prime_cache_tree_rec(sub->cache_tree, subtree);
+                       cnt += sub->cache_tree->entry_count;
+               }
+       }
+       it->entry_count = cnt;
+}
+
+static void prime_cache_tree(void)
+{
+       struct tree *tree = (struct tree *)trees->item;
+       if (!tree)
+               return;
+       active_cache_tree = cache_tree();
+       prime_cache_tree_rec(active_cache_tree, tree);
+
+}
+
 static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
 
 static struct cache_file cache_file;
@@ -814,10 +859,9 @@ int main(int argc, char **argv)
                        fn = twoway_merge;
                        break;
                case 3:
-                       fn = threeway_merge;
-                       break;
                default:
                        fn = threeway_merge;
+                       cache_tree_free(&active_cache_tree);
                        break;
                }
 
@@ -828,6 +872,18 @@ int main(int argc, char **argv)
        }
 
        unpack_trees(fn);
+
+       /*
+        * When reading only one tree (either the most basic form,
+        * "-m ent" or "--reset ent" form), we can obtain a fully
+        * valid cache-tree because the index must match exactly
+        * what came from the tree.
+        */
+       if (trees->item && (!merge || (stage == 2))) {
+               cache_tree_free(&active_cache_tree);            
+               prime_cache_tree();
+       }
+
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_index_file(&cache_file))
                die("unable to write new index file");
index fa8aba7a1b8760f0316ef0d47c40b0078316ff57..7e06d1a041dfd04d73b54362374f246d497b690f 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,33 +25,37 @@ 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;
+
        if (value_ == NULL)
                value_ = "";
 
-       if (!strcmp(key_, key) &&
+       if ((use_key_regexp || !strcmp(key_, key)) &&
+                       (!use_key_regexp ||
+                        !regexec(key_regexp, key_, 0, NULL, 0)) &&
                        (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);
-               }
-
-               if (type == T_INT) {
-                       value = malloc(256);
+               int dup_error = 0;
+               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) {
-                       value = malloc(256);
+               else if (type == T_BOOL)
                        sprintf(value, "%s", git_config_bool(key_, value_)
                                             ? "true" : "false");
-               } else {
-                       value = strdup(value_);
-               }
+               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 +69,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 +91,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 +98,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 +112,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 +133,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 +151,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 f2a9f25fe11f3883281ea5fdd783700056fd5c6e..846c9ec463e9b7e4fd2f7b4e41c5ee1aa7a7007c 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));
@@ -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,12 +771,24 @@ 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;
@@ -757,13 +800,20 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        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);
        }
diff --git a/setup.c b/setup.c
index cce9bb80692f99ea1661fb8ab925955b5db0336c..fe7f8846962d1c656d258384dbfa466031e28896 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 345935bb2beddac7b86d5b989ea1492f58a777d8..ec5cd2c9ea42cfdd07a1fcac1f2c05072aa26193 100644 (file)
@@ -458,17 +458,55 @@ int get_sha1(const char *name, unsigned char *sha1)
 {
        int ret;
        unsigned unused;
+       int namelen = strlen(name);
+       const char *cp;
 
        prepare_alt_odb();
-       ret = get_sha1_1(name, strlen(name), sha1);
-       if (ret < 0) {
-               const char *cp = strchr(name, ':');
-               if (cp) {
-                       unsigned char tree_sha1[20];
-                       if (!get_sha1_1(name, cp-name, tree_sha1))
-                               return get_tree_entry(tree_sha1, cp+1, sha1,
-                                                     &unused);
+       ret = get_sha1_1(name, namelen, sha1);
+       if (!ret)
+               return ret;
+       /* sha1:path --> object name of path in ent sha1
+        * :path -> object name of path in index
+        * :[0-3]:path -> object name of path in index at stage
+        */
+       if (name[0] == ':') {
+               int stage = 0;
+               struct cache_entry *ce;
+               int pos;
+               if (namelen < 3 ||
+                   name[2] != ':' ||
+                   name[1] < '0' || '3' < name[1])
+                       cp = name + 1;
+               else {
+                       stage = name[1] - '0';
+                       cp = name + 3;
                }
+               namelen = namelen - (cp - name);
+               if (!active_cache)
+                       read_cache();
+               if (active_nr < 0)
+                       return -1;
+               pos = cache_name_pos(cp, namelen);
+               if (pos < 0)
+                       pos = -pos - 1;
+               while (pos < active_nr) {
+                       ce = active_cache[pos];
+                       if (ce_namelen(ce) != namelen ||
+                           memcmp(ce->name, cp, namelen))
+                               break;
+                       if (ce_stage(ce) == stage) {
+                               memcpy(sha1, ce->sha1, 20);
+                               return 0;
+                       }
+               }
+               return -1;
+       }
+       cp = strchr(name, ':');
+       if (cp) {
+               unsigned char tree_sha1[20];
+               if (!get_sha1_1(name, cp-name, tree_sha1))
+                       return get_tree_entry(tree_sha1, cp+1, sha1,
+                                             &unused);
        }
        return ret;
 }
index 24efb65e622d8dfe44db22e5bc23c9f8cc328109..268c57b180627f7d8ba3a3a754210a37d420fb0d 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 ab4dd5c4ce14c496e1239a0013952c4c422a6a50..1bf728fb067cfa8c7209d29207470ef09a91b4b3 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,10 @@ 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"
+
 test_done
 
index facec8d91524ab63117d62134f91df385dcf34ca..1c1f13bd7044ad78800b4f9faa0ff1b05b9b6c21 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "cache-tree.h"
 #include "tree-walk.h"
 
 /*
@@ -71,6 +72,7 @@ static int mark_valid(const char *path)
                        active_cache[pos]->ce_flags &= ~htons(CE_VALID);
                        break;
                }
+               cache_tree_invalidate_path(active_cache_tree, path);
                active_cache_changed = 1;
                return 0;
        }
@@ -84,6 +86,12 @@ static int add_file_to_cache(const char *path)
        struct stat st;
 
        status = lstat(path, &st);
+
+       /* We probably want to do this in remove_file_from_cache() and
+        * add_cache_entry() instead...
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
        if (status < 0 || S_ISDIR(st.st_mode)) {
                /* When we used to have "path" and now we want to add
                 * "path/file", we need a way to remove "path" before
@@ -326,6 +334,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                return error("%s: cannot add to the index - missing --add option?",
                             path);
        report("add '%s'", path);
+       cache_tree_invalidate_path(active_cache_tree, path);
        return 0;
 }
 
@@ -350,6 +359,7 @@ static void chmod_path(int flip, const char *path)
        default:
                goto fail;
        }
+       cache_tree_invalidate_path(active_cache_tree, path);
        active_cache_changed = 1;
        report("chmod %cx '%s'", flip, path);
        return;
@@ -371,6 +381,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                        die("Unable to mark file %s", path);
                return;
        }
+       cache_tree_invalidate_path(active_cache_tree, path);
 
        if (force_remove) {
                if (remove_file_from_cache(p))
@@ -446,6 +457,7 @@ static void read_index_info(int line_termination)
                                free(path_name);
                        continue;
                }
+               cache_tree_invalidate_path(active_cache_tree, path_name);
 
                if (!mode) {
                        /* mode == 0 means there is no such path -- remove */
@@ -550,6 +562,7 @@ static int unresolve_one(const char *path)
                goto free_return;
        }
 
+       cache_tree_invalidate_path(active_cache_tree, path);
        remove_file_from_cache(path);
        if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
                error("%s: cannot add our version to the index.", path);
index dcad6e66708f7cf9cae682da6a9a22086381ecec..7a4f691d8ab8f6f560685ec6fe71cf2d82e8a018 100644 (file)
@@ -5,95 +5,21 @@
  */
 #include "cache.h"
 #include "tree.h"
+#include "cache-tree.h"
 
 static int missing_ok = 0;
 
-static int check_valid_sha1(unsigned char *sha1)
-{
-       int ret;
-
-       /* If we were anal, we'd check that the sha1 of the contents actually matches */
-       ret = has_sha1_file(sha1);
-       if (ret == 0)
-               perror(sha1_file_name(sha1));
-       return ret ? 0 : -1;
-}
-
-static int write_tree(struct cache_entry **cachep, int maxentries, const char *base, int baselen, unsigned char *returnsha1)
-{
-       unsigned char subdir_sha1[20];
-       unsigned long size, offset;
-       char *buffer;
-       int nr;
-
-       /* Guess at some random initial size */
-       size = 8192;
-       buffer = xmalloc(size);
-       offset = 0;
-
-       nr = 0;
-       while (nr < maxentries) {
-               struct cache_entry *ce = cachep[nr];
-               const char *pathname = ce->name, *filename, *dirname;
-               int pathlen = ce_namelen(ce), entrylen;
-               unsigned char *sha1;
-               unsigned int mode;
-
-               /* Did we hit the end of the directory? Return how many we wrote */
-               if (baselen >= pathlen || memcmp(base, pathname, baselen))
-                       break;
-
-               sha1 = ce->sha1;
-               mode = ntohl(ce->ce_mode);
-
-               /* Do we have _further_ subdirectories? */
-               filename = pathname + baselen;
-               dirname = strchr(filename, '/');
-               if (dirname) {
-                       int subdir_written;
-
-                       subdir_written = write_tree(cachep + nr, maxentries - nr, pathname, dirname-pathname+1, subdir_sha1);
-                       nr += subdir_written;
-
-                       /* Now we need to write out the directory entry into this tree.. */
-                       mode = S_IFDIR;
-                       pathlen = dirname - pathname;
-
-                       /* ..but the directory entry doesn't count towards the total count */
-                       nr--;
-                       sha1 = subdir_sha1;
-               }
-
-               if (!missing_ok && check_valid_sha1(sha1) < 0)
-                       exit(1);
-
-               entrylen = pathlen - baselen;
-               if (offset + entrylen + 100 > size) {
-                       size = alloc_nr(offset + entrylen + 100);
-                       buffer = xrealloc(buffer, size);
-               }
-               offset += sprintf(buffer + offset, "%o %.*s", mode, entrylen, filename);
-               buffer[offset++] = 0;
-               memcpy(buffer + offset, sha1, 20);
-               offset += 20;
-               nr++;
-       }
-
-       write_sha1_file(buffer, offset, tree_type, returnsha1);
-       free(buffer);
-       return nr;
-}
-
 static const char write_tree_usage[] = "git-write-tree [--missing-ok]";
 
+static struct cache_file cache_file;
+
 int main(int argc, char **argv)
 {
-       int i, funny;
-       int entries;
-       unsigned char sha1[20];
-       
+       int entries, was_valid, newfd;
+
        setup_git_directory();
 
+       newfd = hold_index_file_for_update(&cache_file, get_index_file());
        entries = read_cache();
        if (argc == 2) {
                if (!strcmp(argv[1], "--missing-ok"))
@@ -108,51 +34,26 @@ int main(int argc, char **argv)
        if (entries < 0)
                die("git-write-tree: error reading cache");
 
-       /* Verify that the tree is merged */
-       funny = 0;
-       for (i = 0; i < entries; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (ce_stage(ce)) {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "%s: unmerged (%s)\n", ce->name, sha1_to_hex(ce->sha1));
+       if (!active_cache_tree)
+               active_cache_tree = cache_tree();
+
+       was_valid = cache_tree_fully_valid(active_cache_tree);
+       if (!was_valid) {
+               if (cache_tree_update(active_cache_tree,
+                                     active_cache, active_nr,
+                                     missing_ok, 0) < 0)
+                       die("git-write-tree: error building trees");
+               if (0 <= newfd) {
+                       if (!write_cache(newfd, active_cache, active_nr))
+                               commit_index_file(&cache_file);
                }
-       }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Also verify that the cache does not have path and path/file
-        * at the same time.  At this point we know the cache has only
-        * stage 0 entries.
-        */
-       funny = 0;
-       for (i = 0; i < entries - 1; i++) {
-               /* path/file always comes after path because of the way
-                * the cache is sorted.  Also path can appear only once,
-                * which means conflicting one would immediately follow.
+               /* Not being able to write is fine -- we are only interested
+                * in updating the cache-tree part, and if the next caller
+                * ends up using the old index with unupdated cache-tree part
+                * it misses the work we did here, but that is just a
+                * performance penalty and not a big deal.
                 */
-               const char *this_name = active_cache[i]->name;
-               const char *next_name = active_cache[i+1]->name;
-               int this_len = strlen(this_name);
-               if (this_len < strlen(next_name) &&
-                   strncmp(this_name, next_name, this_len) == 0 &&
-                   next_name[this_len] == '/') {
-                       if (10 < ++funny) {
-                               fprintf(stderr, "...\n");
-                               break;
-                       }
-                       fprintf(stderr, "You have both %s and %s\n",
-                               this_name, next_name);
-               }
        }
-       if (funny)
-               die("git-write-tree: not able to write tree");
-
-       /* Ok, write it out */
-       if (write_tree(active_cache, entries, "", 0, sha1) != entries)
-               die("git-write-tree: internal error");
-       printf("%s\n", sha1_to_hex(sha1));
+       printf("%s\n", sha1_to_hex(active_cache_tree->sha1));
        return 0;
 }