Merge branch 'ml/cvsserver' into next
authorJunio C Hamano <junkio@cox.net>
Thu, 2 Mar 2006 01:45:09 +0000 (17:45 -0800)
committerJunio C Hamano <junkio@cox.net>
Thu, 2 Mar 2006 01:45:09 +0000 (17:45 -0800)
* ml/cvsserver:
  cvsserver: Checkout correctly on Eclipse

28 files changed:
Documentation/git-checkout-index.txt
Documentation/git-rev-list.txt
Documentation/git-svnimport.txt
INSTALL
Makefile
blame.c [new file with mode: 0644]
cache.h
cat-file.c
checkout-index.c
count-delta.c
diff-delta.c
diffcore-break.c
diffcore-delta.c [new file with mode: 0644]
diffcore-rename.c
diffcore.h
epoch.c [deleted file]
epoch.h [deleted file]
git-archimport.perl
git-svnimport.perl
git-verify-tag.sh
git.c
pager.c [new file with mode: 0644]
refs.c
rev-list.c
rev-parse.c
revision.c [new file with mode: 0644]
revision.h [new file with mode: 0644]
t/t6001-rev-list-merge-order.sh [deleted file]

index 2a1e526..b0b6588 100644 (file)
@@ -10,7 +10,9 @@ SYNOPSIS
 --------
 [verse]
 'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
-                  [--stage=<number>] [--] <file>...
+                  [--stage=<number>]
+                  [-z] [--stdin]
+                  [--] [<file>]\*
 
 DESCRIPTION
 -----------
@@ -45,6 +47,15 @@ OPTIONS
        Instead of checking out unmerged entries, copy out the
        files from named stage.  <number> must be between 1 and 3.
 
+--stdin::
+       Instead of taking list of paths from the command line,
+       read list of paths from the standard input.  Paths are
+       separated by LF (i.e. one path per line) by default.
+
+-z::
+       Only meaningful with `--stdin`; paths are separated with
+       NUL character instead of LF.
+
 --::
        Do not interpret any more arguments as options.
 
@@ -64,7 +75,12 @@ $ find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
 
 which will force all existing `*.h` files to be replaced with their
 cached copies. If an empty command line implied "all", then this would
-force-refresh everything in the index, which was not the point.
+force-refresh everything in the index, which was not the point.  But
+since git-checkout-index accepts --stdin it would be faster to use:
+
+----------------
+$ find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+----------------
 
 The `--` is just a good idea when you know the rest will be filenames;
 it will prevent problems with a filename of, for example,  `-a`.
index 1c6146c..5b306d6 100644 (file)
@@ -16,7 +16,7 @@ SYNOPSIS
             [ \--no-merges ]
             [ \--remove-empty ]
             [ \--all ]
-            [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
+            [ \--topo-order ]
             [ \--parents ]
             [ \--objects [ \--unpacked ] ]
             [ \--pretty | \--header ]
@@ -94,57 +94,10 @@ OPTIONS
        topological order (i.e. descendant commits are shown
        before their parents).
 
---merge-order::
-       When specified the commit history is decomposed into a unique
-       sequence of minimal, non-linear epochs and maximal, linear epochs.
-       Non-linear epochs are then linearised by sorting them into merge
-       order, which is described below.
-+
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
-link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-+
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-+
-    1. if a commit P is reachable from commit N, commit P sorts after commit N
-       in the linearised list.
-    2. if Pi and Pj are any two parents of a merge M (with i < j), then any
-       commit N, such that N is reachable from Pj but not reachable from Pi,
-       sorts before all commits reachable from Pi.
-+
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
-+
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
-
---show-breaks::
-       Each item of the list is output with a 2-character prefix consisting
-       of one of: (|), (^), (=) followed by a space.
-+
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-+
-Commits marked with (|) are direct parents of commits immediately preceding
-the marked commit in the list.
-+
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
-represent an arbitrary DAG in a linear form.
-+
-`--show-breaks` is only valid if `--merge-order` is also specified.
-
-
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
 
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
-
 Documentation
 --------------
 Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
index 912a808..a158813 100644 (file)
@@ -82,6 +82,11 @@ When importing incrementally, you might need to edit the .git/svn2git file.
        "username". If encountering a commit made by a user not in the
        list, abort.
 
+       For convenience, this data is saved to $GIT_DIR/svn-authors
+       each time the -A option is provided, and read from that same
+       file each time git-svnimport is run with an existing GIT
+       repository without -A.
+
 -m::
        Attempt to detect merges based on the commit message. This option
        will enable default regexes that try to capture the name source
diff --git a/INSTALL b/INSTALL
index 433449f..63af8ec 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -40,9 +40,7 @@ Issues of note:
 
          If you don't have openssl, you can use one of the SHA1 libraries
          that come with git (git includes the one from Mozilla, and has
-         its own PowerPC-optimized one too - see the Makefile), and you
-         can avoid the bignum support by excising git-rev-list support
-         for "--merge-order" (by hand).
+         its own PowerPC and ARM optimized ones too - see the Makefile).
 
        - "libcurl" and "curl" executable.  git-http-fetch and
          git-fetch use them.  If you do not use http
index 19578fc..b6d8804 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -6,8 +6,8 @@ all:
 # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
 # choice) has very fast version optimized for i586.
 #
-# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will
-# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
 #
 # Define NO_CURL if you do not have curl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
@@ -165,7 +165,7 @@ PROGRAMS = \
        git-upload-pack$X git-verify-pack$X git-write-tree$X \
        git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
        git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
-       git-describe$X git-merge-tree$X
+       git-describe$X git-merge-tree$X git-blame$X
 
 # what 'all' will build and 'install' will install, in gitexecdir
 ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -191,12 +191,13 @@ LIB_FILE=libgit.a
 
 LIB_H = \
        blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
-       diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
-       run-command.h strbuf.h tag.h tree.h git-compat-util.h
+       diff.h object.h pack.h pkt-line.h quote.h refs.h \
+       run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h
 
 DIFF_OBJS = \
        diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
-       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o
+       diffcore-pickaxe.o diffcore-rename.o tree-diff.o combine-diff.o \
+       diffcore-delta.o
 
 LIB_OBJS = \
        blob.o commit.o connect.o count-delta.o csum-file.o \
@@ -205,7 +206,7 @@ LIB_OBJS = \
        quote.o read-cache.o refs.o run-command.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
-       fetch-clone.o \
+       fetch-clone.o revision.o pager.o \
        $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
@@ -328,7 +329,6 @@ ifndef NO_CURL
 endif
 
 ifndef NO_OPENSSL
-       LIB_OBJS += epoch.o
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                # Again this may be problematic -- gcc does not always want -R.
@@ -454,7 +454,7 @@ strip: $(PROGRAMS) git$X
 
 git$X: git.c $(LIB_FILE)
        $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
