Merge branch 'maint'
authorJunio C Hamano <junkio@cox.net>
Thu, 4 May 2006 06:00:02 +0000 (23:00 -0700)
committerJunio C Hamano <junkio@cox.net>
Thu, 4 May 2006 06:00:02 +0000 (23:00 -0700)
* maint:
  Add a few more words to the glossary.
  Added definitions for a few words:
  Alphabetize the glossary.

49 files changed:
Documentation/git-rebase.txt
Documentation/git-repo-config.txt
Documentation/git-update-index.txt
Documentation/git-var.txt
Makefile
blame.c
builtin-help.c [new file with mode: 0644]
builtin-log.c [new file with mode: 0644]
builtin.h [new file with mode: 0644]
cache.h
combine-diff.c
commit-tree.c
commit.h
config.c
contrib/colordiff/README [new file with mode: 0644]
contrib/colordiff/colordiff.perl [new file with mode: 0755]
diff-files.c
diff-index.c
diff-lib.c [new file with mode: 0644]
diff-tree.c
diff.c
diff.h
git-cvsserver.perl
git-fetch.sh
git-rebase.sh
git-repack.sh
git.c
gitk
http-push.c
log-tree.c
log-tree.h
merge-tree.c
pack-objects.c
pager.c
repo-config.c
rev-list.c
revision.c
revision.h
setup.c
sha1_file.c
sha1_name.c
t/t0000-basic.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1200-tutorial.sh
t/t4010-diff-pathspec.sh
tree-walk.c
tree-walk.h
update-index.c

index 4a7e67a..1b482ab 100644 (file)
@@ -3,38 +3,54 @@ git-rebase(1)
 
 NAME
 ----
-git-rebase - Rebase local commits to new upstream head
+git-rebase - Rebase local commits to a new head
 
 SYNOPSIS
 --------
 'git-rebase' [--onto <newbase>] <upstream> [<branch>]
 
+'git-rebase' --continue
+
+'git-rebase' --abort
+
 DESCRIPTION
 -----------
-git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
+git-rebase replaces <branch> with a new branch of the same name.  When
+the --onto option is provided the new branch starts out with a HEAD equal
+to <newbase>, otherwise it is equal to <upstream>.  It then attempts to
+create a new commit for each commit from the original <branch> that does
+not exist in the <upstream> branch.
 
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run `git rebase --continue`.  If you can not resolve the merge
+failure, running `git rebase --abort` will restore the original <branch>
+and remove the working files found in the .dotest directory.
+
+Note that if <branch> is not specified on the command line, the currently
+checked out branch is used.
 
 Assume the following history exists and the current branch is "topic":
 
+------------
           A---B---C topic
          /
     D---E---F---G master
+------------
 
 From this point, the result of either of the following commands:
 
+
     git-rebase master
     git-rebase master topic
 
 would be:
 
+------------
                   A'--B'--C' topic
                  /
     D---E---F---G master
+------------
 
 While, starting from the same point, the result of either of the following
 commands:
@@ -44,21 +60,33 @@ commands:
 
 would be:
 
+------------
               A'--B'--C' topic
              /
     D---E---F---G master
+------------
 
 In case of conflict, git-rebase will stop at the first problematic commit
-and leave conflict markers in the tree.  After resolving the conflict manually
-and updating the index with the desired resolution, you can continue the
-rebasing process with
+and leave conflict markers in the tree.  You can use git diff to locate
+the markers (<<<<<<) and make edits to resolve the conflict.  For each
+file you edit, you need to tell git that the conflict has been resolved,
+typically this would be done with
+
+
+    git update-index <filename>
+
+
+After resolving the conflict manually and updating the index with the
+desired resolution, you can continue the rebasing process with
+
+
+    git rebase --continue
 
-    git am --resolved --3way
 
 Alternatively, you can undo the git-rebase with
 
-    git reset --hard ORIG_HEAD
-    rm -r .dotest
+
+    git rebase --abort
 
 OPTIONS
 -------
@@ -73,6 +101,28 @@ OPTIONS
 <branch>::
        Working branch; defaults to HEAD.
 
+--continue::
+       Restart the rebasing process after having resolved a merge conflict.
+
+--abort::
+       Restore the original branch and abort the rebase operation.
+
+NOTES
+-----
+When you rebase a branch, you are changing its history in a way that
+will cause problems for anyone who already has a copy of the branch
+in their repository and tries to pull updates from you.  You should
+understand the implications of using 'git rebase' on a repository that
+you share.
+
+When the git rebase command is run, it will first execute a "pre-rebase"
+hook if one exists.  You can use this hook to do sanity checks and
+reject the rebase if it isn't appropriate.  Please see the template
+pre-rebase hook script for an example.
+
+You must be in the top directory of your project to start (or continue)
+a rebase.  Upon completion, <branch> will be the current branch.
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index 71f96bd..566cfa1 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
 'git-repo-config' [type] --get-all name [value_regex]
 'git-repo-config' [type] --unset name [value_regex]
 'git-repo-config' [type] --unset-all name [value_regex]
+'git-repo-config' -l | --list
 
 DESCRIPTION
 -----------
@@ -64,6 +65,9 @@ OPTIONS
 --unset-all::
        Remove all matching lines from .git/config.
 
+-l, --list::
+       List all variables set in .git/config.
+
 
 EXAMPLE
 -------
index e01b42f..23f2b6f 100644 (file)
@@ -15,7 +15,7 @@ SYNOPSIS
             [--cacheinfo <mode> <object> <file>]\*
             [--chmod=(+|-)x]
             [--assume-unchanged | --no-assume-unchanged]
-            [--really-refresh]
+            [--really-refresh] [--unresolve]
             [--info-only] [--index-info]
             [-z] [--stdin]
             [--verbose]
@@ -80,6 +80,10 @@ OPTIONS
        filesystem that has very slow lstat(2) system call
        (e.g. cifs).
 
+--unresolve::
+       Restores the 'unmerged' or 'needs updating' state of a
+       file during a merge if it was cleared by accident.
+
 --info-only::
        Do not create objects in the object database for all
        <file> arguments that follow this flag; just insert
index 379571e..a5b1a0d 100644 (file)
@@ -19,7 +19,8 @@ OPTIONS
 -l::
        Cause the logical variables to be listed. In addition, all the
        variables of the git configuration file .git/config are listed
-       as well.
+       as well. (However, the configuration variables listing functionality
+       is deprecated in favor of `git-repo-config -l`.)
 
 EXAMPLE
 --------
index 8aed3af..8ce27a6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -199,7 +199,7 @@ LIB_H = \
        tree-walk.h log-tree.h
 
 DIFF_OBJS = \
-       diff.o diffcore-break.o diffcore-order.o \
+       diff.o diff-lib.o diffcore-break.o diffcore-order.o \
        diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
        diffcore-delta.o log-tree.o
 
@@ -213,6 +213,9 @@ LIB_OBJS = \
        fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \
        $(DIFF_OBJS)
 
+BUILTIN_OBJS = \
+       builtin-log.o builtin-help.o
+
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
 LIBS = $(GITLIBS) -lz
 
@@ -462,10 +465,12 @@ all:
 strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
-git$X: git.c common-cmds.h $(GITLIBS)
+git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS)
        $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
                $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
-               $(ALL_LDFLAGS) $(LIBS)
+               $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
+
+builtin-help.o: common-cmds.h
 
 $(BUILT_INS): git$X
        rm -f $@ && ln git$X $@
@@ -565,17 +570,17 @@ init-db.o: init-db.c
        $(CC) -c $(ALL_CFLAGS) \
                -DDEFAULT_GIT_TEMPLATE_DIR='"$(template_dir_SQ)"' $*.c
 
-$(LIB_OBJS): $(LIB_H)
+$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
 $(patsubst git-%$X,%.o,$(PROGRAMS)): $(GITLIBS)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(AR) rcs $@ $(LIB_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
 XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(AR) rcs $@ $(XDIFF_OBJS)
+       rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
 doc:
diff --git a/blame.c b/blame.c
index 07d2d27..99ceea8 100644 (file)
--- a/blame.c
+++ b/blame.c
@@ -515,9 +515,9 @@ static int compare_tree_path(struct rev_info* revs,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_compare_tree(revs, c1->tree, c2->tree);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -531,9 +531,9 @@ static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1,
        paths[1] = NULL;
 
        diff_tree_setup_paths(get_pathspec(revs->prefix, paths),
-                             &revs->diffopt);
+                             &revs->pruning);
        ret = rev_same_tree_as_empty(revs, t1);
-       diff_tree_release_paths(&revs->diffopt);
+       diff_tree_release_paths(&revs->pruning);
        return ret;
 }
 
@@ -834,7 +834,7 @@ int main(int argc, const char **argv)
 
        args[0] = filename;
        args[1] = NULL;
-       diff_tree_setup_paths(args, &rev.diffopt);
+       diff_tree_setup_paths(args, &rev.pruning);
        prepare_revision_walk(&rev);
        process_commits(&rev, filename, &initial);
 
