1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-08 21:26:08 +02:00
git/rev-list.c
jon@blackcubes.dyndns.org a3437b8c26 [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>
2005-06-06 09:07:26 -07:00

232 lines
4.9 KiB
C

#include "cache.h"
#include "commit.h"
#include "epoch.h"
#define SEEN (1u << 0)
#define INTERESTING (1u << 1)
static const char rev_list_usage[] =
"usage: git-rev-list [OPTION] commit-id <commit-id>\n"
" --max-count=nr\n"
" --max-age=epoch\n"
" --min-age=epoch\n"
" --header\n"
" --pretty\n"
" --merge-order [ --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
static int hdr_termination = 0;
static const char *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 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;
while (parents) {
printf(" %s", sha1_to_hex(parents->item->object.sha1));
parents = parents->next;
}
}
putchar('\n');
if (verbose_header) {
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)
{
while (list) {
struct commit *commit = pop_most_recent_commit(&list, SEEN);
if (process_commit(commit) == STOP)
break;
}
}
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;
parents = parents->next;
}
}
static int everybody_uninteresting(struct commit_list *list)
{
while (list) {
struct commit *commit = list->item;
list = list->next;
if (commit->object.flags & UNINTERESTING)
continue;
return 0;
}
return 1;
}
struct commit_list *limit_list(struct commit_list *list)
{
struct commit_list *newlist = NULL;
struct commit_list **p = &newlist;
do {
struct commit *commit = pop_most_recent_commit(&list, SEEN);
struct object *obj = &commit->object;
if (obj->flags & UNINTERESTING) {
mark_parents_uninteresting(commit);
if (everybody_uninteresting(list))
break;
continue;
}
p = &commit_list_insert(commit, p)->next;
} while (list);
return newlist;
}
static enum cmit_fmt get_commit_format(const char *arg)
{
if (!*arg)
return CMIT_FMT_DEFAULT;
if (!strcmp(arg, "=raw"))
return CMIT_FMT_RAW;
if (!strcmp(arg, "=medium"))
return CMIT_FMT_MEDIUM;
if (!strcmp(arg, "=short"))
return CMIT_FMT_SHORT;
usage(rev_list_usage);
}
int main(int argc, char **argv)
{
struct commit_list *list = NULL;
int i, limited = 0;
for (i = 1 ; i < argc; i++) {
int flags;
char *arg = argv[i];
unsigned char sha1[20];
struct commit *commit;
if (!strncmp(arg, "--max-count=", 12)) {
max_count = atoi(arg + 12);
continue;
}
if (!strncmp(arg, "--max-age=", 10)) {
max_age = atoi(arg + 10);
continue;
}
if (!strncmp(arg, "--min-age=", 10)) {
min_age = atoi(arg + 10);
continue;
}
if (!strcmp(arg, "--header")) {
verbose_header = 1;
continue;
}
if (!strncmp(arg, "--pretty", 8)) {
commit_format = get_commit_format(arg+8);
verbose_header = 1;
hdr_termination = '\n';
prefix = "commit ";
continue;
}
if (!strcmp(arg, "--parents")) {
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 == '^') {
flags = UNINTERESTING;
arg++;
limited = 1;
}
if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
die("bad commit object %s", arg);
commit->object.flags |= flags;
commit_list_insert(commit, &list);
}
if (!list)
usage(rev_list_usage);
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");
}
}
return 0;
}