--------
[verse]
'git-checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
- [--stage=<number>] [--] <file>...
+ [--stage=<number>]
+ [-z] [--stdin]
+ [--] [<file>]\*
DESCRIPTION
-----------
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.
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`.
[ \--no-merges ]
[ \--remove-empty ]
[ \--all ]
- [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
+ [ \--topo-order ]
[ \--parents ]
[ \--objects [ \--unpacked ] ]
[ \--pretty | \--header ]
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>.
"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
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
# 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://
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)
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 \
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)
endif
ifndef NO_OPENSSL
- LIB_OBJS += epoch.o
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R.
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 $@
--- /dev/null
+#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;
+}
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 */
* 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)
{
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] == '-' ) {
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;
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;
}
*
* 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
* but get used to it in scripting!).
*/
#include "cache.h"
+#include "strbuf.h"
+#include "quote.h"
static const char *prefix;
static int prefix_length;
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);
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);
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();
* 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;
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) {
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 */
}
}
+ *src_copied = count_range(span);
+
/* sanity check */
if (data != top || out != dst_size)
return -1;
/* 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;
}
#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;
}
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;
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;
}
}
}
#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,
* 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;
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
--- /dev/null
+#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;
+}
#include "cache.h"
#include "diff.h"
#include "diffcore.h"
-#include "delta.h"
-#include "count-delta.h"
/* Table of rename/copy destinations */
* 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;
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)
#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 */
#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
+++ /dev/null
-/*
- * 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;
-}
+++ /dev/null
-#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 */
# 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;
# 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);
use strict;
use warnings;
use Getopt::Std;
+use File::Copy;
use File::Spec;
use File::Temp qw(tempfile);
use File::Path qw(mkpath);
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*$/;
-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($$$) {
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+<(.*)>$/) {
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)" ||
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' |
#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
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)
} commands[] = {
{ "version", cmd_version },
{ "help", cmd_help },
+ { "log", cmd_log },
};
int i;
--- /dev/null
+#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);
+}
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;
#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"
" --remove-empty\n"
" --all\n"
" ordering output:\n"
-" --merge-order [ --show-breaks ]\n"
" --topo-order\n"
" --date-order\n"
" formatting output:\n"
" --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;
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,
{
struct object *obj = &blob->object;
- if (!blob_objects)
+ if (!revs.blob_objects)
return p;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
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;
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))
}
}
-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.
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;
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;
}
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);
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));
}
}
}
-#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")) {
commit_prefix = "commit ";
continue;
}
- if (!strncmp(arg, "--no-merges", 11)) {
- no_merges = 1;
- continue;
- }
if (!strcmp(arg, "--parents")) {
show_parents = 1;
continue;
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;
}
"--header",
"--max-age=",
"--max-count=",
- "--merge-order",
"--min-age=",
"--no-merges",
"--objects",
"--objects-edge",
"--parents",
"--pretty",
- "--show-breaks",
"--sparse",
"--topo-order",
"--date-order",
--- /dev/null
+#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;
+}
--- /dev/null
+#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
+++ /dev/null
-#!/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