diff --git a/builtin-help.c b/builtin-help.c
new file mode 100644 (file)
index 0000000..7470faa
--- /dev/null
@@ -0,0 +1,242 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help-related commands (help, usage, version)
+ */
+#include <sys/ioctl.h>
+#include "cache.h"
+#include "builtin.h"
+#include "exec_cmd.h"
+#include "common-cmds.h"
+
+static const char git_usage[] =
+       "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+
+/* most gui terms set COLUMNS (although some don't export it) */
+static int term_columns(void)
+{
+       char *col_string = getenv("COLUMNS");
+       int n_cols = 0;
+
+       if (col_string && (n_cols = atoi(col_string)) > 0)
+               return n_cols;
+
+#ifdef TIOCGWINSZ
+       {
+               struct winsize ws;
+               if (!ioctl(1, TIOCGWINSZ, &ws)) {
+                       if (ws.ws_col)
+                               return ws.ws_col;
+               }
+       }
+#endif
+
+       return 80;
+}
+
+static void oom(void)
+{
+       fprintf(stderr, "git: out of memory\n");
+       exit(1);
+}
+
+static inline void mput_char(char c, unsigned int num)
+{
+       while(num--)
+               putchar(c);
+}
+
+static struct cmdname {
+       size_t len;
+       char name[1];
+} **cmdname;
+static int cmdname_alloc, cmdname_cnt;
+
+static void add_cmdname(const char *name, int len)
+{
+       struct cmdname *ent;
+       if (cmdname_alloc <= cmdname_cnt) {
+               cmdname_alloc = cmdname_alloc + 200;
+               cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
+               if (!cmdname)
+                       oom();
+       }
+       ent = malloc(sizeof(*ent) + len);
+       if (!ent)
+               oom();
+       ent->len = len;
+       memcpy(ent->name, name, len);
+       ent->name[len] = 0;
+       cmdname[cmdname_cnt++] = ent;
+}
+
+static int cmdname_compare(const void *a_, const void *b_)
+{
+       struct cmdname *a = *(struct cmdname **)a_;
+       struct cmdname *b = *(struct cmdname **)b_;
+       return strcmp(a->name, b->name);
+}
+
+static void pretty_print_string_list(struct cmdname **cmdname, int longest)
+{
+       int cols = 1, rows;
+       int space = longest + 1; /* min 1 SP between words */
+       int max_cols = term_columns() - 1; /* don't print *on* the edge */
+       int i, j;
+
+       if (space < max_cols)
+               cols = max_cols / space;
+       rows = (cmdname_cnt + cols - 1) / cols;
+
+       qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
+
+       for (i = 0; i < rows; i++) {
+               printf("  ");
+
+               for (j = 0; j < cols; j++) {
+                       int n = j * rows + i;
+                       int size = space;
+                       if (n >= cmdname_cnt)
+                               break;
+                       if (j == cols-1 || n + rows >= cmdname_cnt)
+                               size = 1;
+                       printf("%-*s", size, cmdname[n]->name);
+               }
+               putchar('\n');
+       }
+}
+
+static void list_commands(const char *exec_path, const char *pattern)
+{
+       unsigned int longest = 0;
+       char path[PATH_MAX];
+       int dirlen;
+       DIR *dir = opendir(exec_path);
+       struct dirent *de;
+
+       if (!dir) {
+               fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
+               exit(1);
+       }
+
+       dirlen = strlen(exec_path);
+       if (PATH_MAX - 20 < dirlen) {
+               fprintf(stderr, "git: insanely long exec-path '%s'\n",
+                       exec_path);
+               exit(1);
+       }
+
+       memcpy(path, exec_path, dirlen);
+       path[dirlen++] = '/';
+
+       while ((de = readdir(dir)) != NULL) {
+               struct stat st;
+               int entlen;
+
+               if (strncmp(de->d_name, "git-", 4))
+                       continue;
+               strcpy(path+dirlen, de->d_name);
+               if (stat(path, &st) || /* stat, not lstat */
+                   !S_ISREG(st.st_mode) ||
+                   !(st.st_mode & S_IXUSR))
+                       continue;
+
+               entlen = strlen(de->d_name);
+               if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
+                       entlen -= 4;
+
+               if (longest < entlen)
+                       longest = entlen;
+
+               add_cmdname(de->d_name + 4, entlen-4);
+       }
+       closedir(dir);
+
+       printf("git commands available in '%s'\n", exec_path);
+       printf("----------------------------");
+       mput_char('-', strlen(exec_path));
+       putchar('\n');
+       pretty_print_string_list(cmdname, longest - 4);
+       putchar('\n');
+}
+
+static void list_common_cmds_help(void)
+{
+       int i, longest = 0;
+
+       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               if (longest < strlen(common_cmds[i].name))
+                       longest = strlen(common_cmds[i].name);
+       }
+
+       puts("The most commonly used git commands are:");
+       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+               printf("    %s", common_cmds[i].name);
+               mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
+               puts(common_cmds[i].help);
+       }
+       puts("(use 'git help -a' to get a list of all installed git commands)");
+}
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+{
+       if (fmt) {
+               va_list ap;
+
+               va_start(ap, fmt);
+               printf("git: ");
+               vprintf(fmt, ap);
+               va_end(ap);
+               putchar('\n');
+       }
+       else
+               puts(git_usage);
+
+       if (exec_path) {
+               putchar('\n');
+               if (show_all)
+                       list_commands(exec_path, "git-*");
+               else
+                       list_common_cmds_help();
+        }
+
+       exit(1);
+}
+
+static void show_man_page(const char *git_cmd)
+{
+       const char *page;
+
+       if (!strncmp(git_cmd, "git", 3))
+               page = git_cmd;
+       else {
+               int page_len = strlen(git_cmd) + 4;
+               char *p = malloc(page_len + 1);
+               strcpy(p, "git-");
+               strcpy(p + 4, git_cmd);
+               p[page_len] = 0;
+               page = p;
+       }
+
+       execlp("man", "man", page, NULL);
+}
+
+int cmd_version(int argc, const char **argv, char **envp)
+{
+       printf("git version %s\n", git_version_string);
+       return 0;
+}
+
+int cmd_help(int argc, const char **argv, char **envp)
+{
+       const char *help_cmd = argv[1];
+       if (!help_cmd)
+               cmd_usage(0, git_exec_path(), NULL);
+       else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
+               cmd_usage(1, git_exec_path(), NULL);
+       else
+               show_man_page(help_cmd);
+       return 0;
+}
+
+
diff --git a/builtin-log.c b/builtin-log.c
new file mode 100644 (file)
index 0000000..69f2911
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Builtin "git log" and related commands (show, whatchanged)
+ *
+ * (C) Copyright 2006 Linus Torvalds
+ *              2006 Junio Hamano
+ */
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "log-tree.h"
+
+static int cmd_log_wc(int argc, const char **argv, char **envp,
+                     struct rev_info *rev)
+{
+       struct commit *commit;
+
+       rev->abbrev = DEFAULT_ABBREV;
+       rev->commit_format = CMIT_FMT_DEFAULT;
+       rev->verbose_header = 1;
+       argc = setup_revisions(argc, argv, rev, "HEAD");
+
+       if (argc > 1)
+               die("unrecognized argument: %s", argv[1]);
+
+       prepare_revision_walk(rev);
+       setup_pager();
+       while ((commit = get_revision(rev)) != NULL) {
+               log_tree_commit(rev, commit);
+               free(commit->buffer);
+               commit->buffer = NULL;
+       }
+       return 0;
+}
+
+int cmd_whatchanged(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.diff = 1;
+       rev.diffopt.recursive = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_show(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.diff = 1;
+       rev.diffopt.recursive = 1;
+       rev.combine_merges = 1;
+       rev.dense_combined_merges = 1;
+       rev.always_show_header = 1;
+       rev.ignore_merges = 0;
+       rev.no_walk = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
+
+int cmd_log(int argc, const char **argv, char **envp)
+{
+       struct rev_info rev;
+
+       init_revisions(&rev);
+       rev.always_show_header = 1;
+       rev.diffopt.recursive = 1;
+       return cmd_log_wc(argc, argv, envp, &rev);
+}
diff --git a/builtin.h b/builtin.h
new file mode 100644 (file)
index 0000000..47408a0
--- /dev/null
+++ b/builtin.h
@@ -0,0 +1,23 @@
+#ifndef BUILTIN_H
+#define BUILTIN_H
+
+#ifndef PATH_MAX
+# define PATH_MAX 4096
+#endif
+
+extern const char git_version_string[];
+
+void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
+#ifdef __GNUC__
+       __attribute__((__format__(__printf__, 3, 4), __noreturn__))
+#endif
+       ;
+
+extern int cmd_help(int argc, const char **argv, char **envp);
+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);
+
+#endif
diff --git a/cache.h b/cache.h
index 4d8fabc..a4f253e 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -135,6 +135,7 @@ extern const char *setup_git_directory(void);
 extern const char *prefix_path(const char *prefix, int len, const char *path);
 extern const char *prefix_filename(const char *prefix, int len, const char *path);
 extern void verify_filename(const char *prefix, const char *name);
+extern void verify_non_filename(const char *prefix, const char *name);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 
index 9445e86..ca36f5d 100644 (file)
@@ -5,6 +5,7 @@
 #include "diffcore.h"
 #include "quote.h"
 #include "xdiff-interface.h"
+#include "log-tree.h"
 
 static int uninteresting(struct diff_filepair *p)
 {
@@ -584,10 +585,20 @@ static void reuse_combine_diff(struct sline *sline, unsigned long cnt,
        sline->p_lno[i] = sline->p_lno[j];
 }
 
+static void dump_quoted_path(const char *prefix, const char *path)
+{
+       fputs(prefix, stdout);
+       if (quote_c_style(path, NULL, NULL, 0))
+               quote_c_style(path, NULL, stdout, 0);
+       else
+               printf("%s", path);
+       putchar('\n');
+}
+
 static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
-                          int dense, const char *header,
-                          struct diff_options *opt)
+                          int dense, struct rev_info *rev)
 {
+       struct diff_options *opt = &rev->diffopt;
        unsigned long result_size, cnt, lno;
        char *result, *cp;
        struct sline *sline; /* survived lines */
@@ -688,16 +699,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
        if (show_hunks || mode_differs || working_tree_file) {
                const char *abb;
 
-               if (header) {
-                       shown_header++;
-                       printf("%s%c", header, opt->line_termination);
-               }
-               printf("diff --%s ", dense ? "cc" : "combined");
-               if (quote_c_style(elem->path, NULL, NULL, 0))
-                       quote_c_style(elem->path, NULL, stdout, 0);
-               else
-                       printf("%s", elem->path);
-               putchar('\n');
+               if (rev->loginfo)
+                       show_log(rev, rev->loginfo, "\n");
+               dump_quoted_path(dense ? "diff --cc " : "diff --combined ", elem->path);
                printf("index ");
                for (i = 0; i < num_parent; i++) {
                        abb = find_unique_abbrev(elem->parent[i].sha1,
@@ -728,6 +732,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
                        }
                        putchar('\n');
                }
+               dump_quoted_path("--- a/", elem->path);
+               dump_quoted_path("+++ b/", elem->path);
                dump_sline(sline, cnt, num_parent);
        }
        free(result);
@@ -749,8 +755,9 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent,
 
 #define COLONS "::::::::::::::::::::::::::::::::"
 
-static void show_raw_diff(struct combine_diff_path *p, int num_parent, const char *header, struct diff_options *opt)
+static void show_raw_diff(struct combine_diff_path *p, int num_parent, struct rev_info *rev)
 {
+       struct diff_options *opt = &rev->diffopt;
        int i, offset;
        const char *prefix;
        int line_termination, inter_name_termination;
@@ -760,8 +767,8 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha
        if (!line_termination)
                inter_name_termination = 0;
 
-       if (header)
-               printf("%s%c", header, line_termination);
+       if (rev->loginfo)
+               show_log(rev, rev->loginfo, "\n");
 
        if (opt->output_format == DIFF_FORMAT_RAW) {
                offset = strlen(COLONS) - num_parent;
@@ -802,40 +809,44 @@ static void show_raw_diff(struct combine_diff_path *p, int num_parent, const cha
        }
 }
 
-int show_combined_diff(struct combine_diff_path *p,
+void show_combined_diff(struct combine_diff_path *p,
                       int num_parent,
                       int dense,
-                      const char *header,
-                      struct diff_options *opt)
+                      struct rev_info *rev)
 {
+       struct diff_options *opt = &rev->diffopt;
        if (!p->len)
-               return 0;
+               return;
        switch (opt->output_format) {
        case DIFF_FORMAT_RAW:
        case DIFF_FORMAT_NAME_STATUS:
        case DIFF_FORMAT_NAME:
-               show_raw_diff(p, num_parent, header, opt);
-               return 1;
-
-       default:
+               show_raw_diff(p, num_parent, rev);
+               return;
        case DIFF_FORMAT_PATCH:
-               return show_patch_diff(p, num_parent, dense, header, opt);
+               show_patch_diff(p, num_parent, dense, rev);
+               return;
+       default:
+               return;
        }
 }
 
-const char *diff_tree_combined_merge(const unsigned char *sha1,
-                            const char *header, int dense,
-                            struct diff_options *opt)
+void diff_tree_combined_merge(const unsigned char *sha1,
+                            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 do_diffstat;
 
+       do_diffstat = (opt->output_format == DIFF_FORMAT_DIFFSTAT ||
+                      opt->with_stat);
        diffopts = *opt;
-       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diffopts.with_raw = 0;
+       diffopts.with_stat = 0;
        diffopts.recursive = 1;
 
        /* count parents */
@@ -849,11 +860,24 @@ const char *diff_tree_combined_merge(const unsigned char *sha1,
             parents;
             parents = parents->next, i++) {
                struct commit *parent = parents->item;
+               /* show stat against the first parent even
+                * when doing combined diff.
+                */
+               if (i == 0 && do_diffstat)
+                       diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+               else
+                       diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
                diff_tree_sha1(parent->object.sha1, commit->object.sha1, "",
                               &diffopts);
                diffcore_std(&diffopts);
                paths = intersect_paths(paths, i, num_parent);
+
+               if (do_diffstat && rev->loginfo)
+                       show_log(rev, rev->loginfo,
+                                opt->with_stat ? "---\n" : "\n");
                diff_flush(&diffopts);
+               if (opt->with_stat)
+                       putchar('\n');
        }
 
        /* find out surviving paths */
@@ -866,17 +890,13 @@ const char *diff_tree_combined_merge(const unsigned char *sha1,
                        int saved_format = opt->output_format;
                        opt->output_format = DIFF_FORMAT_RAW;
                        for (p = paths; p; p = p->next) {
-                               if (show_combined_diff(p, num_parent, dense,
-                                                      header, opt))
-                                       header = NULL;
+                               show_combined_diff(p, num_parent, dense, rev);
                        }
                        opt->output_format = saved_format;
                        putchar(opt->line_termination);
                }
                for (p = paths; p; p = p->next) {
-                       if (show_combined_diff(p, num_parent, dense,
-                                              header, opt))
-                               header = NULL;
+                       show_combined_diff(p, num_parent, dense, rev);
                }
        }
 
@@ -886,5 +906,4 @@ const char *diff_tree_combined_merge(const unsigned char *sha1,
                paths = paths->next;
                free(tmp);
        }
-       return header;
 }
index 2595850..bad72e8 100644 (file)
@@ -91,7 +91,7 @@ int main(int argc, char **argv)
 
        git_config(git_default_config);
 
-       if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
+       if (argc < 2 || get_sha1(argv[1], tree_sha1) < 0)
                usage(commit_tree_usage);
 
        check_valid(tree_sha1, tree_type);
index 918c9ab..de142af 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -45,6 +45,8 @@ enum cmit_fmt {
        CMIT_FMT_FULL,
        CMIT_FMT_FULLER,
        CMIT_FMT_ONELINE,
+
+       CMIT_FMT_UNSPECIFIED,
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
index 4e1f0c2..253c48a 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/contrib/colordiff/README b/contrib/colordiff/README
new file mode 100644 (file)
index 0000000..2678fdf
--- /dev/null
@@ -0,0 +1,2 @@
+This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
+Ewart <davee@sungate.co.uk>, modified specifically for git.
diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl
new file mode 100755 (executable)
index 0000000..5789cfb
--- /dev/null
@@ -0,0 +1,196 @@
+#!/usr/bin/perl -w
+#
+# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
+
+########################################################################
+#                                                                      #
+# ColorDiff - a wrapper/replacment for 'diff' producing                #
+#             colourful output                                         #
+#                                                                      #
+# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk)              #
+#                                                                      #
+########################################################################
+#                                                                      #
+# This program is free software; you can redistribute it and/or modify #
+# it under the terms of the GNU General Public License as published by #
+# the Free Software Foundation; either version 2 of the License, or    #
+# (at your option) any later version.                                  #
+#                                                                      #
+# This program is distributed in the hope that it will be useful,      #
+# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
+# GNU General Public License for more details.                         #
+#                                                                      #
+# You should have received a copy of the GNU General Public License    #
+# along with this program; if not, write to the Free Software          #
+# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.            #
+#                                                                      #
+########################################################################
+
+use strict;
+use Getopt::Long qw(:config pass_through);
+use IPC::Open2;
+
+my $app_name     = 'colordiff';
+my $version      = '1.0.4';
+my $author       = 'Dave Ewart';
+my $author_email = 'davee@sungate.co.uk';
+my $app_www      = 'http://colordiff.sourceforge.net/';
+my $copyright    = '(C)2002-2004';
+my $show_banner  = 1;
+
+# ANSI sequences for colours
+my %colour;
+$colour{white}       = "\033[1;37m";
+$colour{yellow}      = "\033[1;33m";
+$colour{green}       = "\033[1;32m";
+$colour{blue}        = "\033[1;34m";
+$colour{cyan}        = "\033[1;36m";
+$colour{red}         = "\033[1;31m";
+$colour{magenta}     = "\033[1;35m";
+$colour{black}       = "\033[1;30m";
+$colour{darkwhite}   = "\033[0;37m";
+$colour{darkyellow}  = "\033[0;33m";
+$colour{darkgreen}   = "\033[0;32m";
+$colour{darkblue}    = "\033[0;34m";
+$colour{darkcyan}    = "\033[0;36m";
+$colour{darkred}     = "\033[0;31m";
+$colour{darkmagenta} = "\033[0;35m";
+$colour{darkblack}   = "\033[0;30m";
+$colour{OFF}         = "\033[0;0m";
+
+# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
+my $plain_text = $colour{OFF};
+my $file_old   = $colour{red};
+my $file_new   = $colour{blue};
+my $diff_stuff = $colour{magenta};
+
+# Locations for personal and system-wide colour configurations
+my $HOME   = $ENV{HOME};
+my $etcdir = '/etc';
+
+my ($setting, $value);
+my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
+my $config_file;
+
+foreach $config_file (@config_files) {
+    if (open(COLORDIFFRC, "<$config_file")) {
+        while (<COLORDIFFRC>) {
+            chop;
+            next if (/^#/ || /^$/);
+            s/\s+//g;
+            ($setting, $value) = split ('=');
+            if ($setting eq 'banner') {
+                if ($value eq 'no') {
+                    $show_banner = 0;
+                }
+                next;
+            }
+            if (!defined $colour{$value}) {
+                print "Invalid colour specification ($value) in $config_file\n";
+                next;
+            }
+            if ($setting eq 'plain') {
+                $plain_text = $colour{$value};
+            }
+            elsif ($setting eq 'oldtext') {
+                $file_old = $colour{$value};
+            }
+            elsif ($setting eq 'newtext') {
+                $file_new = $colour{$value};
+            }
+            elsif ($setting eq 'diffstuff') {
+                $diff_stuff = $colour{$value};
+            }
+            else {
+                print "Unknown option in $etcdir/colordiffrc: $setting\n";
+            }
+        }
+        close COLORDIFFRC;
+    }
+}
+
+# colordiff specfic options here.  Need to pre-declare if using variables
+GetOptions(
+    "no-banner" => sub { $show_banner = 0 },
+    "plain-text=s" => \&set_color,
+    "file-old=s"   => \&set_color,
+    "file-new=s"   => \&set_color,
+    "diff-stuff=s" => \&set_color
+);
+
+if ($show_banner == 1) {
+    print STDERR "$app_name $version ($app_www)\n";
+    print STDERR "$copyright $author, $author_email\n\n";
+}
+
+if (defined $ARGV[0]) {
+    # More reliable way of pulling in arguments
+    open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
+}
+else {
+    *INPUTSTREAM = \*STDIN;
+}
+
+my $record;
+my $nrecs           = 0;
+my $inside_file_old = 1;
+my $nparents        = undef;
+
+while (<INPUTSTREAM>) {
+    $nrecs++;
+    if (/^(\@\@+) -[-+0-9, ]+ \1/) {
+           print "$diff_stuff";
+           $nparents = length($1) - 1;
+    }
+    elsif (/^diff -/ || /^index / ||
+          /^old mode / || /^new mode / ||
+          /^deleted file mode / || /^new file mode / ||
+          /^similarity index / || /^dissimilarity index / ||
+          /^copy from / || /^copy to / ||
+          /^rename from / || /^rename to /) {
+           $nparents = undef;
+           print "$diff_stuff";
+    }
+    elsif (defined $nparents) {
+           if ($nparents == 1) {
+                   if (/^\+/) {
+                           print $file_new;
+                   }
+                   elsif (/^-/) {
+                           print $file_old;
+                   }
+                   else {
+                           print $plain_text;
+                   }
+           }
+           elsif (/^ {$nparents}/) {
+                   print "$plain_text";
+           }
+           elsif (/^[+ ]{$nparents}/) {
+                   print "$file_new";
+           }
+           elsif (/^[- ]{$nparents}/) {
+                   print "$file_old";
+           }
+           else {
+                   print $plain_text;
+           }
+    }
+    elsif (/^--- / || /^\+\+\+ /) {
+           print $diff_stuff;
+    }
+    else {
+           print "$plain_text";
+    }
+    s/$/$colour{OFF}/;
+    print "$_";
+}
+close INPUTSTREAM;
+
+sub set_color {
+    my ($type, $color) = @_;
+
+    $type =~ s/-/_/;
+    eval "\$$type = \$colour{$color}";
+}
index 3e7f5f1..b9d193d 100644 (file)
  */
 #include "cache.h"
 #include "diff.h"
+#include "commit.h"
+#include "revision.h"
 
 static const char diff_files_usage[] =
 "git-diff-files [-q] [-0/-1/2/3 |-c|--cc] [<common diff options>] [<path>...]"
 COMMON_DIFF_OPTIONS_HELP;
 
-static struct diff_options diff_options;
-static int silent = 0;
-static int diff_unmerged_stage = 2;
-static int combine_merges = 0;
-static int dense_combined_merges = 0;
-
-static void show_unmerge(const char *path)
-{
-       diff_unmerge(&diff_options, path);
-}
-
-static void show_file(int pfx, struct cache_entry *ce)
-{
-       diff_addremove(&diff_options, pfx, ntohl(ce->ce_mode),
-                      ce->sha1, ce->name, NULL);
-}
-
-static void show_modified(int oldmode, int mode,
-                         const unsigned char *old_sha1, const unsigned char *sha1,
-                         char *path)
-{
-       diff_change(&diff_options, oldmode, mode, old_sha1, sha1, path, NULL);
-}
-
 int main(int argc, const char **argv)
 {
-       const char **pathspec;
-       const char *prefix = setup_git_directory();
-       int entries, i;
+       struct rev_info rev;
+       int silent = 0;
 
        git_config(git_diff_config);
-       diff_setup(&diff_options);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
        while (1 < argc && argv[1][0] == '-') {
-               if (!strcmp(argv[1], "--")) {
-                       argv++;
-                       argc--;
-                       break;
-               }
-               if (!strcmp(argv[1], "-0"))
-                       diff_unmerged_stage = 0;
-               else if (!strcmp(argv[1], "-1"))
-                       diff_unmerged_stage = 1;
-               else if (!strcmp(argv[1], "-2"))
-                       diff_unmerged_stage = 2;
-               else if (!strcmp(argv[1], "-3"))
-                       diff_unmerged_stage = 3;
-               else if (!strcmp(argv[1], "--base"))
-                       diff_unmerged_stage = 1;
+               if (!strcmp(argv[1], "--base"))
+                       rev.max_count = 1;
                else if (!strcmp(argv[1], "--ours"))
-                       diff_unmerged_stage = 2;
+                       rev.max_count = 2;
                else if (!strcmp(argv[1], "--theirs"))
-                       diff_unmerged_stage = 3;
+                       rev.max_count = 3;
                else if (!strcmp(argv[1], "-q"))
                        silent = 1;
-               else if (!strcmp(argv[1], "-r"))
-                       ; /* no-op */
-               else if (!strcmp(argv[1], "-s"))
-                       ; /* no-op */
-               else if (!strcmp(argv[1], "-c"))
-                       combine_merges = 1;
-               else if (!strcmp(argv[1], "--cc"))
-                       dense_combined_merges = combine_merges = 1;
-               else {
-                       int diff_opt_cnt;
-                       diff_opt_cnt = diff_opt_parse(&diff_options,
-                                                     argv+1, argc-1);
-                       if (diff_opt_cnt < 0)
-                               usage(diff_files_usage);
-                       else if (diff_opt_cnt) {
-                               argv += diff_opt_cnt;
-                               argc -= diff_opt_cnt;
-                               continue;
-                       }
-                       else
-                               usage(diff_files_usage);
-               }
+               else
+                       usage(diff_files_usage);
                argv++; argc--;
        }
-       if (dense_combined_merges)
-               diff_options.output_format = DIFF_FORMAT_PATCH;
-
-       /* Find the directory, and set up the pathspec */
-       pathspec = get_pathspec(prefix, argv + 1);
-       entries = read_cache();
-
-       if (diff_setup_done(&diff_options) < 0)
+       /*
+        * Make sure there are NO revision (i.e. pending object) parameter,
+        * rev.max_count is reasonable (0 <= n <= 3),
+        * there is no other revision filtering parameters.
+        */
+       if (rev.pending_objects ||
+           rev.min_age != -1 || rev.max_age != -1)
                usage(diff_files_usage);
-
-       /* At this point, if argc == 1, then we are doing everything.
-        * Otherwise argv[1] .. argv[argc-1] have the explicit paths.
+       /*
+        * Backward compatibility wart - "diff-files -s" used to
+        * defeat the common diff option "-s" which asked for
+        * DIFF_FORMAT_NO_OUTPUT.
         */
-       if (entries < 0) {
-               perror("read_cache");
-               exit(1);
-       }
-
-       for (i = 0; i < entries; i++) {
-               struct stat st;
-               unsigned int oldmode, newmode;
-               struct cache_entry *ce = active_cache[i];
-               int changed;
-
-               if (!ce_path_match(ce, pathspec))
-                       continue;
-
-               if (ce_stage(ce)) {
-                       struct {
-                               struct combine_diff_path p;
-                               struct combine_diff_parent filler[5];
-                       } combine;
-                       int num_compare_stages = 0;
-
-                       combine.p.next = NULL;
-                       combine.p.len = ce_namelen(ce);
-                       combine.p.path = xmalloc(combine.p.len + 1);
-                       memcpy(combine.p.path, ce->name, combine.p.len);
-                       combine.p.path[combine.p.len] = 0;
-                       combine.p.mode = 0;
-                       memset(combine.p.sha1, 0, 20);
-                       memset(&combine.p.parent[0], 0,
-                              sizeof(combine.filler));
-
-                       while (i < entries) {
-                               struct cache_entry *nce = active_cache[i];
-                               int stage;
-
-                               if (strcmp(ce->name, nce->name))
-                                       break;
-
-                               /* Stage #2 (ours) is the first parent,
-                                * stage #3 (theirs) is the second.
-                                */
-                               stage = ce_stage(nce);
-                               if (2 <= stage) {
-                                       int mode = ntohl(nce->ce_mode);
-                                       num_compare_stages++;
-                                       memcpy(combine.p.parent[stage-2].sha1,
-                                              nce->sha1, 20);
-                                       combine.p.parent[stage-2].mode =
-                                               canon_mode(mode);
-                                       combine.p.parent[stage-2].status =
-                                               DIFF_STATUS_MODIFIED;
-                               }
-
-                               /* diff against the proper unmerged stage */
-                               if (stage == diff_unmerged_stage)
-                                       ce = nce;
-                               i++;
-                       }
-                       /*
-                        * Compensate for loop update
-                        */
-                       i--;
-
-                       if (combine_merges && num_compare_stages == 2) {
-                               show_combined_diff(&combine.p, 2,
-                                                  dense_combined_merges,
-                                                  NULL,
-                                                  &diff_options);
-                               free(combine.p.path);
-                               continue;
-                       }
-                       free(combine.p.path);
-
-                       /*
-                        * Show the diff for the 'ce' if we found the one
-                        * from the desired stage.
-                        */
-                       show_unmerge(ce->name);
-                       if (ce_stage(ce) != diff_unmerged_stage)
-                               continue;
-               }
-
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno != ENOENT && errno != ENOTDIR) {
-                               perror(ce->name);
-                               continue;
-                       }
-                       if (silent)
-                               continue;
-                       show_file('-', ce);
-                       continue;
-               }
-               changed = ce_match_stat(ce, &st, 0);
-               if (!changed && !diff_options.find_copies_harder)
-                       continue;
-               oldmode = ntohl(ce->ce_mode);
-
-               newmode = canon_mode(st.st_mode);
-               if (!trust_executable_bit &&
-                   S_ISREG(newmode) && S_ISREG(oldmode) &&
-                   ((newmode ^ oldmode) == 0111))
-                       newmode = oldmode;
-               show_modified(oldmode, newmode,
-                             ce->sha1, (changed ? null_sha1 : ce->sha1),
-                             ce->name);
-       }
-       diffcore_std(&diff_options);
-       diff_flush(&diff_options);
-       return 0;
+       if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT)
+               rev.diffopt.output_format = DIFF_FORMAT_RAW;
+       return run_diff_files(&rev, silent);
 }
