rev-tree.c 5.1 KB
Newer Older
1 2
#include "cache.h"

3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * The low 16 bits of the "flags" field shows whether
 * a commit is part of the path to the root for that
 * parent.
 *
 * Bit 16 is an internal flag that we've seen the
 * definition for this rev, and not just seen it as
 * a parent target.
 */
#define MAX_COMMITS (16)
#define marked(rev)	((rev)->flags & 0xffff)
#define SEEN 0x10000

static int show_edges = 0;
17
static int basemask = 0;
18 19 20 21 22 23 24 25

struct parent {
	struct revision *parent;
	struct parent *next;
};

struct revision {
	unsigned int flags;
26
	unsigned char sha1[20];
27
	struct parent *parent;
28 29
};

30 31
static struct revision **revs;
static int nr_revs, rev_allocs;
32

33
static int find_rev(unsigned char *sha1)
34
{
35
	int first = 0, last = nr_revs;
36 37 38

	while (first < last) {
		int next = (first + last) / 2;
39
		struct revision *rev = revs[next];
40 41
		int cmp;

42 43 44
		cmp = memcmp(sha1, rev->sha1, 20);
		if (!cmp)
			return next;
45 46 47 48 49 50 51 52 53
		if (cmp < 0) {
			last = next;
			continue;
		}
		first = next+1;
	}
	return -first-1;
}

54
static struct revision *lookup_rev(unsigned char *sha1)
55
{
56 57
	int pos = find_rev(sha1);
	struct revision *n;
58 59

	if (pos >= 0)
60 61
		return revs[pos];
	
62 63
	pos = -pos-1;

64 65 66
	if (rev_allocs == nr_revs) {
		rev_allocs = alloc_nr(rev_allocs);
		revs = realloc(revs, rev_allocs * sizeof(struct revision *));
67
	}
68 69 70
	n = malloc(sizeof(struct revision));

	n->flags = 0;
71
	memcpy(n->sha1, sha1, 20);
72 73 74 75 76 77 78 79
	n->parent = NULL;

	/* Insert it into the right place */
	memmove(revs + pos + 1, revs + pos, (nr_revs - pos) * sizeof(struct revision *));
	revs[pos] = n;
	nr_revs++;

	return n;
80 81
}

82
static int add_relationship(struct revision *rev, unsigned char *parent_sha)
83
{
84 85
	struct revision *parent_rev = lookup_rev(parent_sha);
	struct parent **pp = &rev->parent, *p;
86

87 88 89 90 91 92 93 94 95 96 97
	while ((p = *pp) != NULL) {
		if (p->parent == parent_rev)
			return 0;
		pp = &p->next;
	}

	p = malloc(sizeof(*p));
	p->parent = parent_rev;
	p->next = NULL;
	*pp = p;
	return 1;
98 99 100 101
}

static int parse_commit(unsigned char *sha1)
{
102 103 104
	struct revision *rev = lookup_rev(sha1);

	if (!(rev->flags & SEEN)) {
105 106 107 108 109
		void *buffer;
		unsigned long size;
		char type[20];
		unsigned char parent[20];

110
		rev->flags |= SEEN;
111 112 113 114 115
		buffer = read_sha1_file(sha1, type, &size);
		if (!buffer || strcmp(type, "commit"))
			return -1;
		buffer += 46; /* "tree " + "hex sha1" + "\n" */
		while (!memcmp(buffer, "parent ", 7) && !get_sha1_hex(buffer+7, parent)) {
116
			add_relationship(rev, parent);
117 118 119 120 121 122 123 124 125 126 127 128
			parse_commit(parent);
			buffer += 48;	/* "parent " + "hex sha1" + "\n" */
		}
	}
	return 0;	
}

static void read_cache_file(const char *path)
{
	FILE *file = fopen(path, "r");
	char line[100];

129 130 131
	if (!file)
		usage("bad revtree cache file (%s)", path);

132 133
	while (fgets(line, sizeof(line), file)) {
		unsigned char sha1[20], parent[20];
134 135
		struct revision *rev;

136 137
		if (get_sha1_hex(line, sha1) || get_sha1_hex(line + 41, parent))
			usage("bad rev-tree cache file %s", path);
138 139 140
		rev = lookup_rev(sha1);
		rev->flags |= SEEN;
		add_relationship(rev, parent);
141 142 143 144
	}
	fclose(file);
}

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
static void mark_sha1_path(struct revision *rev, unsigned int mask)
{
	struct parent *p;

	if (rev->flags & mask)
		return;

	rev->flags |= mask;
	p = rev->parent;
	while (p) {
		mark_sha1_path(p->parent, mask);
		p = p->next;
	}
}

160
/*
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
 * Some revisions are less interesting than others.
 *
 * For example, if we use a cache-file, that one may contain
 * revisions that were never used. They are never interesting.
 *
 * And sometimes we're only interested in "edge" commits, ie
 * places where the marking changes between parent and child.
 */
static int interesting(struct revision *rev)
{
	unsigned mask = marked(rev);

	if (!mask)
		return 0;
	if (show_edges) {
		struct parent *p = rev->parent;
		while (p) {
			if (mask != marked(p->parent))
				return 1;
			p = p->next;
		}
		return 0;
	}
184 185 186
	if (mask & basemask)
		return 0;

187 188 189 190 191
	return 1;
}

/*
 * Usage: rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id2>]
192 193 194 195 196 197 198 199
 *
 * The cache-file can be quite important for big trees. This is an
 * expensive operation if you have to walk the whole chain of
 * parents in a tree with a long revision history.
 */
int main(int argc, char **argv)
{
	int i;
200 201
	int nr = 0;
	unsigned char sha1[MAX_COMMITS][20];
202

203 204 205 206 207 208 209 210
	/*
	 * First - pick up all the revisions we can (both from
	 * caches and from commit file chains).
	 */
	for (i = 1; i < argc ; i++) {
		char *arg = argv[i];

		if (!strcmp(arg, "--cache")) {
211
			read_cache_file(argv[2]);
212 213 214 215 216 217
			i++;
			continue;
		}

		if (!strcmp(arg, "--edges")) {
			show_edges = 1;
218 219
			continue;
		}
220

221 222 223 224
		if (arg[0] == '^') {
			arg++;
			basemask |= 1<<nr;
		}
225 226 227 228
		if (nr >= MAX_COMMITS || get_sha1_hex(arg, sha1[nr]))
			usage("rev-tree [--edges] [--cache <cache-file>] <commit-id> [<commit-id>]");
		parse_commit(sha1[nr]);
		nr++;
229 230
	}

231 232 233 234 235 236 237 238 239
	/*
	 * Now we have the maximal tree. Walk the different sha files back to the root.
	 */
	for (i = 0; i < nr; i++)
		mark_sha1_path(lookup_rev(sha1[i]), 1 << i);

	/*
	 * Now print out the results..
	 */
240 241 242 243
	for (i = 0; i < nr_revs; i++) {
		struct revision *rev = revs[i];
		struct parent *p;

244 245 246
		if (!interesting(rev))
			continue;

247
		printf("%s:%d", sha1_to_hex(rev->sha1), marked(rev));
248 249
		p = rev->parent;
		while (p) {
250
			printf(" %s:%d", sha1_to_hex(p->parent->sha1), marked(p->parent));
251 252 253
			p = p->next;
		}
		printf("\n");
254 255 256
	}
	return 0;
}