+               $(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS)
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        rm -f $@
diff --git a/blame.c b/blame.c
new file mode 100644 (file)
index 0000000..dbce7e2
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,442 @@
+#include <assert.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+
+#define DEBUG 0
+
+struct commit** blame_lines;
+int num_blame_lines;
+
+struct util_info
+{
+    int* line_map;
+    int num_lines;
+    unsigned char sha1[20]; /* blob sha, not commit! */
+    char* buf;
+    unsigned long size;
+//    const char* path;
+};
+
+struct chunk
+{
+    int off1, len1; // ---
+    int off2, len2; // +++
+};
+
+struct patch
+{
+    struct chunk* chunks;
+    int num;
+};
+
+static void get_blob(struct commit* commit);
+
+int num_get_patch = 0;
+int num_commits = 0;
+
+struct patch* get_patch(struct commit* commit, struct commit* other)
+{
+    struct patch* ret = xmalloc(sizeof(struct patch));
+    ret->chunks = NULL;
+    ret->num = 0;
+
+    struct util_info* info_c = (struct util_info*) commit->object.util;
+    struct util_info* info_o = (struct util_info*) other->object.util;
+
+    if(!memcmp(info_c->sha1, info_o->sha1, 20))
+        return ret;
+
+    get_blob(commit);
+    get_blob(other);
+
+    FILE* fout = fopen("/tmp/git-blame-tmp1", "w");
+    if(!fout)
+        die("fopen tmp1 failed: %s", strerror(errno));
+
+    if(fwrite(info_c->buf, info_c->size, 1, fout) != 1)
+        die("fwrite 1 failed: %s", strerror(errno));
+    fclose(fout);
+
+    fout = fopen("/tmp/git-blame-tmp2", "w");
+    if(!fout)
+        die("fopen tmp2 failed: %s", strerror(errno));
+
+    if(fwrite(info_o->buf, info_o->size, 1, fout) != 1)
+        die("fwrite 2 failed: %s", strerror(errno));
+    fclose(fout);
+
+    FILE* fin = popen("diff -u0 /tmp/git-blame-tmp1 /tmp/git-blame-tmp2", "r");
+    if(!fin)
+        die("popen failed: %s", strerror(errno));
+
+    char buf[1024];
+    while(fgets(buf, sizeof(buf), fin)) {
+        if(buf[0] != '@' || buf[1] != '@')
+            continue;
+
+        if(DEBUG)
+            printf("chunk line: %s", buf);
+        ret->num++;
+        ret->chunks = xrealloc(ret->chunks, sizeof(struct chunk)*ret->num);
+        struct chunk* chunk = &ret->chunks[ret->num-1];
+
+        assert(!strncmp(buf, "@@ -", 4));
+
+        char* start = buf+4;
+        char* sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+            assert(ret == 2);
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off1);
+            assert(ret == 1);
+            chunk->len1 = 1;
+        }
+        *sp = ' ';
+
+        start = sp+1;
+        sp = index(start, ' ');
+        *sp = '\0';
+        if(index(start, ',')) {
+            int ret = sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+            assert(ret == 2);
+        } else {
+            int ret = sscanf(start, "%d", &chunk->off2);
+            assert(ret == 1);
+            chunk->len2 = 1;
+        }
+        *sp = ' ';
+
+        if(chunk->off1 > 0)
+            chunk->off1 -= 1;
+        if(chunk->off2 > 0)
+            chunk->off2 -= 1;
+
+        assert(chunk->off1 >= 0);
+        assert(chunk->off2 >= 0);
+    }
+    fclose(fin);
+
+    num_get_patch++;
+    return ret;
+}
+
+void free_patch(struct patch* p)
+{
+    free(p->chunks);
+    free(p);
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage);
+
+
+static unsigned char blob_sha1[20];
+static int get_blob_sha1(struct tree* t, const char* pathname, unsigned char* sha1)
+{
+    const char *pathspec[2];
+    pathspec[0] = pathname;
+    pathspec[1] = NULL;
+    memset(blob_sha1, 0, sizeof(blob_sha1));
+    read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+    int i;
+    for(i = 0; i < 20; i++) {
+        if(blob_sha1[i] != 0)
+            break;
+    }
+
+    if(i == 20)
+        return -1;
+
+    memcpy(sha1, blob_sha1, 20);
+    return 0;
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base, int baselen,
+                                  const char *pathname, unsigned mode, int stage)
+{
+//    printf("Got blob: %s base: '%s' baselen: %d pathname: '%s' mode: %o stage: %d\n",
+//           sha1_to_hex(sha1), base, baselen, pathname, mode, stage);
+
+    if(S_ISDIR(mode))
+        return READ_TREE_RECURSIVE;
+
+    memcpy(blob_sha1, sha1, 20);
+    return -1;
+}
+
+static void get_blob(struct commit* commit)
+{
+    struct util_info* info = commit->object.util;
+    char type[20];
+
+    if(info->buf)
+        return;
+
+    info->buf = read_sha1_file(info->sha1, type, &info->size);
+    assert(!strcmp(type, "blob"));
+}
+
+void print_patch(struct patch* p)
+{
+    printf("Num chunks: %d\n", p->num);
+    int i;
+    for(i = 0; i < p->num; i++) {
+        printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, p->chunks[i].off2, p->chunks[i].len2);
+    }
+}
+
+
+// p is a patch from commit to other.
+void fill_line_map(struct commit* commit, struct commit* other, struct patch* p)
+{
+    int num_lines = ((struct util_info*) commit->object.util)->num_lines;
+    int* line_map = ((struct util_info*) commit->object.util)->line_map;
+    int num_lines2 = ((struct util_info*) other->object.util)->num_lines;
+    int* line_map2 = ((struct util_info*) other->object.util)->line_map;
+    int cur_chunk = 0;
+    int i1, i2;
+
+    if(p->num && DEBUG)
+        print_patch(p);
+
+    for(i1 = 0; i1 < num_lines; i1++)
+        line_map[i1] = -1;
+
+    if(DEBUG)
+        printf("num lines 1: %d num lines 2: %d\n", num_lines, num_lines2);
+
+    for(i1 = 0, i2 = 0; i1 < num_lines; i1++, i2++) {
+        if(DEBUG > 1)
+            printf("%d %d\n", i1, i2);
+
+        if(i2 >= num_lines2)
+            break;
+
+        line_map[i1] = line_map2[i2];
+
+        struct chunk* chunk = NULL;
+        if(cur_chunk < p->num)
+            chunk = &p->chunks[cur_chunk];
+
+        if(chunk && chunk->off1 == i1) {
+            i2 = chunk->off2;
+
+            if(chunk->len1 > 0)
+                i1 += chunk->len1-1;
+            if(chunk->len2 > 0)
+                i2 += chunk->len2-1;
+            cur_chunk++;
+        }
+    }
+}
+
+int map_line(struct commit* commit, int line)
+{
+    struct util_info* info = commit->object.util;
+    assert(line >= 0 && line < info->num_lines);
+    return info->line_map[line];
+}
+
+int fill_util_info(struct commit* commit, const char* path)
+{
+    if(commit->object.util)
+        return 0;
+
+    struct util_info* util = xmalloc(sizeof(struct util_info));
+    util->buf = NULL;
+    util->size = 0;
+    util->num_lines = -1;
+    util->line_map = NULL;
+
+    commit->object.util = util;
+
+    if(get_blob_sha1(commit->tree, path, util->sha1))
+        return -1;
+
+    return 0;
+}
+
+void alloc_line_map(struct commit* commit)
+{
+    struct util_info* util = commit->object.util;
+
+    if(util->line_map)
+        return;
+
+    get_blob(commit);
+
+    int i;
+    util->num_lines = 0;
+    for(i = 0; i < util->size; i++) {
+        if(util->buf[i] == '\n')
+            util->num_lines++;
+    }
+    util->line_map = xmalloc(sizeof(int)*util->num_lines);
+}
+
+void copy_line_map(struct commit* dst, struct commit* src)
+{
+    struct util_info* u_dst = dst->object.util;
+    struct util_info* u_src = src->object.util;
+
+    u_dst->line_map = u_src->line_map;
+    u_dst->num_lines = u_src->num_lines;
+    u_dst->buf = u_src->buf;
+    u_dst->size = u_src->size;
+}
+
+void process_commits(struct commit_list* list, const char* path)
+{
+    int i;
+
+    while(list) {
+        struct commit* commit = pop_commit(&list);
+        struct commit_list* parents;
+        struct util_info* info;
+
+        info = commit->object.util;
+        num_commits++;
+        if(DEBUG)
+            printf("\nProcessing commit: %d %s\n", num_commits, sha1_to_hex(commit->object.sha1));
+        for(parents = commit->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+
+            if(parse_commit(parent) < 0)
+                die("parse_commit error");
+
+            if(DEBUG)
+                printf("parent: %s\n", sha1_to_hex(parent->object.sha1));
+
+            if(fill_util_info(parent, path))
+                continue;
+
+            // Temporarily assign everything to the parent.
+            int num_blame = 0;
+            for(i = 0; i < num_blame_lines; i++) {
+                if(blame_lines[i] == commit) {
+                    num_blame++;
+                    blame_lines[i] = parent;
+                }
+            }
+
+            if(num_blame == 0)
+                continue;
+
+            struct patch* patch = get_patch(parent, commit);
+            if(patch->num == 0) {
+                copy_line_map(parent, commit);
+            } else {
+                alloc_line_map(parent);
+                fill_line_map(parent, commit, patch);
+            }
+
+            for(i = 0; i < patch->num; i++) {
+                int l;
+                for(l = 0; l < patch->chunks[i].len2; l++) {
+                    int mapped_line = map_line(commit, patch->chunks[i].off2 + l);
+                    if(mapped_line != -1 && blame_lines[mapped_line] == parent)
+                        blame_lines[mapped_line] = commit;
+                }
+            }
+            free_patch(patch);
+        }
+    }
+}
+
+#define SEEN 1
+struct commit_list* get_commit_list(struct commit* commit, const char* pathname)
+{
+    struct commit_list* ret = NULL;
+    struct commit_list* process = NULL;
+    unsigned char sha1[20];
+
+    commit_list_insert(commit, &process);
+
+    while(process) {
+        struct commit* com = pop_commit(&process);
+        if(com->object.flags & SEEN)
+            continue;
+
+        com->object.flags |= SEEN;
+        commit_list_insert(com, &ret);
+        struct commit_list* parents;
+
+        parse_commit(com);
+
+        for(parents = com->parents;
+            parents != NULL; parents = parents->next) {
+            struct commit* parent = parents->item;
+
+            parse_commit(parent);
+
+            if(!get_blob_sha1(parent->tree, pathname, sha1))
+                commit_list_insert(parent, &process);
+        }
+    }
+
+    return ret;
+}
+
+int main(int argc, const char **argv)
+{
+    unsigned char sha1[20];
+    struct commit *commit;
+    const char* filename;
+    int i;
+
+    setup_git_directory();
+
+    if (argc != 3)
+        die("Usage: blame commit-ish file");
+
+    if (get_sha1(argv[1], sha1))
+        die("get_sha1 failed");
+
+    commit = lookup_commit_reference(sha1);
+
+    filename = argv[2];
+
+    struct commit_list* list = get_commit_list(commit, filename);
+    sort_in_topological_order(&list, 1);
+
+    if(fill_util_info(commit, filename)) {
+        printf("%s not found in %s\n", filename, argv[1]);
+        return 0;
+    }
+    alloc_line_map(commit);
+
+    struct util_info* util = commit->object.util;
+    num_blame_lines = util->num_lines;
+    blame_lines = xmalloc(sizeof(struct commit*)*num_blame_lines);
+
+
+    for(i = 0; i < num_blame_lines; i++) {
+        blame_lines[i] = commit;
+
+        ((struct util_info*) commit->object.util)->line_map[i] = i;
+    }
+
+    process_commits(list, filename);
+
+    for(i = 0; i < num_blame_lines; i++) {
+        printf("%d %s\n", i+1-1, sha1_to_hex(blame_lines[i]->object.sha1));
+//        printf("%d %s\n", i+1-1, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+    }
+
+    if(DEBUG) {
+        printf("num get patch: %d\n", num_get_patch);
+        printf("num commits: %d\n", num_commits);
+    }
+
+    return 0;
+}
diff --git a/cache.h b/cache.h
index 0d3b244..8dc1de1 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -354,4 +354,7 @@ extern int copy_fd(int ifd, int ofd);
 extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
 extern int receive_keep_pack(int fd[2], const char *me, int quiet);
 
+/* pager.c */
+extern void setup_pager(void);
+
 #endif /* CACHE_H */
index 96d66b4..1a613f3 100644 (file)
@@ -4,6 +4,92 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 #include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_buffer(const char *buf, unsigned long size)
+{
+       while (size > 0) {
+               long ret = xwrite(1, buf, size);
+               if (ret < 0) {
+                       /* Ignore epipe */
+                       if (errno == EPIPE)
+                               break;
+                       die("git-cat-file: %s", strerror(errno));
+               } else if (!ret) {
+                       die("git-cat-file: disk full?");
+               }
+               size -= ret;
+               buf += ret;
+       }
+}
+
+static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+       /* the parser in tag.c is useless here. */
+       const char *endp = buf + size;
+       const char *cp = buf;
+
+       while (cp < endp) {
+               char c = *cp++;
+               if (c != '\n')
+                       continue;
+               if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+                       const char *tagger = cp;
+
+                       /* Found the tagger line.  Copy out the contents
+                        * of the buffer so far.
+                        */
+                       flush_buffer(buf, cp - buf);
+
+                       /*
+                        * Do something intelligent, like pretty-printing
+                        * the date.
+                        */
+                       while (cp < endp) {
+                               if (*cp++ == '\n') {
+                                       /* tagger to cp is a line
+                                        * that has ident and time.
+                                        */
+                                       const char *sp = tagger;
+                                       char *ep;
+                                       unsigned long date;
+                                       long tz;
+                                       while (sp < cp && *sp != '>')
+                                               sp++;
+                                       if (sp == cp) {
+                                               /* give up */
+                                               flush_buffer(tagger,
+                                                            cp - tagger);
+                                               break;
+                                       }
+                                       while (sp < cp &&
+                                              !('0' <= *sp && *sp <= '9'))
+                                               sp++;
+                                       flush_buffer(tagger, sp - tagger);
+                                       date = strtoul(sp, &ep, 10);
+                                       tz = strtol(ep, NULL, 10);
+                                       sp = show_date(date, tz);
+                                       flush_buffer(sp, strlen(sp));
+                                       xwrite(1, "\n", 1);
+                                       break;
+                               }
+                       }
+                       break;
+               }
+               if (cp < endp && *cp == '\n')
+                       /* end of header */
+                       break;
+       }
+       /* At this point, we have copied out the header up to the end of
+        * the tagger line and cp points at one past \n.  It could be the
+        * next header line after the tagger line, or it could be another
+        * \n that marks the end of the headers.  We need to copy out the
+        * remainder as is.
+        */
+       if (cp < endp)
+               flush_buffer(cp, endp - cp);
+       return 0;
+}
 
 int main(int argc, char **argv)
 {
@@ -15,7 +101,7 @@ int main(int argc, char **argv)
 
        setup_git_directory();
        if (argc != 3 || get_sha1(argv[2], sha1))
-               usage("git-cat-file [-t|-s|-e|<type>] <sha1>");
+               usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
 
        opt = 0;
        if ( argv[1][0] == '-' ) {
@@ -43,6 +129,23 @@ int main(int argc, char **argv)
        case 'e':
                return !has_sha1_file(sha1);
 
+       case 'p':
+               if (get_sha1(argv[2], sha1) ||
+                   sha1_object_info(sha1, type, NULL))
+                       die("Not a valid object name %s", argv[2]);
+
+               /* custom pretty-print here */
+               if (!strcmp(type, "tree"))
+                       return execl_git_cmd("ls-tree", argv[2], NULL);
+
+               buf = read_sha1_file(sha1, type, &size);
+               if (!buf)
+                       die("Cannot read object %s", argv[2]);
+               if (!strcmp(type, "tag"))
+                       return pprint_tag(sha1, buf, size);
+
+               /* otherwise just spit out the data */
+               break;
        case 0:
                buf = read_object_with_reference(sha1, argv[1], &size, NULL);
                break;
@@ -54,18 +157,6 @@ int main(int argc, char **argv)
        if (!buf)
                die("git-cat-file %s: bad file", argv[2]);
 
-       while (size > 0) {
-               long ret = xwrite(1, buf, size);
-               if (ret < 0) {
-                       /* Ignore epipe */
-                       if (errno == EPIPE)
-                               break;
-                       die("git-cat-file: %s", strerror(errno));
-               } else if (!ret) {
-                       die("git-cat-file: disk full?");
-               }
-               size -= ret;
-               buf += ret;
-       }
+       flush_buffer(buf, size);
        return 0;
 }
index 957b4a8..f54c606 100644 (file)
  *
  *     find . -name '*.h' -print0 | xargs -0 git-checkout-index -f --
  *
+ * or:
+ *
+ *     find . -name '*.h' -print0 | git-checkout-index -f -z --stdin
+ *
  * which will force all existing *.h files to be replaced with
  * their cached copies. If an empty command line implied "all",
  * then this would force-refresh everything in the cache, which
@@ -33,6 +37,8 @@
  * but get used to it in scripting!).
  */
 #include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
 
 static const char *prefix;
 static int prefix_length;
@@ -114,6 +120,8 @@ int main(int argc, char **argv)
        int i;
        int newfd = -1;
        int all = 0;
+       int read_from_stdin = 0;
+       int line_termination = '\n';
 
        prefix = setup_git_directory();
        git_config(git_default_config);
@@ -156,6 +164,17 @@ int main(int argc, char **argv)
                                die("cannot open index.lock file.");
                        continue;
                }
+               if (!strcmp(arg, "-z")) {
+                       line_termination = 0;
+                       continue;
+               }
+               if (!strcmp(arg, "--stdin")) {
+                       if (i != argc - 1)
+                               die("--stdin must be at the end");
+                       read_from_stdin = 1;
+                       i++; /* do not consider arg as a file name */
+                       break;
+               }
                if (!strncmp(arg, "--prefix=", 9)) {
                        state.base_dir = arg+9;
                        state.base_dir_len = strlen(state.base_dir);
@@ -191,9 +210,31 @@ int main(int argc, char **argv)
 
                if (all)
                        die("git-checkout-index: don't mix '--all' and explicit filenames");
+               if (read_from_stdin)
+                       die("git-checkout-index: don't mix '--stdin' and explicit filenames");
                checkout_file(prefix_path(prefix, prefix_length, arg));
        }
 
+       if (read_from_stdin) {
+               struct strbuf buf;
+               if (all)
+                       die("git-checkout-index: don't mix '--all' and '--stdin'");
+               strbuf_init(&buf);
+               while (1) {
+                       char *path_name;
+                       read_line(&buf, stdin, line_termination);
+                       if (buf.eof)
+                               break;
+                       if (line_termination && buf.buf[0] == '"')
+                               path_name = unquote_c_style(buf.buf, NULL);
+                       else
+                               path_name = buf.buf;
+                       checkout_file(prefix_path(prefix, prefix_length, path_name));
+                       if (path_name != buf.buf)
+                               free(path_name);
+               }
+       }
+
        if (all)
                checkout_all();
 
index 058a2aa..3ee3a0c 100644 (file)
@@ -3,11 +3,74 @@
  * The delta-parsing part is almost straight copy of patch-delta.c
  * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
  */
+#include "cache.h"
+#include "delta.h"
+#include "count-delta.h"
 #include <stdlib.h>
 #include <string.h>
 #include <limits.h>
-#include "delta.h"
-#include "count-delta.h"
+
+struct span {
+       struct span *next;
+       unsigned long ofs;
+       unsigned long end;
+};
+
+static void touch_range(struct span **span,
+                       unsigned long ofs, unsigned long end)
+{
+       struct span *e = *span;
+       struct span *p = NULL;
+
+       while (e && e->ofs <= ofs) {
+               again:
+               if (ofs < e->end) {
+                       while (e->end < end) {
+                               if (e->next && e->next->ofs <= end) {
+                                       e->end = e->next->ofs;
+                                       e = e->next;
+                               }
+                               else {
+                                       e->end = end;
+                                       return;
+                               }
+                       }
+                       return;
+               }
+               p = e;
+               e = e->next;
+       }
+       if (e && e->ofs <= end) {
+               e->ofs = ofs;
+               goto again;
+       }
+       else {
+               e = xmalloc(sizeof(*e));
+               e->ofs = ofs;
+               e->end = end;
+               if (p) {
+                       e->next = p->next;
+                       p->next = e;
+               }
+               else {
+                       e->next = *span;
+                       *span = e;
+               }
+       }
+}
+
+static unsigned long count_range(struct span *s)
+{
+       struct span *t;
+       unsigned long sz = 0;
+       while (s) {
+               t = s;
+               sz += s->end - s->ofs;
+               s = s->next;
+               free(t);
+       }
+       return sz;
+}
 
 /*
  * NOTE.  We do not _interpret_ delta fully.  As an approximation, we
 int count_delta(void *delta_buf, unsigned long delta_size,
                unsigned long *src_copied, unsigned long *literal_added)
 {
-       unsigned long copied_from_source, added_literal;
+       unsigned long added_literal;
        const unsigned char *data, *top;
        unsigned char cmd;
        unsigned long src_size, dst_size, out;
+       struct span *span = NULL;
 
        if (delta_size < DELTA_SIZE_MIN)
                return -1;
@@ -35,7 +99,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        src_size = get_delta_hdr_size(&data);
        dst_size = get_delta_hdr_size(&data);
 
-       added_literal = copied_from_source = out = 0;
+       added_literal = out = 0;
        while (data < top) {
                cmd = *data++;
                if (cmd & 0x80) {
@@ -49,7 +113,7 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                        if (cmd & 0x40) cp_size |= (*data++ << 16);
                        if (cp_size == 0) cp_size = 0x10000;
 
-                       copied_from_source += cp_size;
+                       touch_range(&span, cp_off, cp_off+cp_size);
                        out += cp_size;
                } else {
                        /* write literal into dst */