index e376d65..8c9f601 100644 (file)
@@ -1,166 +1,7 @@
 #include "cache.h"
-#include "tree.h"
 #include "diff.h"
-
-static int cached_only = 0;
-static int match_nonexisting = 0;
-static struct diff_options diff_options;
-
-/* A file entry went away or appeared */
-static void show_file(const char *prefix,
-                     struct cache_entry *ce,
-                     unsigned char *sha1, unsigned int mode)
-{
-       diff_addremove(&diff_options, prefix[0], ntohl(mode),
-                      sha1, ce->name, NULL);
-}
-
-static int get_stat_data(struct cache_entry *ce,
-                        unsigned char ** sha1p, unsigned int *modep)
-{
-       unsigned char *sha1 = ce->sha1;
-       unsigned int mode = ce->ce_mode;
-
-       if (!cached_only) {
-               static unsigned char no_sha1[20];
-               int changed;
-               struct stat st;
-               if (lstat(ce->name, &st) < 0) {
-                       if (errno == ENOENT && match_nonexisting) {
-                               *sha1p = sha1;
-                               *modep = mode;
-                               return 0;
-                       }
-                       return -1;
-               }
-               changed = ce_match_stat(ce, &st, 0);
-               if (changed) {
-                       mode = create_ce_mode(st.st_mode);
-                       if (!trust_executable_bit && S_ISREG(st.st_mode))
-                               mode = ce->ce_mode;
-                       sha1 = no_sha1;
-               }
-       }
-
-       *sha1p = sha1;
-       *modep = mode;
-       return 0;
-}
-
-static void show_new_file(struct cache_entry *new)
-{
-       unsigned char *sha1;
-       unsigned int mode;
-
-       /* New file in the index: it might actually be different in
-        * the working copy.
-        */
-       if (get_stat_data(new, &sha1, &mode) < 0)
-               return;
-
-       show_file("+", new, sha1, mode);
-}
-
-static int show_modified(struct cache_entry *old,
-                        struct cache_entry *new,
-                        int report_missing)
-{
-       unsigned int mode, oldmode;
-       unsigned char *sha1;
-
-       if (get_stat_data(new, &sha1, &mode) < 0) {
-               if (report_missing)
-                       show_file("-", old, old->sha1, old->ce_mode);
-               return -1;
-       }
-
-       oldmode = old->ce_mode;
-       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
-           !diff_options.find_copies_harder)
-               return 0;
-
-       mode = ntohl(mode);
-       oldmode = ntohl(oldmode);
-
-       diff_change(&diff_options, oldmode, mode,
-                   old->sha1, sha1, old->name, NULL);
-       return 0;
-}
-
-static int diff_cache(struct cache_entry **ac, int entries, const char **pathspec)
-{
-       while (entries) {
-               struct cache_entry *ce = *ac;
-               int same = (entries > 1) && ce_same_name(ce, ac[1]);
-
-               if (!ce_path_match(ce, pathspec))
-                       goto skip_entry;
-
-               switch (ce_stage(ce)) {
-               case 0:
-                       /* No stage 1 entry? That means it's a new file */
-                       if (!same) {
-                               show_new_file(ce);
-                               break;
-                       }
-                       /* Show difference between old and new */
-                       show_modified(ac[1], ce, 1);
-                       break;
-               case 1:
-                       /* No stage 3 (merge) entry? That means it's been deleted */
-                       if (!same) {
-                               show_file("-", ce, ce->sha1, ce->ce_mode);
-                               break;
-                       }
-                       /* We come here with ce pointing at stage 1
-                        * (original tree) and ac[1] pointing at stage
-                        * 3 (unmerged).  show-modified with
-                        * report-missing set to false does not say the
-                        * file is deleted but reports true if work
-                        * tree does not have it, in which case we
-                        * fall through to report the unmerged state.
-                        * Otherwise, we show the differences between
-                        * the original tree and the work tree.
-                        */
-                       if (!cached_only && !show_modified(ce, ac[1], 0))
-                               break;
-                       /* fallthru */
-               case 3:
-                       diff_unmerge(&diff_options, ce->name);
-                       break;
-
-               default:
-                       die("impossible cache entry stage");
-               }
-
-skip_entry:
-               /*
-                * Ignore all the different stages for this file,
-                * we've handled the relevant cases now.
-                */
-               do {
-                       ac++;
-                       entries--;
-               } while (entries && ce_same_name(ce, ac[0]));
-       }
-       return 0;
-}
-
-/*
- * This turns all merge entries into "stage 3". That guarantees that
- * when we read in the new tree (into "stage 1"), we won't lose sight
- * of the fact that we had unmerged entries.
- */
-static void mark_merge_entries(void)
-{
-       int i;
-       for (i = 0; i < active_nr; i++) {
-               struct cache_entry *ce = active_cache[i];
-               if (!ce_stage(ce))
-                       continue;
-               ce->ce_flags |= htons(CE_STAGEMASK);
-       }
-}
+#include "commit.h"
+#include "revision.h"
 
 static const char diff_cache_usage[] =
 "git-diff-index [-m] [--cached] "
@@ -169,85 +10,29 @@ COMMON_DIFF_OPTIONS_HELP;
 
 int main(int argc, const char **argv)
 {
-       const char *tree_name = NULL;
-       unsigned char sha1[20];
-       const char *prefix = setup_git_directory();
-       const char **pathspec = NULL;
-       struct tree *tree;
-       int ret;
-       int allow_options = 1;
+       struct rev_info rev;
+       int cached = 0;
        int i;
 
        git_config(git_diff_config);
-       diff_setup(&diff_options);
+       init_revisions(&rev);
+       rev.abbrev = 0;
+
+       argc = setup_revisions(argc, argv, &rev, NULL);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               int diff_opt_cnt;
-
-               if (!allow_options || *arg != '-') {
-                       if (tree_name)
-                               break;
-                       tree_name = arg;
-                       continue;
-               }
                        
-               if (!strcmp(arg, "--")) {
-                       allow_options = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "-r")) {
-                       /* We accept the -r flag just to look like git-diff-tree */
-                       continue;
-               }
-               if (!strcmp(arg, "--cc"))
-                       /*
-                        * I _think_ "diff-index --cached HEAD" with an
-                        * unmerged index could show something else
-                        * later, but pretend --cc is the same as -p for
-                        * now.  "git diff" uses --cc by default.
-                        */
-                       argv[i] = arg = "-p";
-               diff_opt_cnt = diff_opt_parse(&diff_options, argv + i,
-                                             argc - i);
-               if (diff_opt_cnt < 0)
+               if (!strcmp(arg, "--cached"))
+                       cached = 1;
+               else
                        usage(diff_cache_usage);
-               else if (diff_opt_cnt) {
-                       i += diff_opt_cnt - 1;
-                       continue;
-               }
-
-               if (!strcmp(arg, "-m")) {
-                       match_nonexisting = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--cached")) {
-                       cached_only = 1;
-                       continue;
-               }
-               usage(diff_cache_usage);
        }
-
-       pathspec = get_pathspec(prefix, argv + i);
-
-       if (diff_setup_done(&diff_options) < 0)
-               usage(diff_cache_usage);
-
-       if (!tree_name || get_sha1(tree_name, sha1))
+       /*
+        * Make sure there is one revision (i.e. pending object),
+        * and there is no revision filtering parameters.
+        */
+       if (!rev.pending_objects || rev.pending_objects->next ||
+           rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1)
                usage(diff_cache_usage);
-
-       read_cache();
-
-       mark_merge_entries();
-
-       tree = parse_tree_indirect(sha1);
-       if (!tree)
-               die("bad tree object %s", tree_name);
-       if (read_tree(tree, 1, pathspec))
-               die("unable to read tree object %s", tree_name);
-
-       ret = diff_cache(active_cache, active_nr, pathspec);
-
-       diffcore_std(&diff_options);
-       diff_flush(&diff_options);
-       return ret;
+       return run_diff_index(&rev, cached);
 }
