[PATCH] Modify git-rev-list to linearise the commit history in merge order.

This patch linearises the GIT commit history graph into merge order
which is defined by invariants specified in Documentation/git-rev-list.txt.

The linearisation produced by this patch is superior in an objective sense
to that produced by the existing git-rev-list implementation in that
the linearisation produced is guaranteed to have the minimum number of
discontinuities, where a discontinuity is defined as an adjacent pair of
commits in the output list which are not related in a direct child-parent
relationship.

With this patch a graph like this:

	a4 ---
	| \   \
	|  b4 |
	|/ |  |
	a3 |  |
	|  |  |
	a2 |  |
	|  |  c3
	|  |  |
	|  |  c2
	|  b3 |
	|  | /|
	|  b2 |
	|  |  c1
	|  | /
	|  b1
	a1 |
	|  |
	a0 |
	| /
	root

Sorts like this:

	= a4
	| c3
	| c2
	| c1
	^ b4
	| b3
	| b2
	| b1
	^ a3
	| a2
	| a1
	| a0
	= root

Instead of this:

	= a4
	| c3
	^ b4
	| a3
	^ c2
	^ b3
	^ a2
	^ b2
	^ c1
	^ a1
	^ b1
	^ a0
	= root

A test script, t/t6000-rev-list.sh, includes a test which demonstrates
that the linearisation produced by --merge-order has less discontinuities
than the linearisation produced by git-rev-list without the --merge-order
flag specified. To see this, do the following:

	cd t
	./t6000-rev-list.sh
	cd trash
	cat actual-default-order
	cat actual-merge-order

The existing behaviour of git-rev-list is preserved, by default. To obtain
the modified behaviour, specify --merge-order or --merge-order --show-breaks
on the command line.

This version of the patch has been tested on the git repository and also on the linux-2.6
repository and has reasonable performance on both - ~50-100% slower than the original algorithm.

This version of the patch has incorporated a functional equivalent of the Linus' output limiting
algorithm into the merge-order algorithm itself. This operates per the notes associated
with Linus' commit 337cb3fb8d.

This version has incorporated Linus' feedback regarding proposed changes to rev-list.c.
(see: [PATCH] Factor out filtering in rev-list.c)

This version has improved the way sort_first_epoch marks commits as uninteresting.

For more details about this change, refer to Documentation/git-rev-list.txt
and http://blackcubes.dyndns.org/epoch/.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
This commit is contained in:
jon@blackcubes.dyndns.org
2005-06-06 15:39:40 +00:00
committed by Linus Torvalds
parent d925ffbd06
commit a3437b8c26
8 changed files with 840 additions and 18 deletions

View File

@ -1,9 +1,9 @@
#include "cache.h"
#include "commit.h"
#include "epoch.h"
#define SEEN (1u << 0)
#define INTERESTING (1u << 1)
#define UNINTERESTING (1u << 2)
static const char rev_list_usage[] =
"usage: git-rev-list [OPTION] commit-id <commit-id>\n"
@ -11,7 +11,8 @@ static const char rev_list_usage[] =
" --max-age=epoch\n"
" --min-age=epoch\n"
" --header\n"
" --pretty";
" --pretty\n"
" --merge-order [ --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
@ -21,9 +22,19 @@ 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 void show_commit(struct commit *commit)
{
if (show_breaks) {
prefix = "| ";
if (commit->object.flags & DISCONTINUITY) {
prefix = "^ ";
} else if (commit->object.flags & BOUNDARY) {
prefix = "= ";
}
}
printf("%s%s", prefix, sha1_to_hex(commit->object.sha1));
if (show_parents) {
struct commit_list *parents = commit->parents;
@ -37,7 +48,38 @@ static void show_commit(struct commit *commit)
static char pretty_header[16384];
pretty_print_commit(commit_format, commit->buffer, ~0, pretty_header, sizeof(pretty_header));
printf("%s%c", pretty_header, hdr_termination);
}
}
static int filter_commit(struct commit * commit)
{
if (commit->object.flags & UNINTERESTING)
return CONTINUE;
if (min_age != -1 && (commit->date > min_age))
return CONTINUE;
if (max_age != -1 && (commit->date < max_age))
return STOP;
if (max_count != -1 && !max_count--)
return STOP;
return DO;
}
static int process_commit(struct commit * commit)
{
int action=filter_commit(commit);
if (action == STOP) {
return STOP;
}
if (action == CONTINUE) {
return CONTINUE;
}
show_commit(commit);
return CONTINUE;
}
static void show_commit_list(struct commit_list *list)
@ -45,15 +87,8 @@ static void show_commit_list(struct commit_list *list)
while (list) {
struct commit *commit = pop_most_recent_commit(&list, SEEN);
if (commit->object.flags & UNINTERESTING)
continue;
if (min_age != -1 && (commit->date > min_age))
continue;
if (max_age != -1 && (commit->date < max_age))
if (process_commit(commit) == STOP)
break;
if (max_count != -1 && !max_count--)
break;
show_commit(commit);
}
}
@ -151,6 +186,14 @@ int main(int argc, char **argv)
show_parents = 1;
continue;
}
if (!strncmp(arg, "--merge-order", 13)) {
merge_order = 1;
continue;
}
if (!strncmp(arg, "--show-breaks", 13)) {
show_breaks = 1;
continue;
}
flags = 0;
if (*arg == '^') {
@ -158,7 +201,7 @@ int main(int argc, char **argv)
arg++;
limited = 1;
}
if (get_sha1(arg, sha1))
if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
@ -170,9 +213,19 @@ int main(int argc, char **argv)
if (!list)
usage(rev_list_usage);
if (limited)
list = limit_list(list);
if (!merge_order) {
if (limited)
list = limit_list(list);
show_commit_list(list);
} else {
if (sort_list_in_merge_order(list, &process_commit)) {
die("merge order sort failed\n");
}
}
show_commit_list(list);
return 0;
}