@@ -59,6 +123,8 @@ int count_delta(void *delta_buf, unsigned long delta_size,
                }
        }
 
+       *src_copied = count_range(span);
+
        /* sanity check */
        if (data != top || out != dst_size)
                return -1;
@@ -66,7 +132,6 @@ int count_delta(void *delta_buf, unsigned long delta_size,
        /* delete size is what was _not_ copied from source.
         * edit size is that and literal additions.
         */
-       *src_copied = copied_from_source;
        *literal_added = added_literal;
        return 0;
 }
index 2ed5984..0730b24 100644 (file)
 
 #include <stdlib.h>
 #include <string.h>
-#include <zlib.h>
 #include "delta.h"
 
 
-/* block size: min = 16, max = 64k, power of 2 */
-#define BLK_SIZE 16
-
-#define MIN(a, b) ((a) < (b) ? (a) : (b))
-
-#define GR_PRIME 0x9e370001
-#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
-
 struct index {
        const unsigned char *ptr;
-       unsigned int val;
        struct index *next;
 };
 
 static struct index ** delta_index(const unsigned char *buf,
                                   unsigned long bufsize,
+                                  unsigned long trg_bufsize,
                                   unsigned int *hash_shift)
 {
-       unsigned int hsize, hshift, entries, blksize, i;
+       unsigned long hsize;
+       unsigned int i, hshift, hlimit, *hash_count;
        const unsigned char *data;
        struct index *entry, **hash;
        void *mem;
 
        /* determine index hash size */
-       entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
-       hsize = entries / 4;
-       for (i = 4; (1 << i) < hsize && i < 16; i++);
+       hsize = bufsize / 4;
+       for (i = 8; (1 << i) < hsize && i < 24; i += 2);
        hsize = 1 << i;
-       hshift = 32 - i;
+       hshift = (i - 8) / 2;
        *hash_shift = hshift;
 
        /* allocate lookup index */
-       mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+       mem = malloc(hsize * sizeof(*hash) + bufsize * sizeof(*entry));
        if (!mem)
                return NULL;
        hash = mem;
        entry = mem + hsize * sizeof(*hash);
        memset(hash, 0, hsize * sizeof(*hash));
 
-       /* then populate it */
-       data = buf + entries * BLK_SIZE - BLK_SIZE;
-       blksize = bufsize - (data - buf);
-       while (data >= buf) {
-               unsigned int val = adler32(0, data, blksize);
-               i = HASH(val, hshift);
-               entry->ptr = data;
-               entry->val = val;
+       /* allocate an array to count hash entries */
+       hash_count = calloc(hsize, sizeof(*hash_count));
+       if (!hash_count) {
+               free(hash);
+               return NULL;
+       }
+
+       /* then populate the index */
+       data = buf + bufsize - 2;
+       while (data > buf) {
+               entry->ptr = --data;
+               i = data[0] ^ ((data[1] ^ (data[2] << hshift)) << hshift);
                entry->next = hash[i];
                hash[i] = entry++;
-               blksize = BLK_SIZE;
-               data -= BLK_SIZE;
+               hash_count[i]++;
        }
 
+       /*
+        * Determine a limit on the number of entries in the same hash
+        * bucket.  This guard us against patological data sets causing
+        * really bad hash distribution with most entries in the same hash
+        * bucket that would bring us to O(m*n) computing costs (m and n
+        * corresponding to reference and target buffer sizes).
+        *
+        * The more the target buffer is large, the more it is important to
+        * have small entry lists for each hash buckets.  With such a limit
+        * the cost is bounded to something more like O(m+n).
+        */
+       hlimit = (1 << 26) / trg_bufsize;
+       if (hlimit < 16)
+               hlimit = 16;
+
+       /*
+        * Now make sure none of the hash buckets has more entries than
+        * we're willing to test.  Otherwise we short-circuit the entry
+        * list uniformly to still preserve a good repartition across
+        * the reference buffer.
+        */
+       for (i = 0; i < hsize; i++) {
+               if (hash_count[i] < hlimit)
+                       continue;
+               entry = hash[i];
+               do {
+                       struct index *keep = entry;
+                       int skip = hash_count[i] / hlimit / 2;
+                       do {
+                               entry = entry->next;
+                       } while(--skip && entry);
+                       keep->next = entry;
+               } while(entry);
+       }
+       free(hash_count);
+
        return hash;
 }
 
@@ -100,7 +130,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        if (!from_size || !to_size)
                return NULL;
-       hash = delta_index(from_buf, from_size, &hash_shift);
+       hash = delta_index(from_buf, from_size, to_size, &hash_shift);
        if (!hash)
                return NULL;
 
@@ -141,29 +171,25 @@ void *diff_delta(void *from_buf, unsigned long from_size,
 
        while (data < top) {
                unsigned int moff = 0, msize = 0;
-               unsigned int blksize = MIN(top - data, BLK_SIZE);
-               unsigned int val = adler32(0, data, blksize);
-               i = HASH(val, hash_shift);
-               for (entry = hash[i]; entry; entry = entry->next) {
-                       const unsigned char *ref = entry->ptr;
-                       const unsigned char *src = data;
-                       unsigned int ref_size = ref_top - ref;
-                       if (entry->val != val)
-                               continue;
-                       if (ref_size > top - src)
-                               ref_size = top - src;
-                       while (ref_size && *src++ == *ref) {
-                               ref++;
-                               ref_size--;
-                       }
-                       ref_size = ref - entry->ptr;
-                       if (ref_size > msize) {
-                               /* this is our best match so far */
-                               moff = entry->ptr - ref_data;
-                               msize = ref_size;
-                               if (msize >= 0x10000) {
-                                       msize = 0x10000;
+               if (data + 3 <= top) {
+                       i = data[0] ^ ((data[1] ^ (data[2] << hash_shift)) << hash_shift);
+                       for (entry = hash[i]; entry; entry = entry->next) {
+                               const unsigned char *ref = entry->ptr;
+                               const unsigned char *src = data;
+                               unsigned int ref_size = ref_top - ref;
+                               if (ref_size > top - src)
+                                       ref_size = top - src;
+                               if (ref_size > 0x10000)
+                                       ref_size = 0x10000;
+                               if (ref_size <= msize)
                                        break;
+                               if (*ref != *src)
+                                       continue;
+                               while (ref_size-- && *++src == *++ref);
+                               if (msize < ref - entry->ptr) {
+                                       /* this is our best match so far */
+                                       msize = ref - entry->ptr;
+                                       moff = entry->ptr - ref_data;
                                }
                        }
                }
index 95b5eb4..0fc2b86 100644 (file)
@@ -4,8 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "delta.h"
-#include "count-delta.h"
 
 static int should_break(struct diff_filespec *src,
                        struct diff_filespec *dst,
@@ -47,7 +45,6 @@ static int should_break(struct diff_filespec *src,
         * The value we return is 1 if we want the pair to be broken,
         * or 0 if we do not.
         */
-       void *delta;
        unsigned long delta_size, base_size, src_copied, literal_added;
        int to_break = 0;
 
@@ -69,19 +66,11 @@ static int should_break(struct diff_filespec *src,
        if (base_size < MINIMUM_BREAK_SIZE)
                return 0; /* we do not break too small filepair */
 
-       delta = diff_delta(src->data, src->size,
-                          dst->data, dst->size,
-                          &delta_size, 0);
-       if (!delta)
-               return 0; /* error but caught downstream */
-
-       /* Estimate the edit size by interpreting delta. */
-       if (count_delta(delta, delta_size,
-                       &src_copied, &literal_added)) {
-               free(delta);
-               return 0; /* we cannot tell */
-       }
-       free(delta);
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  0,
+                                  &src_copied, &literal_added))
+               return 0;
 
        /* Compute merge-score, which is "how much is removed
         * from the source material".  The clean-up stage will
diff --git a/diffcore-delta.c b/diffcore-delta.c
new file mode 100644 (file)
index 0000000..d03787b
--- /dev/null
@@ -0,0 +1,128 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+
+struct linehash {
+       unsigned long bytes;
+       unsigned long hash;
+};
+
+static unsigned long hash_extended_line(const unsigned char **buf_p,
+                                       unsigned long left)
+{
+       /* An extended line is zero or more whitespace letters (including LF)
+        * followed by one non whitespace letter followed by zero or more
+        * non LF, and terminated with by a LF (or EOF).
+        */
+       const unsigned char *bol = *buf_p;
+       const unsigned char *buf = bol;
+       unsigned long hashval = 0;
+       while (left) {
+               unsigned c = *buf++;
+               if (!c)
+                       goto binary;
+               left--;
+               if (' ' < c) {
+                       hashval = c;
+                       break;
+               }
+       }
+       while (left) {
+               unsigned c = *buf++;
+               if (!c)
+                       goto binary;
+               left--;
+               if (c == '\n')
+                       break;
+               if (' ' < c)
+                       hashval = hashval * 11 + c;
+       }
+       *buf_p = buf;
+       return hashval;
+
+ binary:
+       *buf_p = NULL;
+       return 0;
+}
+
+static int linehash_compare(const void *a_, const void *b_)
+{
+       struct linehash *a = (struct linehash *) a_;
+       struct linehash *b = (struct linehash *) b_;
+       if (a->hash < b->hash) return -1;
+       if (a->hash > b->hash) return 1;
+       return 0;
+}
+
+static struct linehash *hash_lines(const unsigned char *buf,
+                                  unsigned long size)
+{
+       const unsigned char *eobuf = buf + size;
+       struct linehash *line = NULL;
+       int alloc = 0, used = 0;
+
+       while (buf < eobuf) {
+               const unsigned char *ptr = buf;
+               unsigned long hash = hash_extended_line(&buf, eobuf-ptr);
+               if (!buf) {
+                       free(line);
+                       return NULL;
+               }
+               if (alloc <= used) {
+                       alloc = alloc_nr(alloc);
+                       line = xrealloc(line, sizeof(*line) * alloc);
+               }
+               line[used].bytes = buf - ptr;
+               line[used].hash = hash;
+               used++;
+       }
+       qsort(line, used, sizeof(*line), linehash_compare);
+
+       /* Terminate the list */
+       if (alloc <= used)
+               line = xrealloc(line, sizeof(*line) * (used+1));
+       line[used].bytes = line[used].hash = 0;
+       return line;
+}
+
+int diffcore_count_changes(void *src, unsigned long src_size,
+                          void *dst, unsigned long dst_size,
+                          unsigned long delta_limit,
+                          unsigned long *src_copied,
+                          unsigned long *literal_added)
+{
+       struct linehash *src_lines, *dst_lines;
+       unsigned long sc, la;
+
+       src_lines = hash_lines(src, src_size);
+       if (!src_lines)
+               return -1;
+       dst_lines = hash_lines(dst, dst_size);
+       if (!dst_lines) {
+               free(src_lines);
+               return -1;
+       }
+       sc = la = 0;
+       while (src_lines->bytes && dst_lines->bytes) {
+               int cmp = linehash_compare(src_lines, dst_lines);
+               if (!cmp) {
+                       sc += src_lines->bytes;
+                       src_lines++;
+                       dst_lines++;
+                       continue;
+               }
+               if (cmp < 0) {
+                       src_lines++;
+                       continue;
+               }
+               la += dst_lines->bytes;
+               dst_lines++;
+       }
+       while (dst_lines->bytes) {
+               la += dst_lines->bytes;
+               dst_lines++;
+       }
+       *src_copied = sc;
+       *literal_added = la;
+       return 0;
+}
index ffd126a..55cf1c3 100644 (file)
@@ -4,8 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "delta.h"
-#include "count-delta.h"
 
 /* Table of rename/copy destinations */
 
@@ -135,7 +133,6 @@ static int estimate_similarity(struct diff_filespec *src,
         * match than anything else; the destination does not even
         * call into this function in that case.
         */
-       void *delta;
        unsigned long delta_size, base_size, src_copied, literal_added;
        unsigned long delta_limit;
        int score;
@@ -165,28 +162,13 @@ static int estimate_similarity(struct diff_filespec *src,
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
-       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
-       delta = diff_delta(src->data, src->size,
-                          dst->data, dst->size,
-                          &delta_size, delta_limit);
-       if (!delta)
-               /* If delta_limit is exceeded, we have too much differences */
-               return 0;
-
-       /* A delta that has a lot of literal additions would have
-        * big delta_size no matter what else it does.
-        */
-       if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE) {
-               free(delta);
-               return 0;
-       }
 
-       /* Estimate the edit size by interpreting delta. */
-       if (count_delta(delta, delta_size, &src_copied, &literal_added)) {
-               free(delta);
+       delta_limit = base_size * (MAX_SCORE-minimum_score) / MAX_SCORE;
+       if (diffcore_count_changes(src->data, src->size,
+                                  dst->data, dst->size,
+                                  delta_limit,
+                                  &src_copied, &literal_added))
                return 0;
-       }
-       free(delta);
 
        /* Extent of damage */
        if (src->size + literal_added < src_copied)
index 12cd816..7f9889d 100644 (file)
@@ -18,7 +18,7 @@
 #define MAX_SCORE 60000.0
 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
 #define DEFAULT_BREAK_SCORE  30000 /* minimum for break to happen (50%)*/
-#define DEFAULT_MERGE_SCORE  48000 /* maximum for break-merge to happen (80%)*/
+#define DEFAULT_MERGE_SCORE  45000 /* maximum for break-merge to happen (75%)*/
 
 #define MINIMUM_BREAK_SIZE     400 /* do not break a file smaller than this */
 
@@ -101,4 +101,10 @@ void diff_debug_queue(const char *, struct diff_queue_struct *);
 #define diff_debug_queue(a,b) do {} while(0)
 #endif
 
+extern int diffcore_count_changes(void *src, unsigned long src_size,
+                                 void *dst, unsigned long dst_size,
+                                 unsigned long delta_limit,
+                                 unsigned long *src_copied,
+                                 unsigned long *literal_added);
+
 #endif
diff --git a/epoch.c b/epoch.c
deleted file mode 100644 (file)
index 3a76748..0000000
--- a/epoch.c
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
- * Copyright (c) 2005, Jon Seymour
- *
- * For more information about epoch theory on which this module is based,
- * refer to http://blackcubes.dyndns.org/epoch/. That web page defines
- * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales
- * for some of the algorithms used here.
- *
- */
-#include <stdlib.h>
-
-/* Provides arbitrary precision integers required to accurately represent
- * fractional mass: */
-#include <openssl/bn.h>
-
-#include "cache.h"
-#include "commit.h"
-#include "epoch.h"
-
-struct fraction {
-       BIGNUM numerator;
-       BIGNUM denominator;
-};
-
-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
-
-static BN_CTX *context = NULL;
-static struct fraction *one = NULL;
-static struct fraction *zero = NULL;
-
-static BN_CTX *get_BN_CTX(void)
-{
-       if (!context) {
-               context = BN_CTX_new();
-       }
-       return context;
-}
-
-static struct fraction *new_zero(void)
-{
-       struct fraction *result = xmalloc(sizeof(*result));
-       BN_init(&result->numerator);
-       BN_init(&result->denominator);
-       BN_zero(&result->numerator);
-       BN_one(&result->denominator);
-       return result;
-}
-
-static void clear_fraction(struct fraction *fraction)
-{
-       BN_clear(&fraction->numerator);
-       BN_clear(&fraction->denominator);
-}
-
-static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor)
-{
-       BIGNUM bn_divisor;
-
-       BN_init(&bn_divisor);
-       BN_set_word(&bn_divisor, divisor);
-
-       BN_copy(&result->numerator, &fraction->numerator);
-       BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
-       BN_clear(&bn_divisor);
-       return result;
-}
-
-static struct fraction *init_fraction(struct fraction *fraction)
-{
-       BN_init(&fraction->numerator);
-       BN_init(&fraction->denominator);
-       BN_zero(&fraction->numerator);
-       BN_one(&fraction->denominator);
-       return fraction;
-}
-
-static struct fraction *get_one(void)
-{
-       if (!one) {
-               one = new_zero();
-               BN_one(&one->numerator);
-       }
-       return one;
-}
-
-static struct fraction *get_zero(void)
-{
-       if (!zero) {
-               zero = new_zero();
-       }
-       return zero;
-}
-
-static struct fraction *copy(struct fraction *to, struct fraction *from)
-{
-       BN_copy(&to->numerator, &from->numerator);
-       BN_copy(&to->denominator, &from->denominator);
-       return to;
-}
-
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
-{
-       BIGNUM a, b, gcd;
-
-       BN_init(&a);
-       BN_init(&b);
-       BN_init(&gcd);
-
-       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
-       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-       BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
-       BN_add(&result->numerator, &a, &b);
-
-       BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
-       BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
-       BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
-       BN_clear(&a);
-       BN_clear(&b);
-       BN_clear(&gcd);
-
-       return result;
-}
-
-static int compare(struct fraction *left, struct fraction *right)
-{
-       BIGNUM a, b;
-       int result;
-
-       BN_init(&a);
-       BN_init(&b);
-
-       BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
-       BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
-       result = BN_cmp(&a, &b);
-
-       BN_clear(&a);
-       BN_clear(&b);
-
-       return result;
-}
-
-struct mass_counter {
-       struct fraction seen;
-       struct fraction pending;
-};
-
-static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
-{
-       struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
-       memset(mass_counter, 0, sizeof(*mass_counter));
-
-       init_fraction(&mass_counter->seen);
-       init_fraction(&mass_counter->pending);
-
-       copy(&mass_counter->pending, pending);
-       copy(&mass_counter->seen, get_zero());
-
-       if (commit->object.util) {
-               die("multiple attempts to initialize mass counter for %s",
-                   sha1_to_hex(commit->object.sha1));
-       }
-
-       commit->object.util = mass_counter;
-
-       return mass_counter;
-}
-
-static void free_mass_counter(struct mass_counter *counter)
-{
-       clear_fraction(&counter->seen);
-       clear_fraction(&counter->pending);
-       free(counter);
-}
-
-/*
- * Finds the base commit of a list of commits.
- *
- * One property of the commit being searched for is that every commit reachable
- * from the base commit is reachable from the commits in the starting list only
- * via paths that include the base commit.
- *
- * This algorithm uses a conservation of mass approach to find the base commit.
- *
- * We start by injecting one unit of mass into the graph at each
- * of the commits in the starting list. Injecting mass into a commit
- * is achieved by adding to its pending mass counter and, if it is not already
- * enqueued, enqueuing the commit in a list of pending commits, in latest
- * commit date first order.
- *
- * The algorithm then proceeds to visit each commit in the pending queue.
- * Upon each visit, the pending mass is added to the mass already seen for that
- * commit and then divided into N equal portions, where N is the number of
- * parents of the commit being visited. The divided portions are then injected
- * into each of the parents.
- *
- * The algorithm continues until we discover a commit which has seen all the
- * mass originally injected or until we run out of things to do.
- *
- * If we find a commit that has seen all the original mass, we have found
- * the common base of all the commits in the starting list.
- *
- * The algorithm does _not_ depend on accurate timestamps for correct operation.
- * However, reasonably sane (e.g. non-random) timestamps are required in order
- * to prevent an exponential performance characteristic. The occasional
- * timestamp inaccuracy will not dramatically affect performance but may
- * result in more nodes being processed than strictly necessary.
- *
- * This procedure sets *boundary to the address of the base commit. It returns
- * non-zero if, and only if, there was a problem parsing one of the
- * commits discovered during the traversal.
- */
-static int find_base_for_list(struct commit_list *list, struct commit **boundary)
-{
-       int ret = 0;
-       struct commit_list *cleaner = NULL;
-       struct commit_list *pending = NULL;
-       struct fraction injected;
-       init_fraction(&injected);
-       *boundary = NULL;
-
-       for (; list; list = list->next) {
-               struct commit *item = list->item;
-
-               if (!item->object.util) {
-                       new_mass_counter(list->item, get_one());
-                       add(&injected, &injected, get_one());
-
-                       commit_list_insert(list->item, &cleaner);
-                       commit_list_insert(list->item, &pending);
-               }
-       }
-
-       while (!*boundary && pending && !ret) {
-               struct commit *latest = pop_commit(&pending);
-               struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
-               int num_parents;
-
-               if ((ret = parse_commit(latest)))
-                       continue;
-               add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
-               num_parents = count_parents(latest);
-               if (num_parents) {
-                       struct fraction distribution;
-                       struct commit_list *parents;
-
-                       divide(init_fraction(&distribution), &latest_node->pending, num_parents);
-
-                       for (parents = latest->parents; parents; parents = parents->next) {
-                               struct commit *parent = parents->item;
-                               struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
-                               if (!parent_node) {
-                                       parent_node = new_mass_counter(parent, &distribution);
-                                       insert_by_date(parent, &pending);
-                                       commit_list_insert(parent, &cleaner);
-                               } else {
-                                       if (!compare(&parent_node->pending, get_zero()))
-                                               insert_by_date(parent, &pending);
-                                       add(&parent_node->pending, &parent_node->pending, &distribution);
-                               }
-                       }
-
-                       clear_fraction(&distribution);
-               }
-
-               if (!compare(&latest_node->seen, &injected))
-                       *boundary = latest;
-               copy(&latest_node->pending, get_zero());
-       }
-
-       while (cleaner) {
-               struct commit *next = pop_commit(&cleaner);
-               free_mass_counter((struct mass_counter *) next->object.util);
-               next->object.util = NULL;
-       }
-
-       if (pending)
-               free_commit_list(pending);
-
-       clear_fraction(&injected);
-       return ret;
-}
-
-
-/*
- * Finds the base of an minimal, non-linear epoch, headed at head, by
- * applying the find_base_for_list to a list consisting of the parents
- */
-static int find_base(struct commit *head, struct commit **boundary)
-{
-       int ret = 0;
-       struct commit_list *pending = NULL;
-       struct commit_list *next;
-
-       for (next = head->parents; next; next = next->next) {
-               commit_list_insert(next->item, &pending);
-       }
-       ret = find_base_for_list(pending, boundary);
-       free_commit_list(pending);
-
-       return ret;
-}
-
-/*
- * This procedure traverses to the boundary of the first epoch in the epoch
- * sequence of the epoch headed at head_of_epoch. This is either the end of
- * the maximal linear epoch or the base of a minimal non-linear epoch.
- *
- * The queue of pending nodes is sorted in reverse date order and each node
- * is currently in the queue at most once.
- */
-static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
-{
-       int ret;
-       struct commit *item = head_of_epoch;
-
-       ret = parse_commit(item);
-       if (ret)
-               return ret;
-
-       if (HAS_EXACTLY_ONE_PARENT(item)) {
-               /*
-                * We are at the start of a maximimal linear epoch.
-                * Traverse to the end.
-                */
-               while (HAS_EXACTLY_ONE_PARENT(item) && !ret) {
-                       item = item->parents->item;
-                       ret = parse_commit(item);
-               }
-               *boundary = item;
-
-       } else {
-               /*
-                * Otherwise, we are at the start of a minimal, non-linear
-                * epoch - find the common base of all parents.
-                */
-               ret = find_base(item, boundary);
-       }
-
-       return ret;
-}
-
-/*
- * Returns non-zero if parent is known to be a parent of child.
- */
-static int is_parent_of(struct commit *parent, struct commit *child)
-{
-       struct commit_list *parents;
-       for (parents = child->parents; parents; parents = parents->next) {
-               if (!memcmp(parent->object.sha1, parents->item->object.sha1,
-                           sizeof(parents->item->object.sha1)))
-                       return 1;
-       }
-       return 0;
-}
-
-/*
- * Pushes an item onto the merge order stack. If the top of the stack is
- * marked as being a possible "break", we check to see whether it actually
- * is a break.
- */
-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
-{
-       struct commit_list *top = *stack;
-       if (top && (top->item->object.flags & DISCONTINUITY)) {
-               if (is_parent_of(top->item, item)) {
-                       top->item->object.flags &= ~DISCONTINUITY;
-               }
-       }
-       commit_list_insert(item, stack);
-}
-
-/*
- * Marks all interesting, visited commits reachable from this commit
- * as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
- *
- * This doesn't actually mark all ancestors between the start node and the
- * epoch boundary uninteresting, but does ensure that they will eventually
- * be marked uninteresting when the main sort_first_epoch() traversal
- * eventually reaches them.
- */
-static void mark_ancestors_uninteresting(struct commit *commit)
-{
-       unsigned int flags = commit->object.flags;
-       int visited = flags & VISITED;
-       int boundary = flags & BOUNDARY;
-       int uninteresting = flags & UNINTERESTING;
-       struct commit_list *next;
-
-       commit->object.flags |= UNINTERESTING;
-
-       /*
-        * We only need to recurse if
-        *      we are not on the boundary and
-        *      we have not already been marked uninteresting and
-        *      we have already been visited.
-        *
-        * The main sort_first_epoch traverse will mark unreachable
-        * all uninteresting, unvisited parents as they are visited
-        * so there is no need to duplicate that traversal here.
-        *
-        * Similarly, if we are already marked uninteresting
-        * then either all ancestors have already been marked
-        * uninteresting or will be once the sort_first_epoch
-        * traverse reaches them.
-        */
-
-       if (uninteresting || boundary || !visited)
-               return;
-
-       for (next = commit->parents; next; next = next->next)
-               mark_ancestors_uninteresting(next->item);
-}
-
-/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
- */
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
-{
-       struct commit_list *parents;
-
-       head->object.flags |= VISITED;
-
-       /*
-        * TODO: By sorting the parents in a different order, we can alter the
-        * merge order to show contemporaneous changes in parallel branches
-        * occurring after "local" changes. This is useful for a developer
-        * when a developer wants to see all changes that were incorporated
-        * into the same merge as her own changes occur after her own
-        * changes.
-        */
-
-       for (parents = head->parents; parents; parents = parents->next) {
-               struct commit *parent = parents->item;
-
-               if (head->object.flags & UNINTERESTING) {
-                       /*
-                        * Propagates the uninteresting bit to all parents.
-                        * if we have already visited this parent, then
-                        * the uninteresting bit will be propagated to each
-                        * reachable commit that is still not marked
-                        * uninteresting and won't otherwise be reached.
-                        */
-                       mark_ancestors_uninteresting(parent);
-               }
-
-               if (!(parent->object.flags & VISITED)) {
-                       if (parent->object.flags & BOUNDARY) {
-                               if (*stack) {
-                                       die("something else is on the stack - %s",
-                                           sha1_to_hex((*stack)->item->object.sha1));
-                               }
-                               push_onto_merge_order_stack(stack, parent);
-                               parent->object.flags |= VISITED;
-
-                       } else {
-                               sort_first_epoch(parent, stack);
-                               if (parents) {
-                                       /*
-                                        * This indicates a possible
-                                        * discontinuity it may not be be
-                                        * actual discontinuity if the head
-                                        * of parent N happens to be the tail
-                                        * of parent N+1.
-                                        *
-                                        * The next push onto the stack will
-                                        * resolve the question.
-                                        */
-                                       (*stack)->item->object.flags |= DISCONTINUITY;
-                               }
-                       }
-               }
-       }
-
-       push_onto_merge_order_stack(stack, head);
-}
-
-/*
- * Emit the contents of the stack.
- *
- * The stack is freed and replaced by NULL.
- *
- * Sets the return value to STOP if no further output should be generated.
- */
-static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last)
-{
-       unsigned int seen = 0;
-       int action = CONTINUE;
-
-       while (*stack && (action != STOP)) {
-               struct commit *next = pop_commit(stack);
-               seen |= next->object.flags;
-               if (*stack || include_last) {
-                       if (!*stack) 
-                               next->object.flags |= BOUNDARY;
-                       action = emitter(next);
-               }
-       }
-
-       if (*stack) {
-               free_commit_list(*stack);
-               *stack = NULL;
-       }
-
-       return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
-}
-
-/*
- * Sorts an arbitrary epoch into merge order by sorting each epoch
- * of its epoch sequence into order.
- *
- * Note: this algorithm currently leaves traces of its execution in the
- * object flags of nodes it discovers. This should probably be fixed.
- */
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
-{
-       struct commit *next = head_of_epoch;
-       int ret = 0;
-       int action = CONTINUE;
-
-       ret = parse_commit(head_of_epoch);
-
-       next->object.flags |= BOUNDARY;
-
-       while (next && next->parents && !ret && (action != STOP)) {
-               struct commit *base = NULL;
-
-               ret = find_next_epoch_boundary(next, &base);
-               if (ret)
-                       return ret;
-               next->object.flags |= BOUNDARY;
-               if (base)
-                       base->object.flags |= BOUNDARY;
-
-               if (HAS_EXACTLY_ONE_PARENT(next)) {
-                       while (HAS_EXACTLY_ONE_PARENT(next)
-                              && (action != STOP)
-                              && !ret) {
-                               if (next->object.flags & UNINTERESTING) {
-                                       action = STOP;
-                               } else {
-                                       action = emitter(next);
-                               }
-                               if (action != STOP) {
-                                       next = next->parents->item;
-                                       ret = parse_commit(next);
-                               }
-                       }
-
-               } else {
-                       struct commit_list *stack = NULL;
-                       sort_first_epoch(next, &stack);
-                       action = emit_stack(&stack, emitter, (base == NULL));
-                       next = base;
-               }
-       }
-
-       if (next && (action != STOP) && !ret) {
-               emitter(next);
-       }
-
-       return ret;
-}
-
-/*
- * Sorts the nodes reachable from a starting list in merge order, we
- * first find the base for the starting list and then sort all nodes
- * in this subgraph using the sort_first_epoch algorithm. Once we have
- * reached the base we can continue sorting using sort_in_merge_order.
- */
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
-{
-       struct commit_list *stack = NULL;
-       struct commit *base;
-       int ret = 0;
-       int action = CONTINUE;
-       struct commit_list *reversed = NULL;
-
-       for (; list; list = list->next)
-               commit_list_insert(list->item, &reversed);
-
-       if (!reversed)
-               return ret;
-       else if (!reversed->next) {
-               /*
-                * If there is only one element in the list, we can sort it
-                * using sort_in_merge_order.
-                */
-               base = reversed->item;
-       } else {
-               /*
-                * Otherwise, we search for the base of the list.
-                */
-               ret = find_base_for_list(reversed, &base);
-               if (ret)
-                       return ret;
-               if (base)
-                       base->object.flags |= BOUNDARY;
-
-               while (reversed) {
-                       struct commit * next = pop_commit(&reversed);
-
-                       if (!(next->object.flags & VISITED) && next!=base) {
-                               sort_first_epoch(next, &stack);
-                               if (reversed) {
-                                       /*
-                                        * If we have more commits 
-                                        * to push, then the first
-                                        * push for the next parent may 
-                                        * (or may * not) represent a 
-                                        * discontinuity with respect
-                                        * to the parent currently on 
-                                        * the top of the stack.
-                                        *
-                                        * Mark it for checking here, 
-                                        * and check it with the next 
-                                        * push. See sort_first_epoch()
-                                        * for more details.
-                                        */
-                                       stack->item->object.flags |= DISCONTINUITY;
-                               }
-                       }
-               }
-
-               action = emit_stack(&stack, emitter, (base==NULL));
-       }
-
-       if (base && (action != STOP)) {
-               ret = sort_in_merge_order(base, emitter);
-       }
-
-       return ret;
-}
diff --git a/epoch.h b/epoch.h
deleted file mode 100644 (file)
index 7493d5a..0000000
--- a/epoch.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef EPOCH_H
-#define EPOCH_H
-
-
-// return codes for emitter_func
-#define STOP     0
-#define CONTINUE 1
-#define DO       2
-typedef int (*emitter_func) (struct commit *); 
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
-/* Low bits are used by rev-list */
-#define UNINTERESTING   (1u<<10)
-#define BOUNDARY        (1u<<11)
-#define VISITED         (1u<<12)
-#define DISCONTINUITY   (1u<<13)
-#define LAST_EPOCH_FLAG (1u<<14)
-
-
-#endif /* EPOCH_H */
index 6792624..740bc1f 100755 (executable)
@@ -928,7 +928,7 @@ sub find_parents {
 
        # now walk up to the mergepoint collecting what patches we have
        my $branchtip = git_rev_parse($ps->{branch});
-       my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
+       my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`;
        my %have; # collected merges this branch has
        foreach my $merge (@{$ps->{merges}}) {
            $have{$merge} = 1;
@@ -951,7 +951,7 @@ sub find_parents {
        # see what the remote branch has - these are the merges we 
        # will want to have in a consecutive series from the mergebase
        my $otherbranchtip = git_rev_parse($branch);
-       my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
+       my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
        my @need;
        foreach my $needps (@needraw) {         # get the psets
            $needps = commitid2pset($needps);
index 86837ed..639aa41 100755 (executable)
@@ -13,6 +13,7 @@
 use strict;
 use warnings;
 use Getopt::Std;
+use File::Copy;
 use File::Spec;
 use File::Temp qw(tempfile);
 use File::Path qw(mkpath);
@@ -68,10 +69,16 @@ if ($opt_M) {
        push (@mergerx, qr/$opt_M/);
 }
 
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
 our %users = ();
-if ($opt_A) {
-       die "Cannot open $opt_A\n" unless -f $opt_A;
-       open(my $authors,$opt_A);
+our $users_file = undef;
+sub read_users($) {
+       $users_file = File::Spec->rel2abs(@_);
+       die "Cannot open $users_file\n" unless -f $users_file;
+       open(my $authors,$users_file);
        while(<$authors>) {
                chomp;
                next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
@@ -302,6 +309,14 @@ EOM
 -d $git_dir
        or die "Could not create git subdir ($git_dir).\n";
 
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+       read_users($opt_A);
+       copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+       read_users($default_authors) if -f $default_authors;
+}
+
 open BRANCHES,">>", "$git_dir/svn2git";
 
 sub node_kind($$$) {
@@ -498,8 +513,8 @@ sub commit {
 
        if (not defined $author) {
                $author_name = $author_email = "unknown";
-       } elsif ($opt_A) {
-               die "User $author is not listed in $opt_A\n"
+       } elsif (defined $users_file) {
+               die "User $author is not listed in $users_file\n"
                    unless exists $users{$author};
                ($author_name,$author_email) = @{$users{$author}};
        } elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
index 726b1e7..36f171b 100755 (executable)
@@ -4,9 +4,21 @@ USAGE='<tag>'
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
+verbose=
+while case $# in 0) break;; esac
+do
+       case "$1" in
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t ;;
+       *)
+               break ;;
+       esac
+       shift
+done
+
 if [ "$#" != "1" ]
 then
-  usage
+       usage
 fi
 
 type="$(git-cat-file -t "$1" 2>/dev/null)" ||
@@ -15,6 +27,13 @@ type="$(git-cat-file -t "$1" 2>/dev/null)" ||
 test "$type" = tag ||
        die "$1: cannot verify a non-tag object of type $type."
 
+case "$verbose" in
+t)
+       git-cat-file -p "$1" |
+       sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
+       ;;
+esac
+
 git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
 cat "$GIT_DIR/.tmp-vtag" |
 sed '/-----BEGIN PGP/Q' |
diff --git a/git.c b/git.c
index 993cd0d..bf68dac 100644 (file)
--- a/git.c
+++ b/git.c
 #include "git-compat-util.h"
 #include "exec_cmd.h"
 
+#include "cache.h"
+#include "commit.h"
+#include "revision.h"
+
 #ifndef PATH_MAX
 # define PATH_MAX 4096
 #endif
@@ -245,6 +249,93 @@ static int cmd_help(int argc, char **argv, char **envp)
        return 0;
 }
 
+#define LOGSIZE (65536)
+
+static int cmd_log(int argc, 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 show_parents = 0;
+       const char *commit_prefix = "commit ";
+
+       argc = setup_revisions(argc, argv, &rev, "HEAD");
+       while (1 < argc) {
+               char *arg = argv[1];
+               /* accept -<digit>, like traditilnal "head" */
+               if ((*arg == '-') && isdigit(arg[1])) {
+                       rev.max_count = atoi(arg + 1);
+               }
+               else if (!strcmp(arg, "-n")) {
+                       if (argc < 2)
+                               die("-n requires an argument");
+                       rev.max_count = atoi(argv[2]);
+                       argc--; argv++;
+               }
+               else if (!strncmp(arg,"-n",2)) {
+                       rev.max_count = atoi(arg + 2);
+               }
+               else if (!strncmp(arg, "--pretty", 8)) {
+                       commit_format = get_commit_format(arg + 8);
+                       if (commit_format == CMIT_FMT_ONELINE)
+                               commit_prefix = "";
+               }
+               else if (!strcmp(arg, "--parents")) {
+                       show_parents = 1;
+               }
+               else if (!strcmp(arg, "--no-abbrev")) {
+                       abbrev = 0;
+               }
+               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
+                       die("unrecognized argument: %s", arg);
+               argc--; argv++;
+       }
+
+       prepare_revision_walk(&rev);
+       setup_pager();
+       while ((commit = get_revision(&rev)) != NULL) {
+               printf("%s%s", commit_prefix,
+                      sha1_to_hex(commit->object.sha1));
+               if (show_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);
+       }
+       free(buf);
+       return 0;
+}
+
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
 static void handle_internal_command(int argc, char **argv, char **envp)
@@ -256,6 +347,7 @@ static void handle_internal_command(int argc, char **argv, char **envp)
        } commands[] = {
                { "version", cmd_version },
                { "help", cmd_help },
+               { "log", cmd_log },
        };
        int i;
 
diff --git a/pager.c b/pager.c
new file mode 100644 (file)
index 0000000..1364e15
--- /dev/null
+++ b/pager.c
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+/*
+ * This is split up from the rest of git so that we might do
+ * something different on Windows, for example.
+ */
+
+static void run_pager(void)
+{
+       const char *prog = getenv("PAGER");
+       if (!prog)
+               prog = "less";
+       setenv("LESS", "-S", 0);
+       execlp(prog, prog, NULL);
+}
+
+void setup_pager(void)
+{
+       pid_t pid;
+       int fd[2];
+
+       if (!isatty(1))
+               return;
+       if (pipe(fd) < 0)
+               return;
+       pid = fork();
+       if (pid < 0) {
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* return in the child */
+       if (!pid) {
+               dup2(fd[1], 1);
+               close(fd[0]);
+               close(fd[1]);
+               return;
+       }
+
+       /* The original process turns into the PAGER */
+       dup2(fd[0], 0);
+       close(fd[0]);
+       close(fd[1]);
+
+       run_pager();
+       exit(255);
+}
diff --git a/refs.c b/refs.c
index 826ae7a..982ebf8 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
                                        break;
                                continue;
                        }
-                       if (read_ref(git_path("%s", path), sha1) < 0)
+                       if (read_ref(git_path("%s", path), sha1) < 0) {
+                               fprintf(stderr, "%s points nowhere!", path);
                                continue;
-                       if (!has_sha1_file(sha1))
+                       }
+                       if (!has_sha1_file(sha1)) {
+                               fprintf(stderr, "%s does not point to a valid "
+                                               "commit object!", path);
                                continue;
+                       }
                        retval = fn(path, sha1);
                        if (retval)
                                break;
index 67d2a48..8e4d83e 100644 (file)
@@ -4,15 +4,12 @@
 #include "commit.h"
 #include "tree.h"
 #include "blob.h"
-#include "epoch.h"
 #include "diff.h"
+#include "revision.h"
 
-#define SEEN           (1u << 0)
-#define INTERESTING    (1u << 1)
-#define COUNTED                (1u << 2)
-#define SHOWN          (1u << 3)
-#define TREECHANGE     (1u << 4)
-#define TMP_MARK       (1u << 5) /* for isolated cases; clean after use */
+/* bits #0-4 in revision.h */
+
+#define COUNTED                (1u<<5)
 
 static const char rev_list_usage[] =
 "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -25,7 +22,6 @@ static const char rev_list_usage[] =
 "    --remove-empty\n"
 "    --all\n"
 "  ordering output:\n"
-"    --merge-order [ --show-breaks ]\n"
 "    --topo-order\n"
 "    --date-order\n"
 "  formatting output:\n"
@@ -38,72 +34,18 @@ static const char rev_list_usage[] =
 "    --bisect"
 ;
 
-static int dense = 1;
-static int unpacked = 0;
+struct rev_info revs;
+
 static int bisect_list = 0;
-static int tag_objects = 0;
-static int tree_objects = 0;
-static int blob_objects = 0;
-static int edge_hint = 0;
 static int verbose_header = 0;
 static int abbrev = DEFAULT_ABBREV;
 static int show_parents = 0;
 static int hdr_termination = 0;
 static const char *commit_prefix = "";
-static unsigned long max_age = -1;
-static unsigned long min_age = -1;
-static int max_count = -1;
 static enum cmit_fmt commit_format = CMIT_FMT_RAW;
-static int merge_order = 0;
-static int show_breaks = 0;
-static int stop_traversal = 0;
-static int topo_order = 0;
-static int lifo = 1;
-static int no_merges = 0;
-static const char **paths = NULL;
-static int remove_empty_trees = 0;
-
-struct name_path {
-       struct name_path *up;
-       int elem_len;
-       const char *elem;
-};
-
-static char *path_name(struct name_path *path, const char *name)
-{
-       struct name_path *p;
-       char *n, *m;
-       int nlen = strlen(name);
-       int len = nlen + 1;
-
-       for (p = path; p; p = p->up) {
-               if (p->elem_len)
-                       len += p->elem_len + 1;
-       }
-       n = xmalloc(len);
-       m = n + len - (nlen + 1);
-       strcpy(m, name);
-       for (p = path; p; p = p->up) {
-               if (p->elem_len) {
-                       m -= p->elem_len + 1;
-                       memcpy(m, p->elem, p->elem_len);
-                       m[p->elem_len] = '/';
-               }
-       }
-       return n;
-}
 
 static void show_commit(struct commit *commit)
 {
-       commit->object.flags |= SHOWN;
-       if (show_breaks) {
-               commit_prefix = "| ";
-               if (commit->object.flags & DISCONTINUITY) {
-                       commit_prefix = "^ ";     
-               } else if (commit->object.flags & BOUNDARY) {
-                       commit_prefix = "= ";
-               } 
-        }                      
        printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
        if (show_parents) {
                struct commit_list *parents = commit->parents;
@@ -137,86 +79,6 @@ static void show_commit(struct commit *commit)
        fflush(stdout);
 }
 
-static int rewrite_one(struct commit **pp)
-{
-       for (;;) {
-               struct commit *p = *pp;
-               if (p->object.flags & (TREECHANGE | UNINTERESTING))
-                       return 0;
-               if (!p->parents)
-                       return -1;
-               *pp = p->parents->item;
-       }
-}
-
-static void rewrite_parents(struct commit *commit)
-{
-       struct commit_list **pp = &commit->parents;
-       while (*pp) {
-               struct commit_list *parent = *pp;
-               if (rewrite_one(&parent->item) < 0) {
-                       *pp = parent->next;
-                       continue;
-               }
-               pp = &parent->next;
-       }
-}
-
-static int filter_commit(struct commit * commit)
-{
-       if (stop_traversal && (commit->object.flags & BOUNDARY))
-               return STOP;
-       if (commit->object.flags & (UNINTERESTING|SHOWN))
-               return CONTINUE;
-       if (min_age != -1 && (commit->date > min_age))
-               return CONTINUE;
-       if (max_age != -1 && (commit->date < max_age)) {
-               stop_traversal=1;
-               return CONTINUE;
-       }
-       if (no_merges && (commit->parents && commit->parents->next))
-               return CONTINUE;
-       if (paths && dense) {
-               if (!(commit->object.flags & TREECHANGE))
-                       return CONTINUE;
-               rewrite_parents(commit);
-       }
-       return DO;
-}
-
-static int process_commit(struct commit * commit)
-{
-       int action=filter_commit(commit);
-
-       if (action == STOP) {
-               return STOP;
-       }
-
-       if (action == CONTINUE) {
-               return CONTINUE;
-       }
-
-       if (max_count != -1 && !max_count--)
-               return STOP;
-
-       show_commit(commit);
-
-       return CONTINUE;
-}
-
-static struct object_list **add_object(struct object *obj,
-                                      struct object_list **p,
-                                      struct name_path *path,
-                                      const char *name)
-{
-       struct object_list *entry = xmalloc(sizeof(*entry));
-       entry->item = obj;
-       entry->next = *p;
-       entry->name = path_name(path, name);
-       *p = entry;
-       return &entry->next;
-}
-
 static struct object_list **process_blob(struct blob *blob,
                                         struct object_list **p,
                                         struct name_path *path,
@@ -224,7 +86,7 @@ static struct object_list **process_blob(struct blob *blob,
 {
        struct object *obj = &blob->object;
 
-       if (!blob_objects)
+       if (!revs.blob_objects)
                return p;
        if (obj->flags & (UNINTERESTING | SEEN))
                return p;
@@ -241,7 +103,7 @@ static struct object_list **process_tree(struct tree *tree,
        struct tree_entry_list *entry;
        struct name_path me;
 
-       if (!tree_objects)
+       if (!revs.tree_objects)
                return p;
        if (obj->flags & (UNINTERESTING | SEEN))
                return p;
@@ -266,19 +128,16 @@ static struct object_list **process_tree(struct tree *tree,
        return p;
 }
 
-static struct object_list *pending_objects = NULL;
-
-static void show_commit_list(struct commit_list *list)
+static void show_commit_list(struct rev_info *revs)
 {
+       struct commit *commit;
        struct object_list *objects = NULL, **p = &objects, *pending;
-       while (list) {
-               struct commit *commit = pop_most_recent_commit(&list, SEEN);
 
+       while ((commit = get_revision(revs)) != NULL) {
                p = process_tree(commit->tree, p, NULL, "");
-               if (process_commit(commit) == STOP)
-                       break;
+               show_commit(commit);
        }
-       for (pending = pending_objects; pending; pending = pending->next) {
+       for (pending = revs->pending_objects; pending; pending = pending->next) {
                struct object *obj = pending->item;
                const char *name = pending->name;
                if (obj->flags & (UNINTERESTING | SEEN))
@@ -314,88 +173,6 @@ static void show_commit_list(struct commit_list *list)
        }
 }
 
-static void mark_blob_uninteresting(struct blob *blob)
-{
-       if (!blob_objects)
-               return;
-       if (blob->object.flags & UNINTERESTING)
-               return;
-       blob->object.flags |= UNINTERESTING;
-}
-
-static void mark_tree_uninteresting(struct tree *tree)
-{
-       struct object *obj = &tree->object;
-       struct tree_entry_list *entry;
-
-       if (!tree_objects)
-               return;
-       if (obj->flags & UNINTERESTING)
-               return;
-       obj->flags |= UNINTERESTING;
-       if (!has_sha1_file(obj->sha1))
-               return;
-       if (parse_tree(tree) < 0)
-               die("bad tree %s", sha1_to_hex(obj->sha1));
-       entry = tree->entries;
-       tree->entries = NULL;
-       while (entry) {
-               struct tree_entry_list *next = entry->next;
-               if (entry->directory)
-                       mark_tree_uninteresting(entry->item.tree);
-               else
-                       mark_blob_uninteresting(entry->item.blob);
-               free(entry);
-               entry = next;
-       }
-}
-
-static void mark_parents_uninteresting(struct commit *commit)
-{
-       struct commit_list *parents = commit->parents;
-
-       while (parents) {
-               struct commit *commit = parents->item;
-               commit->object.flags |= UNINTERESTING;
-
-               /*
-                * Normally we haven't parsed the parent
-                * yet, so we won't have a parent of a parent
-                * here. However, it may turn out that we've
-                * reached this commit some other way (where it
-                * wasn't uninteresting), in which case we need
-                * to mark its parents recursively too..
-                */
-               if (commit->parents)
-                       mark_parents_uninteresting(commit);
-
-               /*
-                * A missing commit is ok iff its parent is marked 
-                * uninteresting.
-                *
-                * We just mark such a thing parsed, so that when
-                * it is popped next time around, we won't be trying
-                * to parse it and get an error.
-                */
-               if (!has_sha1_file(commit->object.sha1))
-                       commit->object.parsed = 1;
-               parents = parents->next;
-       }
-}
-
-static int everybody_uninteresting(struct commit_list *orig)
-{
-       struct commit_list *list = orig;
-       while (list) {
-               struct commit *commit = list->item;
-               list = list->next;
-               if (commit->object.flags & UNINTERESTING)
-                       continue;
-               return 0;
-       }
-       return 1;
-}
-
 /*
  * This is a truly stupid algorithm, but it's only
  * used for bisection, and we just don't care enough.
@@ -413,7 +190,7 @@ static int count_distance(struct commit_list *entry)
 
                if (commit->object.flags & (UNINTERESTING | COUNTED))
                        break;
-               if (!paths || (commit->object.flags & TREECHANGE))
+               if (!revs.paths || (commit->object.flags & TREECHANGE))
                        nr++;
                commit->object.flags |= COUNTED;
                p = commit->parents;
@@ -447,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
        nr = 0;
        p = list;
        while (p) {
-               if (!paths || (p->item->object.flags & TREECHANGE))
+               if (!revs.paths || (p->item->object.flags & TREECHANGE))
                        nr++;
                p = p->next;
        }
@@ -457,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
        for (p = list; p; p = p->next) {
                int distance;
 
-               if (paths && !(p->item->object.flags & TREECHANGE))
+               if (revs.paths && !(p->item->object.flags & TREECHANGE))
                        continue;
 
                distance = count_distance(p);
@@ -483,7 +260,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit)
                if (!(parent->object.flags & UNINTERESTING))
                        continue;
                mark_tree_uninteresting(parent->tree);
-               if (edge_hint && !(parent->object.flags & SHOWN)) {
+               if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
                        parent->object.flags |= SHOWN;
                        printf("-%s\n", sha1_to_hex(parent->object.sha1));
                }
@@ -503,356 +280,29 @@ static void mark_edges_uninteresting(struct commit_list *list)
        }
 }
 
-#define TREE_SAME      0
-#define TREE_NEW       1
-#define TREE_DIFFERENT 2
-static int tree_difference = TREE_SAME;
-
-static void file_add_remove(struct diff_options *options,
-                   int addremove, unsigned mode,
-                   const unsigned char *sha1,
-                   const char *base, const char *path)
-{
-       int diff = TREE_DIFFERENT;
-
-       /*
-        * Is it an add of a new file? It means that
-        * the old tree didn't have it at all, so we
-        * will turn "TREE_SAME" -> "TREE_NEW", but
-        * leave any "TREE_DIFFERENT" alone (and if
-        * it already was "TREE_NEW", we'll keep it
-        * "TREE_NEW" of course).
-        */
-       if (addremove == '+') {
-               diff = tree_difference;
-               if (diff != TREE_SAME)
-                       return;
-               diff = TREE_NEW;
-       }
-       tree_difference = diff;
-}
-
-static void file_change(struct diff_options *options,
-                unsigned old_mode, unsigned new_mode,
-                const unsigned char *old_sha1,
-                const unsigned char *new_sha1,
-                const char *base, const char *path)
-{
-       tree_difference = TREE_DIFFERENT;
-}
-
-static struct diff_options diff_opt = {
-       .recursive = 1,
-       .add_remove = file_add_remove,
-       .change = file_change,
-};
-
-static int compare_tree(struct tree *t1, struct tree *t2)
-{
-       if (!t1)
-               return TREE_NEW;
-       if (!t2)
-               return TREE_DIFFERENT;
-       tree_difference = TREE_SAME;
-       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
-               return TREE_DIFFERENT;
-       return tree_difference;
-}
-
-static int same_tree_as_empty(struct tree *t1)
-{
-       int retval;
-       void *tree;
-       struct tree_desc empty, real;
-
-       if (!t1)
-               return 0;
-
-       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
-       if (!tree)
-               return 0;
-       real.buf = tree;
-
-       empty.buf = "";
-       empty.size = 0;
-
-       tree_difference = 0;
-       retval = diff_tree(&empty, &real, "", &diff_opt);
-       free(tree);
-
-       return retval >= 0 && !tree_difference;
-}
-
-static void try_to_simplify_commit(struct commit *commit)
-{
-       struct commit_list **pp, *parent;
-
-       if (!commit->tree)
-               return;
-
-       if (!commit->parents) {
-               if (!same_tree_as_empty(commit->tree))
-                       commit->object.flags |= TREECHANGE;
-               return;
-       }
-
-       pp = &commit->parents;
-       while ((parent = *pp) != NULL) {
-               struct commit *p = parent->item;
-
-               if (p->object.flags & UNINTERESTING) {
-                       pp = &parent->next;
-                       continue;
-               }
-
-               parse_commit(p);
-               switch (compare_tree(p->tree, commit->tree)) {
-               case TREE_SAME:
-                       parent->next = NULL;
-                       commit->parents = parent;
-                       return;
-
-               case TREE_NEW:
-                       if (remove_empty_trees && same_tree_as_empty(p->tree)) {
-                               *pp = parent->next;
-                               continue;
-                       }
-               /* fallthrough */
-               case TREE_DIFFERENT:
-                       pp = &parent->next;
-                       continue;
-               }
-               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
-       }
-       commit->object.flags |= TREECHANGE;
-}
-
-static void add_parents_to_list(struct commit *commit, struct commit_list **list)
-{
-       struct commit_list *parent = commit->parents;
-
-       /*
-        * If the commit is uninteresting, don't try to
-        * prune parents - we want the maximal uninteresting
-        * set.
-        *
-        * Normally we haven't parsed the parent
-        * yet, so we won't have a parent of a parent
-        * here. However, it may turn out that we've
-        * reached this commit some other way (where it
-        * wasn't uninteresting), in which case we need
-        * to mark its parents recursively too..
-        */
-       if (commit->object.flags & UNINTERESTING) {
-               while (parent) {
-                       struct commit *p = parent->item;
-                       parent = parent->next;
-                       parse_commit(p);
-                       p->object.flags |= UNINTERESTING;
-                       if (p->parents)
-                               mark_parents_uninteresting(p);
-                       if (p->object.flags & SEEN)
-                               continue;
-                       p->object.flags |= SEEN;
-                       insert_by_date(p, list);
-               }
-               return;
-       }
-
-       /*
-        * Ok, the commit wasn't uninteresting. Try to
-        * simplify the commit history and find the parent
-        * that has no differences in the path set if one exists.
-        */
-       if (paths)
-               try_to_simplify_commit(commit);
-
-       parent = commit->parents;
-       while (parent) {
-               struct commit *p = parent->item;
-
-               parent = parent->next;
-
-               parse_commit(p);
-               if (p->object.flags & SEEN)
-                       continue;
-               p->object.flags |= SEEN;
-               insert_by_date(p, list);
-       }
-}
-
-static struct commit_list *limit_list(struct commit_list *list)
-{
-       struct commit_list *newlist = NULL;
-       struct commit_list **p = &newlist;
-       while (list) {
-               struct commit_list *entry = list;
-               struct commit *commit = list->item;
-               struct object *obj = &commit->object;
-
-               list = list->next;
-               free(entry);
-
-               if (max_age != -1 && (commit->date < max_age))
-                       obj->flags |= UNINTERESTING;
-               if (unpacked && has_sha1_pack(obj->sha1))
-                       obj->flags |= UNINTERESTING;
-               add_parents_to_list(commit, &list);
-               if (obj->flags & UNINTERESTING) {
-                       mark_parents_uninteresting(commit);
-                       if (everybody_uninteresting(list))
-                               break;
-                       continue;
-               }
-               if (min_age != -1 && (commit->date > min_age))
-                       continue;
-               p = &commit_list_insert(commit, p)->next;
-       }
-       if (tree_objects)
-               mark_edges_uninteresting(newlist);
-       if (bisect_list)
-               newlist = find_bisection(newlist);
-       return newlist;
-}
-
-static void add_pending_object(struct object *obj, const char *name)
-{
-       add_object(obj, &pending_objects, NULL, name);
-}
-
-static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
-{
-       struct object *object;
-
-       object = parse_object(sha1);
-       if (!object)
-               die("bad object %s", name);
-
-       /*
-        * Tag object? Look what it points to..
-        */
-       while (object->type == tag_type) {
-               struct tag *tag = (struct tag *) object;
-               object->flags |= flags;
-               if (tag_objects && !(object->flags & UNINTERESTING))
-                       add_pending_object(object, tag->tag);
-               object = parse_object(tag->tagged->sha1);
-               if (!object)
-                       die("bad object %s", sha1_to_hex(tag->tagged->sha1));
-       }
-
-       /*
-        * Commit object? Just return it, we'll do all the complex
-        * reachability crud.
-        */
-       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)
-                       mark_parents_uninteresting(commit);
-               return commit;
-       }
-
-       /*
-        * Tree object? Either mark it uniniteresting, or add it
-        * to the list of objects to look at later..
-        */
-       if (object->type == tree_type) {
-               struct tree *tree = (struct tree *)object;
-               if (!tree_objects)
-                       return NULL;
-               if (flags & UNINTERESTING) {
-                       mark_tree_uninteresting(tree);
-                       return NULL;
-               }
-               add_pending_object(object, "");
-               return NULL;
-       }
-
-       /*
-        * Blob object? You know the drill by now..
-        */
-       if (object->type == blob_type) {
-               struct blob *blob = (struct blob *)object;
-               if (!blob_objects)
-                       return NULL;
-               if (flags & UNINTERESTING) {
-                       mark_blob_uninteresting(blob);
-                       return NULL;
-               }
-               add_pending_object(object, "");
-               return NULL;
-       }
-       die("%s is unknown object", name);
-}
-
-static void handle_one_commit(struct commit *com, struct commit_list **lst)
-{
-       if (!com || com->object.flags & SEEN)
-               return;
-       com->object.flags |= SEEN;
-       commit_list_insert(com, lst);
-}
-
-/* for_each_ref() callback does not allow user data -- Yuck. */
-static struct commit_list **global_lst;
-
-static int include_one_commit(const char *path, const unsigned char *sha1)
-{
-       struct commit *com = get_commit_reference(path, sha1, 0);
-       handle_one_commit(com, global_lst);
-       return 0;
-}
-
-static void handle_all(struct commit_list **lst)
-{
-       global_lst = lst;
-       for_each_ref(include_one_commit);
-       global_lst = NULL;
-}
-
 int main(int argc, const char **argv)
 {
-       const char *prefix = setup_git_directory();
-       struct commit_list *list = NULL;
-       int i, limited = 0;
+       struct commit_list *list;
+       int i;
+
+       argc = setup_revisions(argc, argv, &revs, NULL);
 
        for (i = 1 ; i < argc; i++) {
-               int flags;
                const char *arg = argv[i];
-               char *dotdot;
-               struct commit *commit;
-               unsigned char sha1[20];
 
                /* accept -<digit>, like traditilnal "head" */
                if ((*arg == '-') && isdigit(arg[1])) {
-                       max_count = atoi(arg + 1);
+                       revs.max_count = atoi(arg + 1);
                        continue;
                }
                if (!strcmp(arg, "-n")) {
                        if (++i >= argc)
                                die("-n requires an argument");
-                       max_count = atoi(argv[i]);
+                       revs.max_count = atoi(argv[i]);
                        continue;
                }
                if (!strncmp(arg,"-n",2)) {
-                       max_count = atoi(arg + 2);
-                       continue;
-               }
-               if (!strncmp(arg, "--max-count=", 12)) {
-                       max_count = atoi(arg + 12);
-                       continue;
-               }
-               if (!strncmp(arg, "--max-age=", 10)) {
-                       max_age = atoi(arg + 10);
-                       limited = 1;
-                       continue;
-               }
-               if (!strncmp(arg, "--min-age=", 10)) {
-                       min_age = atoi(arg + 10);
-                       limited = 1;
+                       revs.max_count = atoi(arg + 2);
                        continue;
                }
                if (!strcmp(arg, "--header")) {
@@ -881,10 +331,6 @@ int main(int argc, const char **argv)
                                commit_prefix = "commit ";
                        continue;
                }
-               if (!strncmp(arg, "--no-merges", 11)) {
-                       no_merges = 1;
-                       continue;
-               }
                if (!strcmp(arg, "--parents")) {
                        show_parents = 1;
                        continue;
@@ -893,140 +339,27 @@ int main(int argc, const char **argv)
                        bisect_list = 1;
                        continue;
                }
-               if (!strcmp(arg, "--all")) {
-                       handle_all(&list);
-                       continue;
-               }
-               if (!strcmp(arg, "--objects")) {
-                       tag_objects = 1;
-                       tree_objects = 1;
-                       blob_objects = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--objects-edge")) {
-                       tag_objects = 1;
-                       tree_objects = 1;
-                       blob_objects = 1;
-                       edge_hint = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--unpacked")) {
-                       unpacked = 1;
-                       limited = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--merge-order")) {
-                       merge_order = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--show-breaks")) {
-                       show_breaks = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--topo-order")) {
-                       topo_order = 1;
-                       lifo = 1;
-                       limited = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--date-order")) {
-                       topo_order = 1;
-                       lifo = 0;
-                       limited = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--dense")) {
-                       dense = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--sparse")) {
-                       dense = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--remove-empty")) {
-                       remove_empty_trees = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-
-               if (show_breaks && !merge_order)
-                       usage(rev_list_usage);
+               usage(rev_list_usage);
 
-               flags = 0;
-               dotdot = strstr(arg, "..");
-               if (dotdot) {
-                       unsigned char from_sha1[20];
-                       char *next = dotdot + 2;
-                       *dotdot = 0;
-                       if (!*next)
-                               next = "HEAD";
-                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
-                               struct commit *exclude;
-                               struct commit *include;
-                               
-                               exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
-                               include = get_commit_reference(next, sha1, 0);
-                               if (!exclude || !include)
-                                       die("Invalid revision range %s..%s", arg, next);
-                               limited = 1;
-                               handle_one_commit(exclude, &list);
-                               handle_one_commit(include, &list);
-                               continue;
-                       }
-                       *dotdot = '.';
-               }
-               if (*arg == '^') {
-                       flags = UNINTERESTING;
-                       arg++;
-                       limited = 1;
-               }
-               if (get_sha1(arg, sha1) < 0) {
-                       struct stat st;
-                       if (lstat(arg, &st) < 0)
-                               die("'%s': %s", arg, strerror(errno));
-                       break;
-               }
-               commit = get_commit_reference(arg, sha1, flags);
-               handle_one_commit(commit, &list);
        }
 
+       list = revs.commits;
+
        if (!list &&
-           (!(tag_objects||tree_objects||blob_objects) && !pending_objects))
+           (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
                usage(rev_list_usage);
 
-       paths = get_pathspec(prefix, argv + i);
-       if (paths) {
-               limited = 1;
-               diff_tree_setup_paths(paths);
-       }
+       prepare_revision_walk(&revs);
+       if (revs.tree_objects)
+               mark_edges_uninteresting(revs.commits);
+
+       if (bisect_list)
+               revs.commits = find_bisection(revs.commits);
 
        save_commit_buffer = verbose_header;
        track_object_refs = 0;
 
-       if (!merge_order) {             
-               sort_by_date(&list);
-               if (list && !limited && max_count == 1 &&
-                   !tag_objects && !tree_objects && !blob_objects) {
-                       show_commit(list->item);
-                       return 0;
-               }
-               if (limited)
-                       list = limit_list(list);
-               if (topo_order)
-                       sort_in_topological_order(&list, lifo);
-               show_commit_list(list);
-       } else {
-#ifndef NO_OPENSSL
-               if (sort_list_in_merge_order(list, &process_commit)) {
-                       die("merge order sort failed\n");
-               }
-#else
-               die("merge order sort unsupported, OpenSSL not linked");
-#endif
-       }
+       show_commit_list(&revs);
 
        return 0;
 }
index 610eacb..f90e999 100644 (file)
@@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg)
                "--header",
                "--max-age=",
                "--max-count=",
-               "--merge-order",
                "--min-age=",
                "--no-merges",
                "--objects",
                "--objects-edge",
                "--parents",
                "--pretty",
-               "--show-breaks",
                "--sparse",
                "--topo-order",
                "--date-order",
diff --git a/revision.c b/revision.c
new file mode 100644 (file)
index 0000000..4885871
--- /dev/null
@@ -0,0 +1,707 @@
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "diff.h"
+#include "refs.h"
+#include "revision.h"
+
+static char *path_name(struct name_path *path, const char *name)
+{
+       struct name_path *p;
+       char *n, *m;
+       int nlen = strlen(name);
+       int len = nlen + 1;
+
+       for (p = path; p; p = p->up) {
+               if (p->elem_len)
+                       len += p->elem_len + 1;
+       }
+       n = xmalloc(len);
+       m = n + len - (nlen + 1);
+       strcpy(m, name);
+       for (p = path; p; p = p->up) {
+               if (p->elem_len) {
+                       m -= p->elem_len + 1;
+                       memcpy(m, p->elem, p->elem_len);
+                       m[p->elem_len] = '/';
+               }
+       }
+       return n;
+}
+
+struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name)
+{
+       struct object_list *entry = xmalloc(sizeof(*entry));
+       entry->item = obj;
+       entry->next = *p;
+       entry->name = path_name(path, name);
+       *p = entry;
+       return &entry->next;
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+       if (blob->object.flags & UNINTERESTING)
+               return;
+       blob->object.flags |= UNINTERESTING;
+}
+
+void mark_tree_uninteresting(struct tree *tree)
+{
+       struct object *obj = &tree->object;
+       struct tree_entry_list *entry;
+
+       if (obj->flags & UNINTERESTING)
+               return;
+       obj->flags |= UNINTERESTING;
+       if (!has_sha1_file(obj->sha1))
+               return;
+       if (parse_tree(tree) < 0)
+               die("bad tree %s", sha1_to_hex(obj->sha1));
+       entry = tree->entries;
+       tree->entries = NULL;
+       while (entry) {
+               struct tree_entry_list *next = entry->next;
+               if (entry->directory)
+                       mark_tree_uninteresting(entry->item.tree);
+               else
+                       mark_blob_uninteresting(entry->item.blob);
+               free(entry);
+               entry = next;
+       }
+}
+
+void mark_parents_uninteresting(struct commit *commit)
+{
+       struct commit_list *parents = commit->parents;
+
+       while (parents) {
+               struct commit *commit = parents->item;
+               commit->object.flags |= UNINTERESTING;
+
+               /*
+                * Normally we haven't parsed the parent
+                * yet, so we won't have a parent of a parent
+                * here. However, it may turn out that we've
+                * reached this commit some other way (where it
+                * wasn't uninteresting), in which case we need
+                * to mark its parents recursively too..
+                */
+               if (commit->parents)
+                       mark_parents_uninteresting(commit);
+
+               /*
+                * A missing commit is ok iff its parent is marked
+                * uninteresting.
+                *
+                * We just mark such a thing parsed, so that when
+                * it is popped next time around, we won't be trying
+                * to parse it and get an error.
+                */
+               if (!has_sha1_file(commit->object.sha1))
+                       commit->object.parsed = 1;
+               parents = parents->next;
+       }
+}
+
+static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+       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)
+{
+       struct object *object;
+
+       object = parse_object(sha1);
+       if (!object)
+               die("bad object %s", name);
+
+       /*
+        * 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))
+                       add_pending_object(revs, object, tag->tag);
+               object = parse_object(tag->tagged->sha1);
+               if (!object)
+                       die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+       }
+
+       /*
+        * Commit object? Just return it, we'll do all the complex
+        * reachability crud.
+        */
+       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) {
+                       mark_parents_uninteresting(commit);
+                       revs->limited = 1;
+               }
+               return commit;
+       }
+
+       /*
+        * Tree object? Either mark it uniniteresting, or add it
+        * to the list of objects to look at later..
+        */
+       if (object->type == tree_type) {
+               struct tree *tree = (struct tree *)object;
+               if (!revs->tree_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_tree_uninteresting(tree);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+
+       /*
+        * Blob object? You know the drill by now..
+        */
+       if (object->type == blob_type) {
+               struct blob *blob = (struct blob *)object;
+               if (!revs->blob_objects)
+                       return NULL;
+               if (flags & UNINTERESTING) {
+                       mark_blob_uninteresting(blob);
+                       return NULL;
+               }
+               add_pending_object(revs, object, "");
+               return NULL;
+       }
+       die("%s is unknown object", name);
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+       struct commit_list *list = orig;
+       while (list) {
+               struct commit *commit = list->item;
+               list = list->next;
+               if (commit->object.flags & UNINTERESTING)
+                       continue;
+               return 0;
+       }
+       return 1;
+}
+
+#define TREE_SAME      0
+#define TREE_NEW       1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+                   int addremove, unsigned mode,
+                   const unsigned char *sha1,
+                   const char *base, const char *path)
+{
+       int diff = TREE_DIFFERENT;
+
+       /*
+        * Is it an add of a new file? It means that
+        * the old tree didn't have it at all, so we
+        * will turn "TREE_SAME" -> "TREE_NEW", but
+        * leave any "TREE_DIFFERENT" alone (and if
+        * it already was "TREE_NEW", we'll keep it
+        * "TREE_NEW" of course).
+        */
+       if (addremove == '+') {
+               diff = tree_difference;
+               if (diff != TREE_SAME)
+                       return;
+               diff = TREE_NEW;
+       }
+       tree_difference = diff;
+}
+
+static void file_change(struct diff_options *options,
+                unsigned old_mode, unsigned new_mode,
+                const unsigned char *old_sha1,
+                const unsigned char *new_sha1,
+                const char *base, const char *path)
+{
+       tree_difference = TREE_DIFFERENT;
+}
+
+static struct diff_options diff_opt = {
+       .recursive = 1,
+       .add_remove = file_add_remove,
+       .change = file_change,
+};
+
+static int compare_tree(struct tree *t1, struct tree *t2)
+{
+       if (!t1)
+               return TREE_NEW;
+       if (!t2)
+               return TREE_DIFFERENT;
+       tree_difference = TREE_SAME;
+       if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+               return TREE_DIFFERENT;
+       return tree_difference;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+       int retval;
+       void *tree;
+       struct tree_desc empty, real;
+
+       if (!t1)
+               return 0;
+
+       tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+       if (!tree)
+               return 0;
+       real.buf = tree;
+
+       empty.buf = "";
+       empty.size = 0;
+
+       tree_difference = 0;
+       retval = diff_tree(&empty, &real, "", &diff_opt);
+       free(tree);
+
+       return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+       struct commit_list **pp, *parent;
+
+       if (!commit->tree)
+               return;
+
+       if (!commit->parents) {
+               if (!same_tree_as_empty(commit->tree))
+                       commit->object.flags |= TREECHANGE;
+               return;
+       }
+
+       pp = &commit->parents;
+       while ((parent = *pp) != NULL) {
+               struct commit *p = parent->item;
+
+               if (p->object.flags & UNINTERESTING) {
+                       pp = &parent->next;
+                       continue;
+               }
+
+               parse_commit(p);
+               switch (compare_tree(p->tree, commit->tree)) {
+               case TREE_SAME:
+                       parent->next = NULL;
+                       commit->parents = parent;
+                       return;
+
+               case TREE_NEW:
+                       if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
+                               *pp = parent->next;
+                               continue;
+                       }
+               /* fallthrough */
+               case TREE_DIFFERENT:
+                       pp = &parent->next;
+                       continue;
+               }
+               die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+       }
+       commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+       struct commit_list *parent = commit->parents;
+
+       /*
+        * If the commit is uninteresting, don't try to
+        * prune parents - we want the maximal uninteresting
+        * set.
+        *
+        * Normally we haven't parsed the parent
+        * yet, so we won't have a parent of a parent
+        * here. However, it may turn out that we've
+        * reached this commit some other way (where it
+        * wasn't uninteresting), in which case we need
+        * to mark its parents recursively too..
+        */
+       if (commit->object.flags & UNINTERESTING) {
+               while (parent) {
+                       struct commit *p = parent->item;
+                       parent = parent->next;
+                       parse_commit(p);
+                       p->object.flags |= UNINTERESTING;
+                       if (p->parents)
+                               mark_parents_uninteresting(p);
+                       if (p->object.flags & SEEN)
+                               continue;
+                       p->object.flags |= SEEN;
+                       insert_by_date(p, list);
+               }
+               return;
+       }
+
+       /*
+        * Ok, the commit wasn't uninteresting. Try to
+        * simplify the commit history and find the parent
+        * that has no differences in the path set if one exists.
+        */
+       if (revs->paths)
+               try_to_simplify_commit(revs, commit);
+
+       parent = commit->parents;
+       while (parent) {
+               struct commit *p = parent->item;
+
+               parent = parent->next;
+
+               parse_commit(p);
+               if (p->object.flags & SEEN)
+                       continue;
+               p->object.flags |= SEEN;
+               insert_by_date(p, list);
+       }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+       struct commit_list *newlist = NULL;
+       struct commit_list **p = &newlist;
+
+       if (revs->paths)
+               diff_tree_setup_paths(revs->paths);
+
+       while (list) {
+               struct commit_list *entry = list;
+               struct commit *commit = list->item;
+               struct object *obj = &commit->object;
+
+               list = list->next;
+               free(entry);
+
+               if (revs->max_age != -1 && (commit->date < revs->max_age))
+                       obj->flags |= UNINTERESTING;
+               if (revs->unpacked && has_sha1_pack(obj->sha1))
+                       obj->flags |= UNINTERESTING;
+               add_parents_to_list(revs, commit, &list);
+               if (obj->flags & UNINTERESTING) {
+                       mark_parents_uninteresting(commit);
+                       if (everybody_uninteresting(list))
+                               break;
+                       continue;
+               }
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               p = &commit_list_insert(commit, p)->next;
+       }
+       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);
+       return 0;
+}
+
+static void handle_all(struct rev_info *revs, unsigned flags)
+{
+       all_revs = revs;
+       all_flags = flags;
+       for_each_ref(handle_one_ref);
+}
+
+/*
+ * Parse revision information, filling in the "rev_info" structure,
+ * and removing the used arguments from the argument list.
+ *
+ * Returns the number of arguments left that weren't recognized
+ * (which are also moved to the head of the argument list)
+ */
+int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
+{
+       int i, flags, seen_dashdash;
+       const char **unrecognized = argv + 1;
+       int left = 1;
+
+       memset(revs, 0, sizeof(*revs));
+       revs->lifo = 1;
+       revs->dense = 1;
+       revs->prefix = setup_git_directory();
+       revs->max_age = -1;
+       revs->min_age = -1;
+       revs->max_count = -1;
+
+       /* First, search for "--" */
+       seen_dashdash = 0;
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (strcmp(arg, "--"))
+                       continue;
+               argv[i] = NULL;
+               argc = i;
+               revs->paths = get_pathspec(revs->prefix, argv + i + 1);
+               seen_dashdash = 1;
+               break;
+       }
+
+       flags = 0;
+       for (i = 1; i < argc; i++) {
+               struct commit *commit;
+               const char *arg = argv[i];
+               unsigned char sha1[20];
+               char *dotdot;
+               int local_flags;
+
+               if (*arg == '-') {
+                       if (!strncmp(arg, "--max-count=", 12)) {
+                               revs->max_count = atoi(arg + 12);
+                               continue;
+                       }
+                       if (!strncmp(arg, "--max-age=", 10)) {
+                               revs->max_age = atoi(arg + 10);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--min-age=", 10)) {
+                               revs->min_age = atoi(arg + 10);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--since=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--after=", 8)) {
+                               revs->max_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--before=", 9)) {
+                               revs->min_age = approxidate(arg + 9);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--until=", 8)) {
+                               revs->min_age = approxidate(arg + 8);
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--all")) {
+                               handle_all(revs, flags);
+                               continue;
+                       }
+                       if (!strcmp(arg, "--not")) {
+                               flags ^= UNINTERESTING;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--default")) {
+                               if (++i >= argc)
+                                       die("bad --default argument");
+                               def = argv[i];
+                               continue;
+                       }
+                       if (!strcmp(arg, "--topo-order")) {
+                               revs->topo_order = 1;
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--date-order")) {
+                               revs->lifo = 0;
+                               revs->topo_order = 1;
+                               revs->limited = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--dense")) {
+                               revs->dense = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--sparse")) {
+                               revs->dense = 0;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--remove-empty")) {
+                               revs->remove_empty_trees = 1;
+                               continue;
+                       }
+                       if (!strncmp(arg, "--no-merges", 11)) {
+                               revs->no_merges = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--objects-edge")) {
+                               revs->tag_objects = 1;
+                               revs->tree_objects = 1;
+                               revs->blob_objects = 1;
+                               revs->edge_hint = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--unpacked")) {
+                               revs->unpacked = 1;
+                               revs->limited = 1;
+                               continue;
+                       }
+                       *unrecognized++ = arg;
+                       left++;
+                       continue;
+               }
+               dotdot = strstr(arg, "..");
+               if (dotdot) {
+                       unsigned char from_sha1[20];
+                       char *next = dotdot + 2;
+                       *dotdot = 0;
+                       if (!*next)
+                               next = "HEAD";
+                       if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+                               struct commit *exclude;
+                               struct commit *include;
+
+                               exclude = get_commit_reference(revs, arg, from_sha1, flags ^ UNINTERESTING);
+                               include = get_commit_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);
+                               continue;
+                       }
+                       *dotdot = '.';
+               }
+               local_flags = 0;
+               if (*arg == '^') {
+                       local_flags = UNINTERESTING;
+                       arg++;
+               }
+               if (get_sha1(arg, sha1) < 0) {
+                       struct stat st;
+                       int j;
+
+                       if (seen_dashdash || local_flags)
+                               die("bad revision '%s'", arg);
+
+                       /* If we didn't have a "--", all filenames must exist */
+                       for (j = i; j < argc; j++) {
+                               if (lstat(argv[j], &st) < 0)
+                                       die("'%s': %s", arg, strerror(errno));
+                       }
+                       revs->paths = get_pathspec(revs->prefix, argv + i);
+                       break;
+               }
+               commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
+               add_one_commit(commit, revs);
+       }
+       if (def && !revs->commits) {
+               unsigned char sha1[20];
+               struct commit *commit;
+               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);
+       }
+       if (revs->paths)
+               revs->limited = 1;
+       return left;
+}
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+       sort_by_date(&revs->commits);
+       if (revs->limited)
+               limit_list(revs);
+       if (revs->topo_order)
+               sort_in_topological_order(&revs->commits, revs->lifo);
+}
+
+static int rewrite_one(struct commit **pp)
+{
+       for (;;) {
+               struct commit *p = *pp;
+               if (p->object.flags & (TREECHANGE | UNINTERESTING))
+                       return 0;
+               if (!p->parents)
+                       return -1;
+               *pp = p->parents->item;
+       }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+       struct commit_list **pp = &commit->parents;
+       while (*pp) {
+               struct commit_list *parent = *pp;
+               if (rewrite_one(&parent->item) < 0) {
+                       *pp = parent->next;
+                       continue;
+               }
+               pp = &parent->next;
+       }
+}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit_list *list = revs->commits;
+       struct commit *commit;
+
+       if (!list)
+               return NULL;
+
+       /* Check the max_count ... */
+       commit = list->item;
+       switch (revs->max_count) {
+       case -1:
+               break;
+       case 0:
+               return NULL;
+       default:
+               revs->max_count--;
+       }
+
+       do {
+               commit = pop_most_recent_commit(&revs->commits, SEEN);
+               if (commit->object.flags & (UNINTERESTING|SHOWN))
+                       continue;
+               if (revs->min_age != -1 && (commit->date > revs->min_age))
+                       continue;
+               if (revs->max_age != -1 && (commit->date < revs->max_age))
+                       return NULL;
+               if (revs->no_merges && commit->parents && commit->parents->next)
+                       continue;
+               if (revs->paths && revs->dense) {
+                       if (!(commit->object.flags & TREECHANGE))
+                               continue;
+                       rewrite_parents(commit);
+               }
+               commit->object.flags |= SHOWN;
+               return commit;
+       } while (revs->commits);
+       return NULL;
+}
diff --git a/revision.h b/revision.h
new file mode 100644 (file)
index 0000000..31e8f61
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef REVISION_H
+#define REVISION_H
+
+#define SEEN           (1u<<0)
+#define UNINTERESTING   (1u<<1)
+#define TREECHANGE     (1u<<2)
+#define SHOWN          (1u<<3)
+#define TMP_MARK       (1u<<4) /* for isolated cases; clean after use */
+
+struct rev_info {
+       /* Starting list */
+       struct commit_list *commits;
+       struct object_list *pending_objects;
+
+       /* Basic information */
+       const char *prefix;
+       const char **paths;
+
+       /* Traversal flags */
+       unsigned int    dense:1,
+                       no_merges:1,
+                       remove_empty_trees:1,
+                       lifo:1,
+                       topo_order:1,
+                       tag_objects:1,
+                       tree_objects:1,
+                       blob_objects:1,
+                       edge_hint:1,
+                       limited:1,
+                       unpacked:1;
+
+       /* special limits */
+       int max_count;
+       unsigned long max_age;
+       unsigned long min_age;
+};
+
+/* revision.c */
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern void prepare_revision_walk(struct rev_info *revs);
+extern struct commit *get_revision(struct rev_info *revs);
+
+extern void mark_parents_uninteresting(struct commit *commit);
+extern void mark_tree_uninteresting(struct tree *tree);
+
+struct name_path {
+       struct name_path *up;
+       int elem_len;
+       const char *elem;
+};
+
+extern struct object_list **add_object(struct object *obj,
+                                      struct object_list **p,
+                                      struct name_path *path,
+                                      const char *name);
+
+#endif
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
deleted file mode 100755 (executable)
index 7724e8a..0000000
+++ /dev/null
@@ -1,462 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Jon Seymour
-#
-
-test_description='Tests git-rev-list --merge-order functionality'
-
-. ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
-
-# test-case specific test function
-check_adjacency()
-{
-    read previous
-    echo "= $previous"
-    while read next
-    do
-        if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null)
-        then
-            echo "^ $next"
-        else
-            echo "| $next"
-        fi
-        previous=$next
-    done
-}
-
-list_duplicates()
-{
-    "$@" | sort | uniq -d
-}
-
-grep_stderr()
-{
-    args=$1
-    shift 1
-    "$@" 2>&1 | grep "$args"
-}
-
-date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
-on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
-on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
-on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
-on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
-on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
-on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
-
-
-#
-# note: as of 20/6, it isn't possible to create duplicate parents, so this
-# can't be tested.
-#
-#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
-hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
-save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
-save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
-save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
-save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
-save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
-save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
-save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
-save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
-save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
-save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
-save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
-save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
-save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
-save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
-save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
-save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
-save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
-save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
-
-hide_error save_tag g0 unique_commit g0 tree
-save_tag g1 unique_commit g1 tree -p g0
-save_tag h1 unique_commit g2 tree -p g0
-save_tag g2 unique_commit g3 tree -p g1 -p h1
-save_tag h2 unique_commit g4 tree -p g2
-save_tag g3 unique_commit g5 tree -p g2
-save_tag g4 unique_commit g6 tree -p g3 -p h2
-
-git-update-ref HEAD $(tag l5)
-
-test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
-19
-EOF
-
-if git-rev-list --merge-order HEAD 2>&1 | grep 'OpenSSL not linked' >/dev/null
-then
-    test_expect_success 'skipping merge-order test' :
-    test_done
-    exit
-fi
-
-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-= a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF
-= g4
-| h2
-^ g3
-= g2
-| h1
-^ g1
-= g0
-EOF
-
-test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-EOF
-
-test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
-a4
-b4
-b3
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF
-= root
-EOF
-
-test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF
-= l0
-= root
-EOF
-
-test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF
-= l1
-| l0
-= root
-EOF
-
-test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
-^ l2
-| l1
-| l0
-EOF
-
-test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
-^ l2
-| l1
-EOF
-
-test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
-^ l2
-EOF
-
-test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
-^ l5
-| l4
-| l3
-EOF
-
-test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
-^ l5
-| l4
-EOF
-
-test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
-^ l5
-EOF
-
-test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-EOF
-
-test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-c2
-b3
-EOF
-
-test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-EOF
-
-test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-c3
-b4
-a3
-a2
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
-EOF
-
-test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
-EOF
-
-test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF
-EOF
-
-#
-# can't test this now - duplicate parents can't be created
-#
-#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF
-#= m3 c3 a4 c3
-#| a4 c3 b4 a3
-#| b4 a3 b3
-#| b3 b2
-#^ a3 a2
-#| a2 a1
-#| a1 a0
-#^ c3 c2
-#| c2 b2 c1
-#| b2 b1
-#^ c1 b1
-#| b1 a0
-#= a0 l2
-#| l2 l1
-#| l1 l0
-#| l0 root
-#= root
-#EOF
-
-test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF
-EOF
-
-test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF
-= l5r1
-| r1
-| r0
-| alt_root
-^ l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF
-= r1l5
-| l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-| root
-^ r1
-| r0
-= alt_root
-EOF
-
-test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-#
-#
-
-test_done