diff --git a/diff-lib.c b/diff-lib.c
new file mode 100644 (file)
index 0000000..2183b41
--- /dev/null
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2005 Junio C Hamano
+ */
+#include "cache.h"
+#include "quote.h"
+#include "commit.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "revision.h"
+
+/*
+ * diff-files
+ */
+
+int run_diff_files(struct rev_info *revs, int silent_on_removed)
+{
+       int entries, i;
+       int diff_unmerged_stage = revs->max_count;
+
+       if (diff_unmerged_stage < 0)
+               diff_unmerged_stage = 2;
+       entries = read_cache();
+       if (entries < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       for (i = 0; i < entries; i++) {
+               struct stat st;
+               unsigned int oldmode, newmode;
+               struct cache_entry *ce = active_cache[i];
+               int changed;
+
+               if (!ce_path_match(ce, revs->prune_data))
+                       continue;
+
+               if (ce_stage(ce)) {
+                       struct {
+                               struct combine_diff_path p;
+                               struct combine_diff_parent filler[5];
+                       } combine;
+                       int num_compare_stages = 0;
+
+                       combine.p.next = NULL;
+                       combine.p.len = ce_namelen(ce);
+                       combine.p.path = xmalloc(combine.p.len + 1);
+                       memcpy(combine.p.path, ce->name, combine.p.len);
+                       combine.p.path[combine.p.len] = 0;
+                       combine.p.mode = 0;
+                       memset(combine.p.sha1, 0, 20);
+                       memset(&combine.p.parent[0], 0,
+                              sizeof(combine.filler));
+
+                       while (i < entries) {
+                               struct cache_entry *nce = active_cache[i];
+                               int stage;
+
+                               if (strcmp(ce->name, nce->name))
+                                       break;
+
+                               /* Stage #2 (ours) is the first parent,
+                                * stage #3 (theirs) is the second.
+                                */
+                               stage = ce_stage(nce);
+                               if (2 <= stage) {
+                                       int mode = ntohl(nce->ce_mode);
+                                       num_compare_stages++;
+                                       memcpy(combine.p.parent[stage-2].sha1,
+                                              nce->sha1, 20);
+                                       combine.p.parent[stage-2].mode =
+                                               canon_mode(mode);
+                                       combine.p.parent[stage-2].status =
+                                               DIFF_STATUS_MODIFIED;
+                               }
+
+                               /* diff against the proper unmerged stage */
+                               if (stage == diff_unmerged_stage)
+                                       ce = nce;
+                               i++;
+                       }
+                       /*
+                        * Compensate for loop update
+                        */
+                       i--;
+
+                       if (revs->combine_merges && num_compare_stages == 2) {
+                               show_combined_diff(&combine.p, 2,
+                                                  revs->dense_combined_merges,
+                                                  revs);
+                               free(combine.p.path);
+                               continue;
+                       }
+                       free(combine.p.path);
+
+                       /*
+                        * Show the diff for the 'ce' if we found the one
+                        * from the desired stage.
+                        */
+                       diff_unmerge(&revs->diffopt, ce->name);
+                       if (ce_stage(ce) != diff_unmerged_stage)
+                               continue;
+               }
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT && errno != ENOTDIR) {
+                               perror(ce->name);
+                               continue;
+                       }
+                       if (silent_on_removed)
+                               continue;
+                       diff_addremove(&revs->diffopt, '-', ntohl(ce->ce_mode),
+                                      ce->sha1, ce->name, NULL);
+                       continue;
+               }
+               changed = ce_match_stat(ce, &st, 0);
+               if (!changed && !revs->diffopt.find_copies_harder)
+                       continue;
+               oldmode = ntohl(ce->ce_mode);
+
+               newmode = canon_mode(st.st_mode);
+               if (!trust_executable_bit &&
+                   S_ISREG(newmode) && S_ISREG(oldmode) &&
+                   ((newmode ^ oldmode) == 0111))
+                       newmode = oldmode;
+               diff_change(&revs->diffopt, oldmode, newmode,
+                           ce->sha1, (changed ? null_sha1 : ce->sha1),
+                           ce->name, NULL);
+
+       }
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return 0;
+}
+
+/*
+ * diff-index
+ */
+
+/* A file entry went away or appeared */
+static void diff_index_show_file(struct rev_info *revs,
+                                const char *prefix,
+                                struct cache_entry *ce,
+                                unsigned char *sha1, unsigned int mode)
+{
+       diff_addremove(&revs->diffopt, prefix[0], ntohl(mode),
+                      sha1, ce->name, NULL);
+}
+
+static int get_stat_data(struct cache_entry *ce,
+                        unsigned char **sha1p,
+                        unsigned int *modep,
+                        int cached, int match_missing)
+{
+       unsigned char *sha1 = ce->sha1;
+       unsigned int mode = ce->ce_mode;
+
+       if (!cached) {
+               static unsigned char no_sha1[20];
+               int changed;
+               struct stat st;
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno == ENOENT && match_missing) {
+                               *sha1p = sha1;
+                               *modep = mode;
+                               return 0;
+                       }
+                       return -1;
+               }
+               changed = ce_match_stat(ce, &st, 0);
+               if (changed) {
+                       mode = create_ce_mode(st.st_mode);
+                       if (!trust_executable_bit && S_ISREG(st.st_mode))
+                               mode = ce->ce_mode;
+                       sha1 = no_sha1;
+               }
+       }
+
+       *sha1p = sha1;
+       *modep = mode;
+       return 0;
+}
+
+static void show_new_file(struct rev_info *revs,
+                         struct cache_entry *new,
+                         int cached, int match_missing)
+{
+       unsigned char *sha1;
+       unsigned int mode;
+
+       /* New file in the index: it might actually be different in
+        * the working copy.
+        */
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0)
+               return;
+
+       diff_index_show_file(revs, "+", new, sha1, mode);
+}
+
+static int show_modified(struct rev_info *revs,
+                        struct cache_entry *old,
+                        struct cache_entry *new,
+                        int report_missing,
+                        int cached, int match_missing)
+{
+       unsigned int mode, oldmode;
+       unsigned char *sha1;
+
+       if (get_stat_data(new, &sha1, &mode, cached, match_missing) < 0) {
+               if (report_missing)
+                       diff_index_show_file(revs, "-", old,
+                                            old->sha1, old->ce_mode);
+               return -1;
+       }
+
+       oldmode = old->ce_mode;
+       if (mode == oldmode && !memcmp(sha1, old->sha1, 20) &&
+           !revs->diffopt.find_copies_harder)
+               return 0;
+
+       mode = ntohl(mode);
+       oldmode = ntohl(oldmode);
+
+       diff_change(&revs->diffopt, oldmode, mode,
+                   old->sha1, sha1, old->name, NULL);
+       return 0;
+}
+
+static int diff_cache(struct rev_info *revs,
+                     struct cache_entry **ac, int entries,
+                     const char **pathspec,
+                     int cached, int match_missing)
+{
+       while (entries) {
+               struct cache_entry *ce = *ac;
+               int same = (entries > 1) && ce_same_name(ce, ac[1]);
+
+               if (!ce_path_match(ce, pathspec))
+                       goto skip_entry;
+
+               switch (ce_stage(ce)) {
+               case 0:
+                       /* No stage 1 entry? That means it's a new file */
+                       if (!same) {
+                               show_new_file(revs, ce, cached, match_missing);
+                               break;
+                       }
+                       /* Show difference between old and new */
+                       show_modified(revs,ac[1], ce, 1,
+                                     cached, match_missing);
+                       break;
+               case 1:
+                       /* No stage 3 (merge) entry?
+                        * That means it's been deleted.
+                        */
+                       if (!same) {
+                               diff_index_show_file(revs, "-", ce,
+                                                    ce->sha1, ce->ce_mode);
+                               break;
+                       }
+                       /* We come here with ce pointing at stage 1
+                        * (original tree) and ac[1] pointing at stage
+                        * 3 (unmerged).  show-modified with
+                        * report-missing set to false does not say the
+                        * file is deleted but reports true if work
+                        * tree does not have it, in which case we
+                        * fall through to report the unmerged state.
+                        * Otherwise, we show the differences between
+                        * the original tree and the work tree.
+                        */
+                       if (!cached &&
+                           !show_modified(revs, ce, ac[1], 0,
+                                          cached, match_missing))
+                               break;
+                       /* fallthru */
+               case 3:
+                       diff_unmerge(&revs->diffopt, ce->name);
+                       break;
+
+               default:
+                       die("impossible cache entry stage");
+               }
+
+skip_entry:
+               /*
+                * Ignore all the different stages for this file,
+                * we've handled the relevant cases now.
+                */
+               do {
+                       ac++;
+                       entries--;
+               } while (entries && ce_same_name(ce, ac[0]));
+       }
+       return 0;
+}
+
+/*
+ * This turns all merge entries into "stage 3". That guarantees that
+ * when we read in the new tree (into "stage 1"), we won't lose sight
+ * of the fact that we had unmerged entries.
+ */
+static void mark_merge_entries(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (!ce_stage(ce))
+                       continue;
+               ce->ce_flags |= htons(CE_STAGEMASK);
+       }
+}
+
+int run_diff_index(struct rev_info *revs, int cached)
+{
+       int ret;
+       struct object *ent;
+       struct tree *tree;
+       const char *tree_name;
+       int match_missing = 0;
+
+       /* 
+        * Backward compatibility wart - "diff-index -m" does
+        * not mean "do not ignore merges", but totally different.
+        */
+       if (!revs->ignore_merges)
+               match_missing = 1;
+
+       if (read_cache() < 0) {
+               perror("read_cache");
+               return -1;
+       }
+       mark_merge_entries();
+
+       ent = revs->pending_objects->item;
+       tree_name = revs->pending_objects->name;
+       tree = parse_tree_indirect(ent->sha1);
+       if (!tree)
+               return error("bad tree object %s", tree_name);
+       if (read_tree(tree, 1, revs->prune_data))
+               return error("unable to read tree object %s", tree_name);
+       ret = diff_cache(revs, active_cache, active_nr, revs->prune_data,
+                        cached, match_missing);
+       diffcore_std(&revs->diffopt);
+       diff_flush(&revs->diffopt);
+       return ret;
+}
index d1c61c8..7207867 100644 (file)
@@ -3,7 +3,7 @@
 #include "commit.h"
 #include "log-tree.h"
 
-static struct log_tree_opt log_tree_opt;
+static struct rev_info log_tree_opt;
 
 static int diff_tree_commit_sha1(const unsigned char *sha1)
 {
@@ -62,47 +62,21 @@ int main(int argc, const char **argv)
 {
        int nr_sha1;
        char line[1000];
-       unsigned char sha1[2][20];
-       const char *prefix = setup_git_directory();
-       static struct log_tree_opt *opt = &log_tree_opt;
+       struct object *tree1, *tree2;
+       static struct rev_info *opt = &log_tree_opt;
+       struct object_list *list;
        int read_stdin = 0;
 
        git_config(git_diff_config);
        nr_sha1 = 0;
-       init_log_tree_opt(opt);
+       init_revisions(opt);
+       opt->abbrev = 0;
+       opt->diff = 1;
+       argc = setup_revisions(argc, argv, opt, NULL);
 
-       for (;;) {
-               int opt_cnt;
-               const char *arg;
+       while (--argc > 0) {
+               const char *arg = *++argv;
 
-               argv++;
-               argc--;
-               arg = *argv;
-               if (!arg)
-                       break;
-
-               if (*arg != '-') {
-                       if (nr_sha1 < 2 && !get_sha1(arg, sha1[nr_sha1])) {
-                               nr_sha1++;
-                               continue;
-                       }
-                       break;
-               }
-
-               opt_cnt = log_tree_opt_parse(opt, argv, argc);
-               if (opt_cnt < 0)
-                       usage(diff_tree_usage);
-               else if (opt_cnt) {
-                       argv += opt_cnt - 1;
-                       argc -= opt_cnt - 1;
-                       continue;
-               }
-
-               if (!strcmp(arg, "--")) {
-                       argv++;
-                       argc--;
-                       break;
-               }
                if (!strcmp(arg, "--stdin")) {
                        read_stdin = 1;
                        continue;
@@ -110,15 +84,36 @@ int main(int argc, const char **argv)
                usage(diff_tree_usage);
        }
 
-       if (opt->combine_merges)
-               opt->ignore_merges = 0;
-
-       /* We can only do dense combined merges with diff output */
-       if (opt->dense_combined_merges)
-               opt->diffopt.output_format = DIFF_FORMAT_PATCH;
-
-       diff_tree_setup_paths(get_pathspec(prefix, argv), &opt->diffopt);
-       diff_setup_done(&opt->diffopt);
+       /*
+        * NOTE! "setup_revisions()" will have inserted the revisions
+        * it parsed in reverse order. So if you do
+        *
+        *      git-diff-tree a b
+        *
+        * the commit list will be "b" -> "a" -> NULL, so we reverse
+        * the order of the objects if the first one is not marked
+        * UNINTERESTING.
+        */
+       nr_sha1 = 0;
+       list = opt->pending_objects;
+       if (list) {
+               nr_sha1++;
+               tree1 = list->item;
+               list = list->next;
+               if (list) {
+                       nr_sha1++;
+                       tree2 = tree1;
+                       tree1 = list->item;
+                       if (list->next)
+                               usage(diff_tree_usage);
+                       /* Switch them around if the second one was uninteresting.. */
+                       if (tree2->flags & UNINTERESTING) {
+                               struct object *tmp = tree2;
+                               tree2 = tree1;
+                               tree1 = tmp;
+                       }
+               }
+       }
 
        switch (nr_sha1) {
        case 0:
@@ -126,10 +121,12 @@ int main(int argc, const char **argv)
                        usage(diff_tree_usage);
                break;
        case 1:
-               diff_tree_commit_sha1(sha1[0]);
+               diff_tree_commit_sha1(tree1->sha1);
                break;
        case 2:
-               diff_tree_sha1(sha1[0], sha1[1], "", &opt->diffopt);
+               diff_tree_sha1(tree1->sha1,
+                              tree2->sha1,
+                              "", &opt->diffopt);
                log_tree_diff_flush(opt);
                break;
        }
diff --git a/diff.c b/diff.c
index 903afa1..c845c87 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -195,6 +195,56 @@ static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
        return 0;
 }
 
+static char *pprint_rename(const char *a, const char *b)
+{
+       const char *old = a;
+       const char *new = b;
+       char *name = NULL;
+       int pfx_length, sfx_length;
+       int len_a = strlen(a);
+       int len_b = strlen(b);
+
+       /* Find common prefix */
+       pfx_length = 0;
+       while (*old && *new && *old == *new) {
+               if (*old == '/')
+                       pfx_length = old - a + 1;
+               old++;
+               new++;
+       }
+
+       /* Find common suffix */
+       old = a + len_a;
+       new = b + len_b;
+       sfx_length = 0;
+       while (a <= old && b <= new && *old == *new) {
+               if (*old == '/')
+                       sfx_length = len_a - (old - a);
+               old--;
+               new--;
+       }
+
+       /*
+        * pfx{mid-a => mid-b}sfx
+        * {pfx-a => pfx-b}sfx
+        * pfx{sfx-a => sfx-b}
+        * name-a => name-b
+        */
+       if (pfx_length + sfx_length) {
+               name = xmalloc(len_a + len_b - pfx_length - sfx_length + 7);
+               sprintf(name, "%.*s{%.*s => %.*s}%s",
+                       pfx_length, a,
+                       len_a - pfx_length - sfx_length, a + pfx_length,
+                       len_b - pfx_length - sfx_length, b + pfx_length,
+                       a + len_a - sfx_length);
+       }
+       else {
+               name = xmalloc(len_a + len_b + 5);
+               sprintf(name, "%s => %s", a, b);
+       }
+       return name;
+}
+
 struct diffstat_t {
        struct xdiff_emit_state xm;
 
@@ -204,12 +254,14 @@ struct diffstat_t {
                char *name;
                unsigned is_unmerged:1;
                unsigned is_binary:1;
+               unsigned is_renamed:1;
                unsigned int added, deleted;
        } **files;
 };
 
 static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
-               const char *name)
+                                         const char *name_a,
+                                         const char *name_b)
 {
        struct diffstat_file *x;
        x = xcalloc(sizeof (*x), 1);
@@ -219,7 +271,12 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
                                diffstat->alloc * sizeof(x));
        }
        diffstat->files[diffstat->nr++] = x;
-       x->name = strdup(name);
+       if (name_b) {
+               x->name = pprint_rename(name_a, name_b);
+               x->is_renamed = 1;
+       }
+       else
+               x->name = strdup(name_a);
        return x;
 }
 
@@ -305,7 +362,8 @@ static void show_stats(struct diffstat_t* data)
                        printf(" %s%-*s |  Unmerged\n", prefix, len, name);
                        goto free_diffstat_file;
                }
-               else if (added + deleted == 0) {
+               else if (!data->files[i]->is_renamed &&
+                        (added + deleted == 0)) {
                        total_files--;
                        goto free_diffstat_file;
                }
@@ -425,19 +483,27 @@ static void builtin_diff(const char *name_a,
 }
 
 static void builtin_diffstat(const char *name_a, const char *name_b,
-               struct diff_filespec *one, struct diff_filespec *two,
-               struct diffstat_t *diffstat)
+                            struct diff_filespec *one,
+                            struct diff_filespec *two,
+                            struct diffstat_t *diffstat,
+                            int complete_rewrite)
 {
        mmfile_t mf1, mf2;
        struct diffstat_file *data;
 
-       data = diffstat_add(diffstat, name_a ? name_a : name_b);
+       data = diffstat_add(diffstat, name_a, name_b);
 
        if (!one || !two) {
                data->is_unmerged = 1;
                return;
        }
-
+       if (complete_rewrite) {
+               diff_populate_filespec(one, 0);
+               diff_populate_filespec(two, 0);
+               data->deleted = count_lines(one->data, one->size);
+               data->added = count_lines(two->data, two->size);
+               return;
+       }
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
@@ -952,14 +1018,12 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        }
 
        if (memcmp(one->sha1, two->sha1, 20)) {
-               char one_sha1[41];
                int abbrev = o->full_index ? 40 : DEFAULT_ABBREV;
-               memcpy(one_sha1, sha1_to_hex(one->sha1), 41);
 
                len += snprintf(msg + len, sizeof(msg) - len,
                                "index %.*s..%.*s",
-                               abbrev, one_sha1, abbrev,
-                               sha1_to_hex(two->sha1));
+                               abbrev, sha1_to_hex(one->sha1),
+                               abbrev, sha1_to_hex(two->sha1));
                if (one->mode == two->mode)
                        len += snprintf(msg + len, sizeof(msg) - len,
                                        " %06o", one->mode);
@@ -992,14 +1056,15 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 }
 
 static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
-               struct diffstat_t *diffstat)
+                        struct diffstat_t *diffstat)
 {
        const char *name;
        const char *other;
+       int complete_rewrite = 0;
 
        if (DIFF_PAIR_UNMERGED(p)) {
                /* unmerged */
-               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat, 0);
                return;
        }
 
@@ -1009,7 +1074,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
        diff_fill_sha1_info(p->one);
        diff_fill_sha1_info(p->two);
 
-       builtin_diffstat(name, other, p->one, p->two, diffstat);
+       if (p->status == DIFF_STATUS_MODIFIED && p->score)
+               complete_rewrite = 1;
+       builtin_diffstat(name, other, p->one, p->two, diffstat, complete_rewrite);
 }
 
 void diff_setup(struct diff_options *options)
@@ -1036,8 +1103,7 @@ int diff_setup_done(struct diff_options *options)
         * recursive bits for other formats here.
         */
        if ((options->output_format == DIFF_FORMAT_PATCH) ||
-           (options->output_format == DIFF_FORMAT_DIFFSTAT) ||
-           (options->with_stat))
+           (options->output_format == DIFF_FORMAT_DIFFSTAT))
                options->recursive = 1;
 
        if (options->detect_rename && options->rename_limit < 0)
@@ -1375,7 +1441,7 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
 }
 
 static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
-               struct diffstat_t *diffstat)
+                           struct diffstat_t *diffstat)
 {
        if (diff_unmodified_pair(p))
                return;
@@ -1560,7 +1626,7 @@ void diff_flush(struct diff_options *options)
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
                        flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
-                                       diffstat);
+                                      diffstat);
                }
                show_stats(diffstat);
                free(diffstat);
diff --git a/diff.h b/diff.h
index f783bae..7150b90 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -6,6 +6,7 @@
 
 #include "tree-walk.h"
 
+struct rev_info;
 struct diff_options;
 
 typedef void (*change_fn_t)(struct diff_options *options,
@@ -27,10 +28,11 @@ struct diff_options {
                 with_raw:1,
                 with_stat:1,
                 tree_in_recursive:1,
-                full_index:1;
+                full_index:1,
+                silent_on_remove:1,
+                find_copies_harder:1;
        int break_opt;
        int detect_rename;
-       int find_copies_harder;
        int line_termination;
        int output_format;
        int pickaxe_opts;
@@ -70,11 +72,10 @@ struct combine_diff_path {
        (sizeof(struct combine_diff_path) + \
         sizeof(struct combine_diff_parent) * (n) + (l) + 1)
 
-extern int show_combined_diff(struct combine_diff_path *elem, int num_parent,
-                             int dense, const char *header,
-                             struct diff_options *);
+extern void show_combined_diff(struct combine_diff_path *elem, int num_parent,
+                             int dense, struct rev_info *);
 
-extern const char *diff_tree_combined_merge(const unsigned char *sha1, const char *, int, struct diff_options *opt);
+extern void diff_tree_combined_merge(const unsigned char *sha1, int, struct rev_info *);
 
 extern void diff_addremove(struct diff_options *,
                           int addremove,
@@ -168,4 +169,8 @@ extern void diff_flush(struct diff_options*);
 
 extern const char *diff_unique_abbrev(const unsigned char *, int);
 
+extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
+
+extern int run_diff_index(struct rev_info *revs, int cached);
+
 #endif /* DIFF_H */
index 7d3f78e..ffd9c66 100755 (executable)
@@ -88,7 +88,7 @@ my $TEMP_DIR = tempdir( CLEANUP => 1 );
 $log->debug("Temporary directory is '$TEMP_DIR'");
 
 # if we are called with a pserver argument,
-# deal with the authentication cat before entereing the
+# deal with the authentication cat before entering the
 # main loop
 if (@ARGV && $ARGV[0] eq 'pserver') {
     my $line = <STDIN>; chomp $line;
@@ -117,7 +117,7 @@ while (<STDIN>)
 {
     chomp;
 
-    # Check to see if we've seen this method, and call appropiate function.
+    # Check to see if we've seen this method, and call appropriate function.
     if ( /^([\w-]+)(?:\s+(.*))?$/ and defined($methods->{$1}) )
     {
         # use the $methods hash to call the appropriate sub for this command
@@ -171,11 +171,11 @@ sub req_Root
        return 0;
     }
 
-    my @gitvars = `git-var -l`;
+    my @gitvars = `git-repo-config -l`;
     if ($?) {
-       print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n";
+       print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
         print "E \n";
-        print "error 1 - problem executing git-var\n";
+        print "error 1 - problem executing git-repo-config\n";
        return 0;
     }
     foreach my $line ( @gitvars )
@@ -224,7 +224,7 @@ sub req_Globaloption
 sub req_Validresponses
 {
     my ( $cmd, $data ) = @_;
-    $log->debug("req_Validrepsonses : $data");
+    $log->debug("req_Validresponses : $data");
 
     # TODO : re-enable this, currently it's not particularly useful
     #$state->{validresponses} = [ split /\s+/, $data ];
@@ -733,7 +733,7 @@ sub req_update
     argsplit("update");
 
     #
-    # It may just be a client exploring the available heads/modukles
+    # It may just be a client exploring the available heads/modules
     # in that case, list them as top level directories and leave it
     # at that. Eclipse uses this technique to offer you a list of
     # projects (heads in this case) to checkout.
@@ -1731,7 +1731,7 @@ sub transmitfile
 }
 
 # This method takes a file name, and returns ( $dirpart, $filepart ) which
-# refers to the directory porition and the file portion of the filename
+# refers to the directory portion and the file portion of the filename
 # respectively
 sub filenamesplit
 {
@@ -1790,7 +1790,7 @@ Log::Log4perl
 =head2 new
 
 Creates a new log object, optionally you can specify a filename here to
-indicate the file to log to. If no log file is specified, you can specifiy one
+indicate the file to log to. If no log file is specified, you can specify one
 later with method setfile, or indicate you no longer want logging with method
 nofile.
 
@@ -2076,14 +2076,15 @@ sub update
     # TODO: log processing is memory bound
     # if we can parse into a 2nd file that is in reverse order
     # we can probably do something really efficient
-    my @git_log_params = ('--parents', '--topo-order');
+    my @git_log_params = ('--pretty', '--parents', '--topo-order');
 
     if (defined $lastcommit) {
         push @git_log_params, "$lastcommit..$self->{module}";
     } else {
         push @git_log_params, $self->{module};
     }
-    open(GITLOG, '-|', 'git-log', @git_log_params) or die "Cannot call git-log: $!";
+    # git-rev-list is the backend / plumbing version of git-log
+    open(GITLOG, '-|', 'git-rev-list', @git_log_params) or die "Cannot call git-rev-list: $!";
 
     my @commits;
 
@@ -2595,7 +2596,7 @@ sub in_array
 
 =head2 safe_pipe_capture
 
-an alterative to `command` that allows input to be passed as an array
+an alternative to `command` that allows input to be passed as an array
 to work around shell problems with weird characters in arguments
 
 =cut
index 83143f8..280f62e 100755 (executable)
@@ -270,14 +270,22 @@ fetch_main () {
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
-         remote_name_quoted=$(perl -e '
+         max_depth=5
+         depth=0
+         head="ref: $remote_name"
+         while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
+         do
+           remote_name_quoted=$(perl -e '
              my $u = $ARGV[0];
+              $u =~ s/^ref:\s*//;
              $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
              print "$u";
-         ' "$remote_name")
-         head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted") &&
+         ' "$head")
+           head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+           depth=$( expr \( $depth + 1 \) )
+         done
          expr "z$head" : "z$_x40\$" >/dev/null ||
-                 die "Failed to fetch $remote_name from $remote"
+             die "Failed to fetch $remote_name from $remote"
          echo >&2 Fetching "$remote_name from $remote" using http
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
index f7b2b94..9e25902 100755 (executable)
@@ -4,37 +4,51 @@
 #
 
 USAGE='[--onto <newbase>] <upstream> [<branch>]'
-LONG_USAGE='git-rebase applies to <upstream> (or optionally to <newbase>) commits
-from <branch> that do not appear in <upstream>. When <branch> is not
-specified it defaults to the current branch (HEAD).
-
-When git-rebase is complete, <branch> will be updated to point to the
-newly created line of commit objects, so the previous line will not be
-accessible unless there are other references to it already.
-
-Assuming the following history:
-
-          A---B---C topic
-         /
-    D---E---F---G master
-
-The result of the following command:
-
-    git-rebase --onto master~1 master topic
-
-  would be:
-
-              A'\''--B'\''--C'\'' topic
-             /
-    D---E---F---G master
+LONG_USAGE='git-rebase replaces <branch> with a new branch of the
+same name.  When the --onto option is provided the new branch starts
+out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
+It then attempts to create a new commit for each commit from the original
+<branch> that does not exist in the <upstream> branch.
+
+It is possible that a merge failure will prevent this process from being
+completely automatic.  You will have to resolve any such merge failure
+and run git-rebase --continue.  If you can not resolve the merge failure,
+running git-rebase --abort will restore the original <branch> and remove
+the working files found in the .dotest directory.
+
+Note that if <branch> is not specified on the command line, the
+currently checked out branch is used.  You must be in the top
+directory of your project to start (or continue) a rebase.
+
+Example:       git-rebase master~1 topic
+
+        A---B---C topic                   A'\''--B'\''--C'\'' topic
+       /                   -->           /
+  D---E---F---G master          D---E---F---G master
 '
-
 . git-sh-setup
 
 unset newbase
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
+       --continue)
+               diff=$(git-diff-files)
+               case "$diff" in
+               ?*)     echo "You must edit all merge conflicts and then"
+                       echo "mark them as resolved using git update-index"
+                       exit 1
+                       ;;
+               esac
+               git am --resolved --3way
+               exit
+               ;;
+       --abort)
+               [ -d .dotest ] || die "No rebase in progress?"
+               git reset --hard ORIG_HEAD
+               rm -r .dotest
+               exit
+               ;;
        --onto)
                test 2 -le "$#" || usage
                newbase="$2"
index a5d349f..e0c9f32 100755 (executable)
@@ -5,9 +5,9 @@
 
 USAGE='[-a] [-d] [-f] [-l] [-n] [-q]'
 . git-sh-setup
-       
+
 no_update_info= all_into_one= remove_redundant=
-local= quiet= no_reuse_delta=
+local= quiet= no_reuse_delta= extra=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -17,6 +17,8 @@ do
        -q)     quiet=-q ;;
        -f)     no_reuse_delta=--no-reuse-delta ;;
        -l)     local=--local ;;
+       --window=*) extra="$extra $1" ;;
+       --depth=*) extra="$extra $1" ;;
        *)      usage ;;
        esac
        shift
@@ -40,7 +42,7 @@ case ",$all_into_one," in
            find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
        ;;
 esac
-pack_objects="$pack_objects $local $quiet $no_reuse_delta"
+pack_objects="$pack_objects $local $quiet $no_reuse_delta$extra"
 name=$(git-rev-list --objects --all $rev_list 2>&1 |
        git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
diff --git a/git.c b/git.c
index 5209b04..01b7e28 100644 (file)
--- a/git.c
+++ b/git.c
 #include <errno.h>
 #include <limits.h>
 #include <stdarg.h>
-#include <sys/ioctl.h>
 #include "git-compat-util.h"
 #include "exec_cmd.h"
-#include "common-cmds.h"
 
-#include "cache.h"
-#include "commit.h"
-#include "diff.h"
-#include "revision.h"
-#include "log-tree.h"
-
-#ifndef PATH_MAX
-# define PATH_MAX 4096
-#endif
-
-static const char git_usage[] =
-       "Usage: git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
-
-/* most gui terms set COLUMNS (although some don't export it) */
-static int term_columns(void)
-{
-       char *col_string = getenv("COLUMNS");
-       int n_cols = 0;
-
-       if (col_string && (n_cols = atoi(col_string)) > 0)
-               return n_cols;
-
-#ifdef TIOCGWINSZ
-       {
-               struct winsize ws;
-               if (!ioctl(1, TIOCGWINSZ, &ws)) {
-                       if (ws.ws_col)
-                               return ws.ws_col;
-               }
-       }
-#endif
-
-       return 80;
-}
-
-static void oom(void)
-{
-       fprintf(stderr, "git: out of memory\n");
-       exit(1);
-}
-
-static inline void mput_char(char c, unsigned int num)
-{
-       while(num--)
-               putchar(c);
-}
-
-static struct cmdname {
-       size_t len;
-       char name[1];
-} **cmdname;
-static int cmdname_alloc, cmdname_cnt;
-
-static void add_cmdname(const char *name, int len)
-{
-       struct cmdname *ent;
-       if (cmdname_alloc <= cmdname_cnt) {
-               cmdname_alloc = cmdname_alloc + 200;
-               cmdname = realloc(cmdname, cmdname_alloc * sizeof(*cmdname));
-               if (!cmdname)
-                       oom();
-       }
-       ent = malloc(sizeof(*ent) + len);
-       if (!ent)
-               oom();
-       ent->len = len;
-       memcpy(ent->name, name, len);
-       ent->name[len] = 0;
-       cmdname[cmdname_cnt++] = ent;
-}
-
-static int cmdname_compare(const void *a_, const void *b_)
-{
-       struct cmdname *a = *(struct cmdname **)a_;
-       struct cmdname *b = *(struct cmdname **)b_;
-       return strcmp(a->name, b->name);
-}
-
-static void pretty_print_string_list(struct cmdname **cmdname, int longest)
-{
-       int cols = 1, rows;
-       int space = longest + 1; /* min 1 SP between words */
-       int max_cols = term_columns() - 1; /* don't print *on* the edge */
-       int i, j;
-
-       if (space < max_cols)
-               cols = max_cols / space;
-       rows = (cmdname_cnt + cols - 1) / cols;
-
-       qsort(cmdname, cmdname_cnt, sizeof(*cmdname), cmdname_compare);
-
-       for (i = 0; i < rows; i++) {
-               printf("  ");
-
-               for (j = 0; j < cols; j++) {
-                       int n = j * rows + i;
-                       int size = space;
-                       if (n >= cmdname_cnt)
-                               break;
-                       if (j == cols-1 || n + rows >= cmdname_cnt)
-                               size = 1;
-                       printf("%-*s", size, cmdname[n]->name);
-               }
-               putchar('\n');
-       }
-}
-
-static void list_commands(const char *exec_path, const char *pattern)
-{
-       unsigned int longest = 0;
-       char path[PATH_MAX];
-       int dirlen;
-       DIR *dir = opendir(exec_path);
-       struct dirent *de;
-
-       if (!dir) {
-               fprintf(stderr, "git: '%s': %s\n", exec_path, strerror(errno));
-               exit(1);
-       }
-
-       dirlen = strlen(exec_path);
-       if (PATH_MAX - 20 < dirlen) {
-               fprintf(stderr, "git: insanely long exec-path '%s'\n",
-                       exec_path);
-               exit(1);
-       }
-
-       memcpy(path, exec_path, dirlen);
-       path[dirlen++] = '/';
-
-       while ((de = readdir(dir)) != NULL) {
-               struct stat st;
-               int entlen;
-
-               if (strncmp(de->d_name, "git-", 4))
-                       continue;
-               strcpy(path+dirlen, de->d_name);
-               if (stat(path, &st) || /* stat, not lstat */
-                   !S_ISREG(st.st_mode) ||
-                   !(st.st_mode & S_IXUSR))
-                       continue;
-
-               entlen = strlen(de->d_name);
-               if (4 < entlen && !strcmp(de->d_name + entlen - 4, ".exe"))
-                       entlen -= 4;
-
-               if (longest < entlen)
-                       longest = entlen;
-
-               add_cmdname(de->d_name + 4, entlen-4);
-       }
-       closedir(dir);
-
-       printf("git commands available in '%s'\n", exec_path);
-       printf("----------------------------");
-       mput_char('-', strlen(exec_path));
-       putchar('\n');
-       pretty_print_string_list(cmdname, longest - 4);
-       putchar('\n');
-}
-
-static void list_common_cmds_help(void)
-{
-       int i, longest = 0;
-
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               if (longest < strlen(common_cmds[i].name))
-                       longest = strlen(common_cmds[i].name);
-       }
-
-       puts("The most commonly used git commands are:");
-       for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               printf("    %s", common_cmds[i].name);
-               mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
-               puts(common_cmds[i].help);
-       }
-       puts("(use 'git help -a' to get a list of all installed git commands)");
-}
-
-#ifdef __GNUC__
-static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
-       __attribute__((__format__(__printf__, 3, 4), __noreturn__));
-#endif
-static void cmd_usage(int show_all, const char *exec_path, const char *fmt, ...)
-{
-       if (fmt) {
-               va_list ap;
-
-               va_start(ap, fmt);
-               printf("git: ");
-               vprintf(fmt, ap);
-               va_end(ap);
-               putchar('\n');
-       }
-       else
-               puts(git_usage);
-
-       if (exec_path) {
-               putchar('\n');
-               if (show_all)
-                       list_commands(exec_path, "git-*");
-               else
-                       list_common_cmds_help();
-        }
-
-       exit(1);
-}
+#include "builtin.h"
 
 static void prepend_to_path(const char *dir, int len)
 {
@@ -240,163 +32,7 @@ static void prepend_to_path(const char *dir, int len)
        setenv("PATH", path, 1);
 }
 
-static void show_man_page(const char *git_cmd)
-{
-       const char *page;
-
-       if (!strncmp(git_cmd, "git", 3))
-               page = git_cmd;
-       else {
-               int page_len = strlen(git_cmd) + 4;
-               char *p = malloc(page_len + 1);
-               strcpy(p, "git-");
-               strcpy(p + 4, git_cmd);
-               p[page_len] = 0;
-               page = p;
-       }
-
-       execlp("man", "man", page, NULL);
-}
-
-static int cmd_version(int argc, const char **argv, char **envp)
-{
-       printf("git version %s\n", GIT_VERSION);
-       return 0;
-}
-
-static int cmd_help(int argc, const char **argv, char **envp)
-{
-       const char *help_cmd = argv[1];
-       if (!help_cmd)
-               cmd_usage(0, git_exec_path(), NULL);
-       else if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a"))
-               cmd_usage(1, git_exec_path(), NULL);
-       else
-               show_man_page(help_cmd);
-       return 0;
-}
-
-#define LOGSIZE (65536)
-
-static int cmd_log(int argc, const char **argv, char **envp)
-{
-       struct rev_info rev;
-       struct commit *commit;
-       char *buf = xmalloc(LOGSIZE);
-       static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
-       int abbrev = DEFAULT_ABBREV;
-       int abbrev_commit = 0;
-       const char *commit_prefix = "commit ";
-       struct log_tree_opt opt;
-       int shown = 0;
-       int do_diff = 0;
-       int full_diff = 0;
-
-       init_log_tree_opt(&opt);
-       argc = setup_revisions(argc, argv, &rev, "HEAD");
-       while (1 < argc) {
-               const char *arg = argv[1];
-               if (!strncmp(arg, "--pretty", 8)) {
-                       commit_format = get_commit_format(arg + 8);
-                       if (commit_format == CMIT_FMT_ONELINE)
-                               commit_prefix = "";
-               }
-               else if (!strcmp(arg, "--no-abbrev")) {
-                       abbrev = 0;
-               }
-               else if (!strcmp(arg, "--abbrev")) {
-                       abbrev = DEFAULT_ABBREV;
-               }
-               else if (!strcmp(arg, "--abbrev-commit")) {
-                       abbrev_commit = 1;
-               }
-               else if (!strncmp(arg, "--abbrev=", 9)) {
-                       abbrev = strtoul(arg + 9, NULL, 10);
-                       if (abbrev && abbrev < MINIMUM_ABBREV)
-                               abbrev = MINIMUM_ABBREV;
-                       else if (40 < abbrev)
-                               abbrev = 40;
-               }
-               else if (!strcmp(arg, "--full-diff")) {
-                       do_diff = 1;
-                       full_diff = 1;
-               }
-               else {
-                       int cnt = log_tree_opt_parse(&opt, argv+1, argc-1);
-                       if (0 < cnt) {
-                               do_diff = 1;
-                               argv += cnt;
-                               argc -= cnt;
-                               continue;
-                       }
-                       die("unrecognized argument: %s", arg);
-               }
-
-               argc--; argv++;
-       }
-
-       if (do_diff) {
-               opt.diffopt.abbrev = abbrev;
-               opt.verbose_header = 0;
-               opt.always_show_header = 0;
-               opt.no_commit_id = 1;
-               if (opt.combine_merges)
-                       opt.ignore_merges = 0;
-               if (opt.dense_combined_merges)
-                       opt.diffopt.output_format = DIFF_FORMAT_PATCH;
-               if (!full_diff && rev.prune_data)
-                       diff_tree_setup_paths(rev.prune_data, &opt.diffopt);
-               diff_setup_done(&opt.diffopt);
-       }
-
-       prepare_revision_walk(&rev);
-       setup_pager();
-       while ((commit = get_revision(&rev)) != NULL) {
-               if (shown && do_diff && commit_format != CMIT_FMT_ONELINE)
-                       putchar('\n');
-               fputs(commit_prefix, stdout);
-               if (abbrev_commit && abbrev)
-                       fputs(find_unique_abbrev(commit->object.sha1, abbrev),
-                             stdout);
-               else
-                       fputs(sha1_to_hex(commit->object.sha1), stdout);
-               if (rev.parents) {
-                       struct commit_list *parents = commit->parents;
-                       while (parents) {
-                               struct object *o = &(parents->item->object);
-                               parents = parents->next;
-                               if (o->flags & TMP_MARK)
-                                       continue;
-                               printf(" %s", sha1_to_hex(o->sha1));
-                               o->flags |= TMP_MARK;
-                       }
-                       /* TMP_MARK is a general purpose flag that can
-                        * be used locally, but the user should clean
-                        * things up after it is done with them.
-                        */
-                       for (parents = commit->parents;
-                            parents;
-                            parents = parents->next)
-                               parents->item->object.flags &= ~TMP_MARK;
-               }
-               if (commit_format == CMIT_FMT_ONELINE)
-                       putchar(' ');
-               else
-                       putchar('\n');
-               pretty_print_commit(commit_format, commit, ~0, buf,
-                                   LOGSIZE, abbrev);
-               printf("%s\n", buf);
-               if (do_diff) {
-                       printf("---\n");
-                       log_tree_commit(&opt, commit);
-               }
-               shown = 1;
-               free(commit->buffer);
-               commit->buffer = NULL;
-       }
-       free(buf);
-       return 0;
-}
+const char git_version_string[] = GIT_VERSION;
 
 static void handle_internal_command(int argc, const char **argv, char **envp)
 {
@@ -408,6 +44,8 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "version", cmd_version },
                { "help", cmd_help },
                { "log", cmd_log },
+               { "whatchanged", cmd_whatchanged },
+               { "show", cmd_show },
        };
        int i;
 
diff --git a/gitk b/gitk
index 87e7162..4aa57c0 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -16,99 +16,112 @@ proc gitdir {} {
     }
 }
 
-proc parse_args {rargs} {
-    global parsed_args
-
-    if {[catch {
-       set parse_args [concat --default HEAD $rargs]
-       set parsed_args [split [eval exec git-rev-parse $parse_args] "\n"]
-    }]} {
-       # if git-rev-parse failed for some reason...
-       if {$rargs == {}} {
-           set rargs HEAD
-       }
-       set parsed_args $rargs
-    }
-    return $parsed_args
-}
-
-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 $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 [parse_args $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}]]
        }
@@ -141,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}]
@@ -186,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]
     }
 }
 
@@ -196,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} {
@@ -290,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
@@ -321,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"
@@ -334,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
@@ -345,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
@@ -353,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
@@ -463,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
@@ -496,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}
 
@@ -553,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]
@@ -622,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
@@ -633,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}]"
@@ -645,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"
     }
@@ -786,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 {
@@ -821,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
@@ -902,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 {{}}
@@ -922,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 {} {
@@ -954,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
     }
@@ -971,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
@@ -985,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
                }
            }
        }
@@ -1007,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} {
@@ -1014,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]
@@ -1030,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)} {
@@ -1045,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
@@ -1056,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]
@@ -1074,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]
@@ -1117,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}]
@@ -1147,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]
@@ -1160,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 {}
@@ -1176,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]
@@ -1195,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
                }
            }
@@ -1254,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]
@@ -1309,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 {}
@@ -1359,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.
@@ -1380,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]
@@ -1389,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]
@@ -1408,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
        }
@@ -1433,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]
@@ -1459,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]
@@ -1484,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]}]
@@ -1500,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
@@ -1576,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)
@@ -1664,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
@@ -1707,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} {
@@ -1718,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]
@@ -1728,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
 
@@ -1789,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,
@@ -1819,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"
@@ -1858,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
 
@@ -1919,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
@@ -2017,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 {} {
@@ -2063,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} {
@@ -2087,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
 }
 
@@ -2165,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
@@ -2336,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
@@ -2346,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
@@ -2378,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
@@ -2451,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"
@@ -2500,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
@@ -2549,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} {
@@ -2577,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} {
@@ -2597,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]} {
@@ -2605,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]
@@ -2647,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
@@ -2721,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
 }
 
@@ -2740,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} {
@@ -2747,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
            }
@@ -2762,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]
@@ -2775,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
@@ -2803,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"
@@ -2869,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 {} {
@@ -2921,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
@@ -2933,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
@@ -2964,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
@@ -2990,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]} {
@@ -3066,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
        }
@@ -3080,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
@@ -3098,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
@@ -3137,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
@@ -3155,8 +4140,7 @@ proc lineclick {x y id isnew} {
        }
     }
     $ctext conf -state disabled
-
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc normalline {} {
@@ -3169,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
     }
 }
 
@@ -3184,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
@@ -3214,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 }
@@ -3389,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
     }
 }
@@ -3508,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]
@@ -3523,7 +4506,7 @@ proc showtag {tag isnew} {
     }
     appendwithlinks $text
     $ctext conf -state disabled
-    $cflist delete 0 end
+    init_flist {}
 }
 
 proc doquit {} {
@@ -3905,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 {}
@@ -3928,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 114d01b..b4327d9 100644 (file)
@@ -2498,6 +2498,7 @@ int main(int argc, char **argv)
                        commit_argv[3] = old_sha1_hex;
                        commit_argc++;
                }
+               init_revisions(&revs);
                setup_revisions(commit_argc, commit_argv, &revs, NULL);
                free(new_sha1_hex);
                if (old_sha1_hex) {
index 3d40482..b90ba67 100644 (file)
@@ -3,59 +3,72 @@
 #include "commit.h"
 #include "log-tree.h"
 
-void init_log_tree_opt(struct log_tree_opt *opt)
+static void show_parents(struct commit *commit, int abbrev)
 {
-       memset(opt, 0, sizeof *opt);
-       opt->ignore_merges = 1;
-       opt->header_prefix = "";
-       opt->commit_format = CMIT_FMT_RAW;
-       diff_setup(&opt->diffopt);
+       struct commit_list *p;
+       for (p = commit->parents; p ; p = p->next) {
+               struct commit *parent = p->item;
+               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+       }
 }
 
-int log_tree_opt_parse(struct log_tree_opt *opt, const char **av, int ac)
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep)
 {
-       const char *arg;
-       int cnt = diff_opt_parse(&opt->diffopt, av, ac);
-       if (0 < cnt)
-               return cnt;
-       arg = *av;
-       if (!strcmp(arg, "-r"))
-               opt->diffopt.recursive = 1;
-       else if (!strcmp(arg, "-t")) {
-               opt->diffopt.recursive = 1;
-               opt->diffopt.tree_in_recursive = 1;
-       }
-       else if (!strcmp(arg, "-m"))
-               opt->ignore_merges = 0;
-       else if (!strcmp(arg, "-c"))
-               opt->combine_merges = 1;
-       else if (!strcmp(arg, "--cc")) {
-               opt->dense_combined_merges = 1;
-               opt->combine_merges = 1;
-       }
-       else if (!strcmp(arg, "-v")) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
-       }
-       else if (!strncmp(arg, "--pretty", 8)) {
-               opt->verbose_header = 1;
-               opt->header_prefix = "diff-tree ";
-               opt->commit_format = get_commit_format(arg+8);
+       static char this_header[16384];
+       struct commit *commit = log->commit, *parent = log->parent;
+       int abbrev = opt->diffopt.abbrev;
+       int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
+       const char *extra;
+       int len;
+
+       opt->loginfo = NULL;
+       if (!opt->verbose_header) {
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
+               if (opt->parents)
+                       show_parents(commit, abbrev_commit);
+               putchar('\n');
+               return;
        }
-       else if (!strcmp(arg, "--root"))
-               opt->show_root_diff = 1;
-       else if (!strcmp(arg, "--no-commit-id"))
-               opt->no_commit_id = 1;
-       else if (!strcmp(arg, "--always"))
-               opt->always_show_header = 1;
-       else
-               return 0;
-       return 1;
+
+       /*
+        * The "oneline" format has several special cases:
+        *  - The pretty-printed commit lacks a newline at the end
+        *    of the buffer, but we do want to make sure that we
+        *    have a newline there. If the separator isn't already
+        *    a newline, add an extra one.
+        *  - unlike other log messages, the one-line format does
+        *    not have an empty line between entries.
+        */
+       extra = "";
+       if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
+               extra = "\n";
+       if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
+               putchar('\n');
+       opt->shown_one = 1;
+
+       /*
+        * Print header line of header..
+        */
+       printf("%s%s",
+               opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
+               diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+       if (opt->parents)
+               show_parents(commit, abbrev_commit);
+       if (parent)
+               printf(" (from %s)", diff_unique_abbrev(parent->object.sha1, abbrev_commit));
+       putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+
+       /*
+        * And then the pretty-printed message itself
+        */
+       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header, sizeof(this_header), abbrev);
+       printf("%s%s%s", this_header, extra, sep);
 }
 
-int log_tree_diff_flush(struct log_tree_opt *opt)
+int log_tree_diff_flush(struct rev_info *opt)
 {
        diffcore_std(&opt->diffopt);
+
        if (diff_queue_is_empty()) {
                int saved_fmt = opt->diffopt.output_format;
                opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
@@ -63,17 +76,14 @@ int log_tree_diff_flush(struct log_tree_opt *opt)
                opt->diffopt.output_format = saved_fmt;
                return 0;
        }
-       if (opt->header) {
-               if (!opt->no_commit_id)
-                       printf("%s%c", opt->header,
-                              opt->diffopt.line_termination);
-               opt->header = NULL;
-       }
+
+       if (opt->loginfo && !opt->no_commit_id)
+               show_log(opt, opt->loginfo, opt->diffopt.with_stat ? "---\n" : "\n");
        diff_flush(&opt->diffopt);
        return 1;
 }
 
-static int diff_root_tree(struct log_tree_opt *opt,
+static int diff_root_tree(struct rev_info *opt,
                          const unsigned char *new, const char *base)
 {
        int retval;
@@ -93,83 +103,78 @@ static int diff_root_tree(struct log_tree_opt *opt,
        return retval;
 }
 
-static const char *generate_header(struct log_tree_opt *opt,
-                                  const unsigned char *commit_sha1,
-                                  const unsigned char *parent_sha1,
-                                  const struct commit *commit)
-{
-       static char this_header[16384];
-       int offset;
-       unsigned long len;
-       int abbrev = opt->diffopt.abbrev;
-       const char *msg = commit->buffer;
-
-       if (!opt->verbose_header)
-               return sha1_to_hex(commit_sha1);
-
-       len = strlen(msg);
-
-       offset = sprintf(this_header, "%s%s ",
-                        opt->header_prefix,
-                        diff_unique_abbrev(commit_sha1, abbrev));
-       if (commit_sha1 != parent_sha1)
-               offset += sprintf(this_header + offset, "(from %s)\n",
-                                 parent_sha1
-                                 ? diff_unique_abbrev(parent_sha1, abbrev)
-                                 : "root");
-       else
-               offset += sprintf(this_header + offset, "(from parents)\n");
-       offset += pretty_print_commit(opt->commit_format, commit, len,
-                                     this_header + offset,
-                                     sizeof(this_header) - offset, abbrev);
-       if (opt->always_show_header) {
-               puts(this_header);
-               return NULL;
-       }
-       return this_header;
-}
-
-static int do_diff_combined(struct log_tree_opt *opt, struct commit *commit)
+static int do_diff_combined(struct rev_info *opt, struct commit *commit)
 {
        unsigned const char *sha1 = commit->object.sha1;
 
-       opt->header = generate_header(opt, sha1, sha1, commit);
-       opt->header = diff_tree_combined_merge(sha1, opt->header,
-                                               opt->dense_combined_merges,
-                                               &opt->diffopt);
-       if (!opt->header && opt->verbose_header)
-               opt->header_prefix = "\ndiff-tree ";
-       return 0;
+       diff_tree_combined_merge(sha1, opt->dense_combined_merges, opt);
+       return !opt->loginfo;
 }
 
-int log_tree_commit(struct log_tree_opt *opt, struct commit *commit)
+/*
+ * Show the diff of a commit.
+ *
+ * Return true if we printed any log info messages
+ */
+static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log_info *log)
 {
+       int showed_log;
        struct commit_list *parents;
        unsigned const char *sha1 = commit->object.sha1;
 
+       if (!opt->diff)
+               return 0;
+
        /* Root commit? */
-       if (opt->show_root_diff && !commit->parents) {
-               opt->header = generate_header(opt, sha1, NULL, commit);
-               diff_root_tree(opt, sha1, "");
+       parents = commit->parents;
+       if (!parents) {
+               if (opt->show_root_diff)
+                       diff_root_tree(opt, sha1, "");
+               return !opt->loginfo;
        }
 
        /* More than one parent? */
-       if (commit->parents && commit->parents->next) {
+       if (parents && parents->next) {
                if (opt->ignore_merges)
                        return 0;
                else if (opt->combine_merges)
                        return do_diff_combined(opt, commit);
+
+               /* If we show individual diffs, show the parent info */
+               log->parent = parents->item;
        }
 
-       for (parents = commit->parents; parents; parents = parents->next) {
+       showed_log = 0;
+       for (;;) {
                struct commit *parent = parents->item;
-               unsigned const char *psha1 = parent->object.sha1;
-               opt->header = generate_header(opt, sha1, psha1, commit);
-               diff_tree_sha1(psha1, sha1, "", &opt->diffopt);
-               log_tree_diff_flush(opt);               
 
-               if (!opt->header && opt->verbose_header)
-                       opt->header_prefix = "\ndiff-tree ";
+               diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+               log_tree_diff_flush(opt);
+
+               showed_log |= !opt->loginfo;
+
+               /* Set up the log info for the next parent, if any.. */
+               parents = parents->next;
+               if (!parents)
+                       break;
+               log->parent = parents->item;
+               opt->loginfo = log;
+       }
+       return showed_log;
+}
+
+int log_tree_commit(struct rev_info *opt, struct commit *commit)
+{
+       struct log_info log;
+
+       log.commit = commit;
+       log.parent = NULL;
+       opt->loginfo = &log;
+
+       if (!log_tree_diff(opt, commit, &log) && opt->loginfo && opt->always_show_header) {
+               log.parent = NULL;
+               show_log(opt, opt->loginfo, "");
        }
+       opt->loginfo = NULL;
        return 0;
 }
index da166c6..a26e484 100644 (file)
@@ -1,23 +1,16 @@
 #ifndef LOG_TREE_H
 #define LOG_TREE_H
 
-struct log_tree_opt {
-       struct diff_options diffopt;
-       int show_root_diff;
-       int no_commit_id;
-       int verbose_header;
-       int ignore_merges;
-       int combine_merges;
-       int dense_combined_merges;
-       int always_show_header;
-       const char *header_prefix;
-       const char *header;
-       enum cmit_fmt commit_format;
+#include "revision.h"
+
+struct log_info {
+       struct commit *commit, *parent;
 };
 
-void init_log_tree_opt(struct log_tree_opt *);
-int log_tree_diff_flush(struct log_tree_opt *);
-int log_tree_commit(struct log_tree_opt *, struct commit *);
-int log_tree_opt_parse(struct log_tree_opt *, const char **, int);
+void init_log_tree_opt(struct rev_info *);
+int log_tree_diff_flush(struct rev_info *);
+int log_tree_commit(struct rev_info *, struct commit *);
+int log_tree_opt_parse(struct rev_info *, const char **, int);
+void show_log(struct rev_info *opt, struct log_info *log, const char *sep);
 
 #endif
index 50528d5..cc7b5bd 100644 (file)
@@ -24,16 +24,14 @@ static const char *sha1_to_hex_zero(const unsigned char *sha1)
 
 static void resolve(const char *base, struct name_entry *branch1, struct name_entry *result)
 {
-       char branch1_sha1[50];
-
        /* If it's already branch1, don't bother showing it */
        if (!branch1)
                return;
-       memcpy(branch1_sha1, sha1_to_hex_zero(branch1->sha1), 41);
 
        printf("0 %06o->%06o %s->%s %s%s\n",
                branch1->mode, result->mode,
-               branch1_sha1, sha1_to_hex_zero(result->sha1),
+               sha1_to_hex_zero(branch1->sha1),
+               sha1_to_hex_zero(result->sha1),
                base, result->path);
 }
 
index c0acc46..6604338 100644 (file)
@@ -1032,12 +1032,6 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
                max_depth -= cur_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)
                return 0;
 
@@ -1048,9 +1042,12 @@ static int try_delta(struct unpacked *cur, struct unpacked *old, unsigned max_de
         * more space-efficient (deletes don't have to say _what_ they
         * delete).
         */
+       size = cur_entry->size;
        max_size = size / 2 - 20;
        if (cur_entry->delta)
                max_size = cur_entry->delta_size-1;
+       oldsize = old_entry->size;
+       sizediff = oldsize < size ? size - oldsize : 0;
        if (sizediff >= max_size)
                return 0;
        delta_buf = diff_delta(old->data, oldsize,
@@ -1109,6 +1106,9 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                         */
                        continue;
 
+               if (entry->size < 50)
+                       continue;
+
                free(n->data);
                n->entry = entry;
                n->data = read_sha1_file(entry->sha1, type, &size);
diff --git a/pager.c b/pager.c
index f7b8e78..9a30939 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -21,7 +21,7 @@ void setup_pager(void)
                return;
        if (!pager)
                pager = "less";
-       else if (!*pager)
+       else if (!*pager || !strcmp(pager, "cat"))
                return;
 
        if (pipe(fd) < 0)
index c5ebb76..e350630 100644 (file)
@@ -2,7 +2,7 @@
 #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]]";
+"git-repo-config [ --bool | --int ] [--get | --get-all | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
 
 static char* key = NULL;
 static char* value = NULL;
@@ -12,6 +12,15 @@ static int do_not_match = 0;
 static int seen = 0;
 static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
 
+static int show_all_config(const char *key_, const char *value_)
+{
+       if (value_)
+               printf("%s=%s\n", key_, value_);
+       else
+               printf("%s\n", key_);
+       return 0;
+}
+
 static int show_config(const char* key_, const char* value_)
 {
        if (value_ == NULL)
@@ -67,7 +76,7 @@ static int get_value(const char* key_, const char* regex_)
                }
        }
 
-       i = git_config(show_config);
+       git_config(show_config);
        if (value) {
                printf("%s\n", value);
                free(value);
@@ -93,6 +102,8 @@ 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--;
index a8fe83c..8b0ec38 100644 (file)
@@ -39,24 +39,21 @@ static const char rev_list_usage[] =
 struct rev_info revs;
 
 static int bisect_list = 0;
-static int verbose_header = 0;
-static int abbrev = DEFAULT_ABBREV;
-static int abbrev_commit = 0;
 static int show_timestamp = 0;
 static int hdr_termination = 0;
-static const char *commit_prefix = "";
-static enum cmit_fmt commit_format = CMIT_FMT_RAW;
+static const char *header_prefix;
 
 static void show_commit(struct commit *commit)
 {
        if (show_timestamp)
                printf("%lu ", commit->date);
-       if (commit_prefix[0])
-               fputs(commit_prefix, stdout);
+       if (header_prefix)
+               fputs(header_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
-       if (abbrev_commit && abbrev)
-               fputs(find_unique_abbrev(commit->object.sha1, abbrev), stdout);
+       if (revs.abbrev_commit && revs.abbrev)
+               fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+                     stdout);
        else
                fputs(sha1_to_hex(commit->object.sha1), stdout);
        if (revs.parents) {
@@ -78,14 +75,16 @@ static void show_commit(struct commit *commit)
                     parents = parents->next)
                        parents->item->object.flags &= ~TMP_MARK;
        }
-       if (commit_format == CMIT_FMT_ONELINE)
+       if (revs.commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
        else
                putchar('\n');
 
-       if (verbose_header) {
+       if (revs.verbose_header) {
                static char pretty_header[16384];
-               pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
+               pretty_print_commit(revs.commit_format, commit, ~0,
+                                   pretty_header, sizeof(pretty_header),
+                                   revs.abbrev);
                printf("%s%c", pretty_header, hdr_termination);
        }
        fflush(stdout);
@@ -297,58 +296,16 @@ int main(int argc, const char **argv)
        struct commit_list *list;
        int i;
 
+       init_revisions(&revs);
+       revs.abbrev = 0;
+       revs.commit_format = CMIT_FMT_UNSPECIFIED;
        argc = setup_revisions(argc, argv, &revs, NULL);
 
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
 
-               /* accept -<digit>, like traditilnal "head" */
-               if ((*arg == '-') && isdigit(arg[1])) {
-                       revs.max_count = atoi(arg + 1);
-                       continue;
-               }
-               if (!strcmp(arg, "-n")) {
-                       if (++i >= argc)
-                               die("-n requires an argument");
-                       revs.max_count = atoi(argv[i]);
-                       continue;
-               }
-               if (!strncmp(arg,"-n",2)) {
-                       revs.max_count = atoi(arg + 2);
-                       continue;
-               }
                if (!strcmp(arg, "--header")) {
-                       verbose_header = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-abbrev")) {
-                       abbrev = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--abbrev")) {
-                       abbrev = DEFAULT_ABBREV;
-                       continue;
-               }
-               if (!strcmp(arg, "--abbrev-commit")) {
-                       abbrev_commit = 1;
-                       continue;
-               }
-               if (!strncmp(arg, "--abbrev=", 9)) {
-                       abbrev = strtoul(arg + 9, NULL, 10);
-                       if (abbrev && abbrev < MINIMUM_ABBREV)
-                               abbrev = MINIMUM_ABBREV;
-                       else if (40 < abbrev)
-                               abbrev = 40;
-                       continue;
-               }
-               if (!strncmp(arg, "--pretty", 8)) {
-                       commit_format = get_commit_format(arg+8);
-                       verbose_header = 1;
-                       hdr_termination = '\n';
-                       if (commit_format == CMIT_FMT_ONELINE)
-                               commit_prefix = "";
-                       else
-                               commit_prefix = "commit ";
+                       revs.verbose_header = 1;
                        continue;
                }
                if (!strcmp(arg, "--timestamp")) {
@@ -362,14 +319,27 @@ int main(int argc, const char **argv)
                usage(rev_list_usage);
 
        }
+       if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
+               /* The command line has a --pretty  */
+               hdr_termination = '\n';
+               if (revs.commit_format == CMIT_FMT_ONELINE)
+                       header_prefix = "";
+               else
+                       header_prefix = "commit ";
+       }
+       else if (revs.verbose_header)
+               /* Only --header was specified */
+               revs.commit_format = CMIT_FMT_RAW;
 
        list = revs.commits;
 
-       if (!list &&
-           (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
+       if ((!list &&
+            (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
+             !revs.pending_objects)) ||
+           revs.diff)
                usage(rev_list_usage);
 
-       save_commit_buffer = verbose_header;
+       save_commit_buffer = revs.verbose_header;
        track_object_refs = 0;
        if (bisect_list)
                revs.limited = 1;
index 5f2f0be..c6e8702 100644 (file)
@@ -116,21 +116,27 @@ static void add_pending_object(struct rev_info *revs, struct object *obj, const
        add_object(obj, &revs->pending_objects, NULL, name);
 }
 
-static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
 {
        struct object *object;
 
        object = parse_object(sha1);
        if (!object)
                die("bad object %s", name);
+       object->flags |= flags;
+       return object;
+}
+
+static struct commit *handle_commit(struct rev_info *revs, struct object *object, const char *name)
+{
+       unsigned long flags = object->flags;
 
        /*
         * Tag object? Look what it points to..
         */
        while (object->type == tag_type) {
                struct tag *tag = (struct tag *) object;
-               object->flags |= flags;
-               if (revs->tag_objects && !(object->flags & UNINTERESTING))
+               if (revs->tag_objects && !(flags & UNINTERESTING))
                        add_pending_object(revs, object, tag->tag);
                object = parse_object(tag->tagged->sha1);
                if (!object)
@@ -143,10 +149,10 @@ static struct commit *get_commit_reference(struct rev_info *revs, const char *na
         */
        if (object->type == commit_type) {
                struct commit *commit = (struct commit *)object;
-               object->flags |= flags;
                if (parse_commit(commit) < 0)
                        die("unable to parse commit %s", name);
                if (flags & UNINTERESTING) {
+                       commit->object.flags |= UNINTERESTING;
                        mark_parents_uninteresting(commit);
                        revs->limited = 1;
                }
@@ -241,7 +247,7 @@ int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
                return REV_TREE_DIFFERENT;
        tree_difference = REV_TREE_SAME;
        if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "",
-                          &revs->diffopt) < 0)
+                          &revs->pruning) < 0)
                return REV_TREE_DIFFERENT;
        return tree_difference;
 }
@@ -264,7 +270,7 @@ int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
        empty.size = 0;
 
        tree_difference = 0;
-       retval = diff_tree(&empty, &real, "", &revs->diffopt);
+       retval = diff_tree(&empty, &real, "", &revs->pruning);
        free(tree);
 
        return retval >= 0 && !tree_difference;
@@ -375,6 +381,9 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
        if (revs->prune_fn)
                revs->prune_fn(revs, commit);
 
+       if (revs->no_walk)
+               return;
+
        parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
@@ -451,21 +460,13 @@ static void limit_list(struct rev_info *revs)
        revs->commits = newlist;
 }
 
-static void add_one_commit(struct commit *commit, struct rev_info *revs)
-{
-       if (!commit || (commit->object.flags & SEEN))
-               return;
-       commit->object.flags |= SEEN;
-       commit_list_insert(commit, &revs->commits);
-}
-
 static int all_flags;
 static struct rev_info *all_revs;
 
 static int handle_one_ref(const char *path, const unsigned char *sha1)
 {
-       struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
-       add_one_commit(commit, all_revs);
+       struct object *object = get_reference(all_revs, path, sha1, all_flags);
+       add_pending_object(all_revs, object, "");
        return 0;
 }
 
@@ -476,12 +477,45 @@ 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));
-       revs->diffopt.recursive = 1;
-       revs->diffopt.add_remove = file_add_remove;
-       revs->diffopt.change = file_change;
+
+       revs->abbrev = DEFAULT_ABBREV;
+       revs->ignore_merges = 1;
+       revs->pruning.recursive = 1;
+       revs->pruning.add_remove = file_add_remove;
+       revs->pruning.change = file_change;
        revs->lifo = 1;
        revs->dense = 1;
        revs->prefix = setup_git_directory();
@@ -494,6 +528,10 @@ void init_revisions(struct rev_info *revs)
 
        revs->topo_setter = topo_sort_default_setter;
        revs->topo_getter = topo_sort_default_getter;
+
+       revs->commit_format = CMIT_FMT_DEFAULT;
+
+       diff_setup(&revs->diffopt);
 }
 
 /*
@@ -509,8 +547,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        const char **unrecognized = argv + 1;
        int left = 1;
 
-       init_revisions(revs);
-
        /* First, search for "--" */
        seen_dashdash = 0;
        for (i = 1; i < argc; i++) {
@@ -526,13 +562,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
        flags = 0;
        for (i = 1; i < argc; i++) {
-               struct commit *commit;
+               struct object *object;
                const char *arg = argv[i];
                unsigned char sha1[20];
                char *dotdot;
                int local_flags;
 
                if (*arg == '-') {
+                       int opts;
                        if (!strncmp(arg, "--max-count=", 12)) {
                                revs->max_count = atoi(arg + 12);
                                continue;
@@ -640,6 +677,76 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->unpacked = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "-r")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-t")) {
+                               revs->diff = 1;
+                               revs->diffopt.recursive = 1;
+                               revs->diffopt.tree_in_recursive = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-m")) {
+                               revs->ignore_merges = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-c")) {
+                               revs->diff = 1;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--cc")) {
+                               revs->diff = 1;
+                               revs->dense_combined_merges = 1;
+                               revs->combine_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "-v")) {
+                               revs->verbose_header = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--pretty", 8)) {
+                               revs->verbose_header = 1;
+                               revs->commit_format = get_commit_format(arg+8);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--root")) {
+                               revs->show_root_diff = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-commit-id")) {
+                               revs->no_commit_id = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--always")) {
+                               revs->always_show_header = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--no-abbrev")) {
+                               revs->abbrev = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev")) {
+                               revs->abbrev = DEFAULT_ABBREV;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--abbrev-commit")) {
+                               revs->abbrev_commit = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--full-diff")) {
+                               revs->diff = 1;
+                               revs->full_diff = 1;
+                               continue;
+                       }
+                       opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
+                       if (opts > 0) {
+                               revs->diff = 1;
+                               i += opts - 1;
+                               continue;
+                       }
                        *unrecognized++ = arg;
                        left++;
                        continue;
@@ -656,19 +763,31 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                this = "HEAD";
                        if (!get_sha1(this, from_sha1) &&
                            !get_sha1(next, sha1)) {
-                               struct commit *exclude;
-                               struct commit *include;
+                               struct object *exclude;
+                               struct object *include;
 
-                               exclude = get_commit_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
-                               include = get_commit_reference(revs, next, sha1, flags);
+                               exclude = get_reference(revs, this, from_sha1, flags ^ UNINTERESTING);
+                               include = get_reference(revs, next, sha1, flags);
                                if (!exclude || !include)
                                        die("Invalid revision range %s..%s", arg, next);
-                               add_one_commit(exclude, revs);
-                               add_one_commit(include, revs);
+
+                               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;
@@ -680,39 +799,72 @@ 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;
                }
-               commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
-               add_one_commit(commit, revs);
+               if (!seen_dashdash)
+                       verify_non_filename(revs->prefix, arg);
+               object = get_reference(revs, arg, sha1, flags ^ local_flags);
+               add_pending_object(revs, object, arg);
        }
-       if (def && !revs->commits) {
+       if (def && !revs->pending_objects) {
                unsigned char sha1[20];
-               struct commit *commit;
+               struct object *object;
                if (get_sha1(def, sha1) < 0)
                        die("bad default revision '%s'", def);
-               commit = get_commit_reference(revs, def, sha1, 0);
-               add_one_commit(commit, revs);
+               object = get_reference(revs, def, sha1, 0);
+               add_pending_object(revs, object, def);
        }
 
        if (revs->topo_order || revs->unpacked)
                revs->limited = 1;
 
        if (revs->prune_data) {
-               diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+               diff_tree_setup_paths(revs->prune_data, &revs->pruning);
                revs->prune_fn = try_to_simplify_commit;
+               if (!revs->full_diff)
+                       diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
+       }
+       if (revs->combine_merges) {
+               revs->ignore_merges = 0;
+               if (revs->dense_combined_merges &&
+                   (revs->diffopt.output_format != DIFF_FORMAT_DIFFSTAT))
+                       revs->diffopt.output_format = DIFF_FORMAT_PATCH;
        }
+       revs->diffopt.abbrev = revs->abbrev;
+       diff_setup_done(&revs->diffopt);
 
        return left;
 }
 
 void prepare_revision_walk(struct rev_info *revs)
 {
-       sort_by_date(&revs->commits);
+       struct object_list *list;
+
+       list = revs->pending_objects;
+       revs->pending_objects = NULL;
+       while (list) {
+               struct commit *commit = handle_commit(revs, list->item, list->name);
+               if (commit) {
+                       if (!(commit->object.flags & SEEN)) {
+                               commit->object.flags |= SEEN;
+                               insert_by_date(commit, &revs->commits);
+                       }
+               }
+               list = list->next;
+       }
+
+       if (revs->no_walk)
+               return;
        if (revs->limited)
                limit_list(revs);
        if (revs->topo_order)
index 4b27043..48d7b4c 100644 (file)
@@ -11,6 +11,7 @@
 #define ADDED          (1u<<7) /* Parents already parsed and added? */
 
 struct rev_info;
+struct log_info;
 
 typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit);
 
@@ -27,6 +28,7 @@ struct rev_info {
        /* Traversal flags */
        unsigned int    dense:1,
                        no_merges:1,
+                       no_walk:1,
                        remove_empty_trees:1,
                        lifo:1,
                        topo_order:1,
@@ -39,13 +41,32 @@ struct rev_info {
                        boundary:1,
                        parents:1;
 
+       /* Diff flags */
+       unsigned int    diff:1,
+                       full_diff:1,
+                       show_root_diff:1,
+                       no_commit_id:1,
+                       verbose_header:1,
+                       ignore_merges:1,
+                       combine_merges:1,
+                       dense_combined_merges:1,
+                       always_show_header:1;
+
+       /* Format info */
+       unsigned int    shown_one:1,
+                       abbrev_commit:1;
+       unsigned int    abbrev;
+       enum cmit_fmt   commit_format;
+       struct log_info *loginfo;
+
        /* special limits */
        int max_count;
        unsigned long max_age;
        unsigned long min_age;
 
-       /* paths limiting */
+       /* diff info for patches and for paths limiting */
        struct diff_options diffopt;
+       struct diff_options pruning;
 
        topo_sort_set_fn_t topo_setter;
        topo_sort_get_fn_t topo_getter;
diff --git a/setup.c b/setup.c
index cce9bb8..fe7f884 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -80,11 +80,31 @@ void verify_filename(const char *prefix, const char *arg)
        if (!lstat(name, &st))
                return;
        if (errno == ENOENT)
-               die("ambiguous argument '%s': unknown revision or filename\n"
-                   "Use '--' to separate filenames from revisions", arg);
+               die("ambiguous argument '%s': unknown revision or path not in the working tree.\n"
+                   "Use '--' to separate paths from revisions", arg);
        die("'%s': %s", arg, strerror(errno));
 }
 
+/*
+ * Opposite of the above: the command line did not have -- marker
+ * and we parsed the arg as a refname.  It should not be interpretable
+ * as a filename.
+ */
+void verify_non_filename(const char *prefix, const char *arg)
+{
+       const char *name;
+       struct stat st;
+
+       if (*arg == '-')
+               return; /* flag */
+       name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
+       if (!lstat(name, &st))
+               die("ambiguous argument '%s': both revision and filename\n"
+                   "Use '--' to separate filenames from revisions", arg);
+       if (errno != ENOENT)
+               die("'%s': %s", arg, strerror(errno));
+}
+
 const char **get_pathspec(const char *prefix, const char **pathspec)
 {
        const char *entry = *pathspec;
index f2d33af..5464828 100644 (file)
@@ -108,9 +108,10 @@ int safe_create_leading_directories(char *path)
 
 char * sha1_to_hex(const unsigned char *sha1)
 {
-       static char buffer[50];
+       static int bufno;
+       static char hexbuffer[4][50];
        static const char hex[] = "0123456789abcdef";
-       char *buf = buffer;
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
        int i;
 
        for (i = 0; i < 20; i++) {
index 4f92e12..345935b 100644 (file)
@@ -3,6 +3,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
+#include "tree-walk.h"
 
 static int find_short_object_filename(int len, const char *name, unsigned char *sha1)
 {
@@ -455,6 +456,19 @@ static int get_sha1_1(const char *name, int len, unsigned char *sha1)
  */
 int get_sha1(const char *name, unsigned char *sha1)
 {
+       int ret;
+       unsigned unused;
+
        prepare_alt_odb();
-       return get_sha1_1(name, strlen(name), sha1);
+       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);
+               }
+       }
+       return ret;
 }
index 6729a18..cf33989 100755 (executable)
@@ -174,6 +174,27 @@ test_expect_success \
     'git-ls-tree -r output for a known tree.' \
     'diff current expected'
 
+# But with -r -t we can have both.
+test_expect_success \
+    'showing tree with git-ls-tree -r -t' \
+    'git-ls-tree -r -t $tree >current'
+cat >expected <<\EOF
+100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
+120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
+040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
+100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
+120000 blob d8ce161addc5173867a3c3c730924388daedbc38   path2/file2sym
+040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
+100644 blob 0aa34cae68d0878578ad119c86ca2b5ed5b28376   path3/file3
+120000 blob 8599103969b43aff7e430efea79ca4636466794f   path3/file3sym
+040000 tree 3c5e5399f3a333eddecce7a9b9465b63f65f51e2   path3/subp3
+100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
+120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
+EOF
+test_expect_success \
+    'git-ls-tree -r output for a known tree.' \
+    'diff current expected'
+
 ################################################################
 rm .git/index
 test_expect_success \
@@ -205,4 +226,32 @@ test_expect_success \
     'no diff after checkout and git-update-index --refresh.' \
     'git-diff-files >current && cmp -s current /dev/null'
 
+################################################################
+P=087704a96baf1c2d1c869a8b084481e121c88b5b
+test_expect_success \
+    'git-commit-tree records the correct tree in a commit.' \
+    'commit0=$(echo NO | git-commit-tree $P) &&
+     tree=$(git show --pretty=raw $commit0 |
+        sed -n -e "s/^tree //p" -e "/^author /q") &&
+     test "z$tree" = "z$P"'
+
+test_expect_success \
+    'git-commit-tree records the correct parent in a commit.' \
+    'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+     parent=$(git show --pretty=raw $commit1 |
+        sed -n -e "s/^parent //p" -e "/^author /q") &&
+     test "z$commit0" = "z$parent"'
+
+test_expect_success \
+    'git-commit-tree omits duplicated parent in a commit.' \
+    'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+     parent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        sort -u) &&
+     test "z$commit0" = "z$parent" &&
+     numparent=$(git show --pretty=raw $commit2 |
+        sed -n -e "s/^parent //p" -e "/^author /q" |
+        wc -l) &&
+     test $numparent = 1'
+
 test_done
index d0ed242..75e4c9a 100755 (executable)
@@ -37,7 +37,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files "$1"`
+       clean_if_empty=`git-diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
index 861ef4c..4d175d8 100755 (executable)
@@ -20,7 +20,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files "$1"`
+       clean_if_empty=`git-diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
index f4d53c0..c7db20e 100755 (executable)
@@ -49,7 +49,7 @@ test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
 #test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
 
 cat > whatchanged.expect << EOF
-diff-tree VARIABLE (from root)
+commit VARIABLE
 Author: VARIABLE
 Date:   VARIABLE
 
@@ -72,7 +72,7 @@ index 0000000..557db03
 EOF
 
 git-whatchanged -p --root | \
-       sed -e "1s/^\(.\{10\}\).\{40\}/\1VARIABLE/" \
+       sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
                -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
 > whatchanged.output
 test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
index 8db329d..9e1544d 100755 (executable)
@@ -28,7 +28,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path should show nothing' \
-    'git-diff-index --cached $tree path >current &&
+    'git-diff-index --cached $tree -- path >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -36,7 +36,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1 should show path1/file1' \
-    'git-diff-index --cached $tree path1 >current &&
+    'git-diff-index --cached $tree -- path1 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -44,7 +44,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1/ should show path1/file1' \
-    'git-diff-index --cached $tree path1/ >current &&
+    'git-diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -52,14 +52,14 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
-    'git-diff-index --cached $tree file0 >current &&
+    'git-diff-index --cached $tree -- file0 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0/ should emit nothing.' \
-    'git-diff-index --cached $tree file0/ >current &&
+    'git-diff-index --cached $tree -- file0/ >current &&
      compare_diff_raw current expected'
 
 test_done
index bf8bfdf..9f7abb7 100644 (file)
@@ -115,3 +115,53 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb
        free(entry);
 }
 
+static int find_tree_entry(struct tree_desc *t, const char *name, unsigned char *result, unsigned *mode)
+{
+       int namelen = strlen(name);
+       while (t->size) {
+               const char *entry;
+               const unsigned char *sha1;
+               int entrylen, cmp;
+
+               sha1 = tree_entry_extract(t, &entry, mode);
+               update_tree_entry(t);
+               entrylen = strlen(entry);
+               if (entrylen > namelen)
+                       continue;
+               cmp = memcmp(name, entry, entrylen);
+               if (cmp > 0)
+                       continue;
+               if (cmp < 0)
+                       break;
+               if (entrylen == namelen) {
+                       memcpy(result, sha1, 20);
+                       return 0;
+               }
+               if (name[entrylen] != '/')
+                       continue;
+               if (!S_ISDIR(*mode))
+                       break;
+               if (++entrylen == namelen) {
+                       memcpy(result, sha1, 20);
+                       return 0;
+               }
+               return get_tree_entry(sha1, name + entrylen, result, mode);
+       }
+       return -1;
+}
+
+int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned char *sha1, unsigned *mode)
+{
+       int retval;
+       void *tree;
+       struct tree_desc t;
+
+       tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL);
+       if (!tree)
+               return -1;
+       t.buf = tree;
+       retval = find_tree_entry(&t, name, sha1, mode);
+       free(tree);
+       return retval;
+}
+
index 76893e3..47438fe 100644 (file)
@@ -22,4 +22,6 @@ typedef void (*traverse_callback_t)(int n, unsigned long mask, struct name_entry
 
 void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callback_t callback);
 
+int get_tree_entry(const unsigned char *, const char *, unsigned char *, unsigned *);
+
 #endif
index 1efac27..9fa3d2b 100644 (file)
@@ -6,6 +6,7 @@
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
+#include "tree-walk.h"
 
 /*
  * Default to not allowing changes to the list of files. The
@@ -328,7 +329,7 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
        return 0;
 }
 
-static int chmod_path(int flip, const char *path)
+static void chmod_path(int flip, const char *path)
 {
        int pos;
        struct cache_entry *ce;
@@ -336,21 +337,24 @@ static int chmod_path(int flip, const char *path)
 
        pos = cache_name_pos(path, strlen(path));
        if (pos < 0)
-               return -1;
+               goto fail;
        ce = active_cache[pos];
        mode = ntohl(ce->ce_mode);
        if (!S_ISREG(mode))
-               return -1;
+               goto fail;
        switch (flip) {
        case '+':
                ce->ce_mode |= htonl(0111); break;
        case '-':
                ce->ce_mode &= htonl(~0111); break;
        default:
-               return -1;
+               goto fail;
        }
        active_cache_changed = 1;
-       return 0;
+       report("chmod %cx '%s'", flip, path);
+       return;
+ fail:
+       die("git-update-index: cannot chmod %cx '%s'", flip, path);
 }
 
 static struct cache_file cache_file;
@@ -469,7 +473,125 @@ static void read_index_info(int line_termination)
 }
 
 static const char update_index_usage[] =
-"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--cacheinfo] [--chmod=(+|-)x] [--info-only] [--force-remove] [--stdin] [--index-info] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+"git-update-index [-q] [--add] [--replace] [--remove] [--unmerged] [--refresh] [--really-refresh] [--cacheinfo] [--chmod=(+|-)x] [--assume-unchanged] [--info-only] [--force-remove] [--stdin] [--index-info] [--unresolve] [--ignore-missing] [-z] [--verbose] [--] <file>...";
+
+static unsigned char head_sha1[20];
+static unsigned char merge_head_sha1[20];
+
+static struct cache_entry *read_one_ent(const char *which,
+                                       unsigned char *ent, const char *path,
+                                       int namelen, int stage)
+{
+       unsigned mode;
+       unsigned char sha1[20];
+       int size;
+       struct cache_entry *ce;
+
+       if (get_tree_entry(ent, path, sha1, &mode)) {
+               error("%s: not in %s branch.", path, which);
+               return NULL;
+       }
+       if (mode == S_IFDIR) {
+               error("%s: not a blob in %s branch.", path, which);
+               return NULL;
+       }
+       size = cache_entry_size(namelen);
+       ce = xcalloc(1, size);
+
+       memcpy(ce->sha1, sha1, 20);
+       memcpy(ce->name, path, namelen);
+       ce->ce_flags = create_ce_flags(namelen, stage);
+       ce->ce_mode = create_ce_mode(mode);
+       return ce;
+}
+
+static int unresolve_one(const char *path)
+{
+       int namelen = strlen(path);
+       int pos;
+       int ret = 0;
+       struct cache_entry *ce_2 = NULL, *ce_3 = NULL;
+
+       /* See if there is such entry in the index. */
+       pos = cache_name_pos(path, namelen);
+       if (pos < 0) {
+               /* If there isn't, either it is unmerged, or
+                * resolved as "removed" by mistake.  We do not
+                * want to do anything in the former case.
+                */
+               pos = -pos-1;
+               if (pos < active_nr) {
+                       struct cache_entry *ce = active_cache[pos];
+                       if (ce_namelen(ce) == namelen &&
+                           !memcmp(ce->name, path, namelen)) {
+                               fprintf(stderr,
+                                       "%s: skipping still unmerged path.\n",
+                                       path);
+                               goto free_return;
+                       }
+               }
+       }
+
+       /* Grab blobs from given path from HEAD and MERGE_HEAD,
+        * stuff HEAD version in stage #2,
+        * stuff MERGE_HEAD version in stage #3.
+        */
+       ce_2 = read_one_ent("our", head_sha1, path, namelen, 2);
+       ce_3 = read_one_ent("their", merge_head_sha1, path, namelen, 3);
+
+       if (!ce_2 || !ce_3) {
+               ret = -1;
+               goto free_return;
+       }
+       if (!memcmp(ce_2->sha1, ce_3->sha1, 20) &&
+           ce_2->ce_mode == ce_3->ce_mode) {
+               fprintf(stderr, "%s: identical in both, skipping.\n",
+                       path);
+               goto free_return;
+       }
+
+       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);
+               ret = -1;
+               goto free_return;
+       }
+       if (!add_cache_entry(ce_3, ADD_CACHE_OK_TO_ADD))
+               return 0;
+       error("%s: cannot add their version to the index.", path);
+       ret = -1;
+ free_return:
+       free(ce_2);
+       free(ce_3);
+       return ret;
+}
+
+static void read_head_pointers(void)
+{
+       if (read_ref(git_path("HEAD"), head_sha1))
+               die("No HEAD -- no initial commit yet?\n");
+       if (read_ref(git_path("MERGE_HEAD"), merge_head_sha1)) {
+               fprintf(stderr, "Not in the middle of a merge.\n");
+               exit(0);
+       }
+}
+
+static int do_unresolve(int ac, const char **av)
+{
+       int i;
+       int err = 0;
+
+       /* Read HEAD and MERGE_HEAD; if MERGE_HEAD does not exist, we
+        * are not doing a merge, so exit with success status.
+        */
+       read_head_pointers();
+
+       for (i = 1; i < ac; i++) {
+               const char *arg = av[i];
+               err |= unresolve_one(arg);
+       }
+       return err;
+}
 
 int main(int argc, const char **argv)
 {
@@ -478,6 +600,7 @@ int main(int argc, const char **argv)
        int read_from_stdin = 0;
        const char *prefix = setup_git_directory();
        int prefix_length = prefix ? strlen(prefix) : 0;
+       char set_executable_bit = 0;
 
        git_config(git_default_config);
 
@@ -544,8 +667,7 @@ int main(int argc, const char **argv)
                            !strcmp(path, "--chmod=+x")) {
                                if (argc <= i+1)
                                        die("git-update-index: %s <path>", path);
-                               if (chmod_path(path[8], argv[++i]))
-                                       die("git-update-index: %s cannot chmod %s", path, argv[i]);
+                               set_executable_bit = path[8];
                                continue;
                        }
                        if (!strcmp(path, "--assume-unchanged")) {
@@ -581,6 +703,12 @@ int main(int argc, const char **argv)
                                read_index_info(line_termination);
                                break;
                        }
+                       if (!strcmp(path, "--unresolve")) {
+                               has_errors = do_unresolve(argc - i, argv + i);
+                               if (has_errors)
+                                       active_cache_changed = 0;
+                               goto finish;
+                       }
                        if (!strcmp(path, "--ignore-missing")) {
                                not_new = 1;
                                continue;
@@ -594,6 +722,8 @@ int main(int argc, const char **argv)
                        die("unknown option %s", path);
                }
                update_one(path, prefix, prefix_length);
+               if (set_executable_bit)
+                       chmod_path(set_executable_bit, path);
        }
        if (read_from_stdin) {
                struct strbuf buf;
@@ -608,10 +738,16 @@ int main(int argc, const char **argv)
                        else
                                path_name = buf.buf;
                        update_one(path_name, prefix, prefix_length);
+                       if (set_executable_bit) {
+                               const char *p = prefix_path(prefix, prefix_length, path_name);
+                               chmod_path(set_executable_bit, p);
+                       }
                        if (path_name != buf.buf)
                                free(path_name);
                }
        }
+
+ finish:
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_index_file(&cache_file))