rev-list.c 11.1 KB
Newer Older
1
#include "cache.h"
2
#include "tag.h"
3
#include "commit.h"
4 5
#include "tree.h"
#include "blob.h"
6
#include "epoch.h"
7

8 9
#define SEEN		(1u << 0)
#define INTERESTING	(1u << 1)
10
#define COUNTED		(1u << 2)
11 12
#define SHOWN		(1u << 3)
#define DUPCHECK	(1u << 4)
13

14 15 16 17 18
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"
19 20 21
		      "  --bisect\n"
		      "  --objects\n"
		      "  --unpacked\n"
22
		      "  --header\n"
23 24
		      "  --pretty\n"
		      "  --merge-order [ --show-breaks ]";
25

26
static int unpacked = 0;
27
static int bisect_list = 0;
28
static int tag_objects = 0;
29 30
static int tree_objects = 0;
static int blob_objects = 0;
31 32 33 34 35 36 37
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;
38
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
39 40
static int merge_order = 0;
static int show_breaks = 0;
41
static int stop_traversal = 0;
42 43 44

static void show_commit(struct commit *commit)
{
45
	commit->object.flags |= SHOWN;
46 47 48 49 50 51 52 53
	if (show_breaks) {
		prefix = "| ";
		if (commit->object.flags & DISCONTINUITY) {
			prefix = "^ ";     
		} else if (commit->object.flags & BOUNDARY) {
			prefix = "= ";
		} 
        }        		
54 55 56 57 58 59 60 61 62 63
	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) {
64 65 66
		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);
67 68
	}
	fflush(stdout);
69 70 71 72
}

static int filter_commit(struct commit * commit)
{
73 74
	if (merge_order && stop_traversal && commit->object.flags & BOUNDARY)
		return STOP;
75
	if (commit->object.flags & (UNINTERESTING|SHOWN))
76 77 78
		return CONTINUE;
	if (min_age != -1 && (commit->date > min_age))
		return CONTINUE;
79 80 81 82 83 84 85 86
	if (max_age != -1 && (commit->date < max_age)) {
		if (!merge_order)
			return STOP;
		else {
			stop_traversal = 1;
			return CONTINUE;
		}
	}
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
	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;
102
	}
103 104 105 106

	show_commit(commit);

	return CONTINUE;
107 108
}

109
static struct object_list **add_object(struct object *obj, struct object_list **p, const char *name)
110 111 112
{
	struct object_list *entry = xmalloc(sizeof(*entry));
	entry->item = obj;
113
	entry->next = *p;
114
	entry->name = name;
115 116 117 118
	*p = entry;
	return &entry->next;
}

119
static struct object_list **process_blob(struct blob *blob, struct object_list **p, const char *name)
120 121 122 123 124 125 126 127
{
	struct object *obj = &blob->object;

	if (!blob_objects)
		return p;
	if (obj->flags & (UNINTERESTING | SEEN))
		return p;
	obj->flags |= SEEN;
128
	return add_object(obj, p, name);
129 130
}

131
static struct object_list **process_tree(struct tree *tree, struct object_list **p, const char *name)
132 133 134 135 136 137 138 139 140 141 142
{
	struct object *obj = &tree->object;
	struct tree_entry_list *entry;

	if (!tree_objects)
		return p;
	if (obj->flags & (UNINTERESTING | SEEN))
		return p;
	if (parse_tree(tree) < 0)
		die("bad tree object %s", sha1_to_hex(obj->sha1));
	obj->flags |= SEEN;
143
	p = add_object(obj, p, name);
144 145
	for (entry = tree->entries ; entry ; entry = entry->next) {
		if (entry->directory)
146
			p = process_tree(entry->item.tree, p, entry->name);
147
		else
148
			p = process_blob(entry->item.blob, p, entry->name);
149 150 151 152
	}
	return p;
}

153 154
static struct object_list *pending_objects = NULL;

155 156
static void show_commit_list(struct commit_list *list)
{
157
	struct object_list *objects = NULL, **p = &objects, *pending;
158 159 160
	while (list) {
		struct commit *commit = pop_most_recent_commit(&list, SEEN);

161
		p = process_tree(commit->tree, p, "");
162
		if (process_commit(commit) == STOP)
163 164
			break;
	}
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
	for (pending = pending_objects; pending; pending = pending->next) {
		struct object *obj = pending->item;
		const char *name = pending->name;
		if (obj->flags & (UNINTERESTING | SEEN))
			continue;
		if (obj->type == tag_type) {
			obj->flags |= SEEN;
			p = add_object(obj, p, name);
			continue;
		}
		if (obj->type == tree_type) {
			p = process_tree((struct tree *)obj, p, name);
			continue;
		}
		if (obj->type == blob_type) {
			p = process_blob((struct blob *)obj, p, name);
			continue;
		}
		die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
	}
185
	while (objects) {
186
		printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
		objects = objects->next;
	}
}

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 (parse_tree(tree) < 0)
		die("bad tree %s", sha1_to_hex(obj->sha1));
	entry = tree->entries;
	while (entry) {
		if (entry->directory)
			mark_tree_uninteresting(entry->item.tree);
		else
			mark_blob_uninteresting(entry->item.blob);
		entry = entry->next;
	}
220 221
}

222 223 224 225
static void mark_parents_uninteresting(struct commit *commit)
{
	struct commit_list *parents = commit->parents;

226 227
	if (tree_objects)
		mark_tree_uninteresting(commit->tree);
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	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;
}

247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
/*
 * This is a truly stupid algorithm, but it's only
 * used for bisection, and we just don't care enough.
 *
 * We care just barely enough to avoid recursing for
 * non-merge entries.
 */
static int count_distance(struct commit_list *entry)
{
	int nr = 0;

	while (entry) {
		struct commit *commit = entry->item;
		struct commit_list *p;

		if (commit->object.flags & (UNINTERESTING | COUNTED))
			break;
		nr++;
		commit->object.flags |= COUNTED;
		p = commit->parents;
		entry = p;
		if (p) {
			p = p->next;
			while (p) {
				nr += count_distance(p);
				p = p->next;
			}
		}
	}
	return nr;
}

279
static void clear_distance(struct commit_list *list)
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
{
	while (list) {
		struct commit *commit = list->item;
		commit->object.flags &= ~COUNTED;
		list = list->next;
	}
}

static struct commit_list *find_bisection(struct commit_list *list)
{
	int nr, closest;
	struct commit_list *p, *best;

	nr = 0;
	p = list;
	while (p) {
		nr++;
		p = p->next;
	}
	closest = 0;
	best = list;

	p = list;
	while (p) {
		int distance = count_distance(p);
		clear_distance(list);
		if (nr - distance < distance)
			distance = nr - distance;
		if (distance > closest) {
			best = p;
			closest = distance;
		}
		p = p->next;
	}
	if (best)
		best->next = NULL;
	return best;
}

L
Linus Torvalds 已提交
319
static struct commit_list *limit_list(struct commit_list *list)
320 321 322
{
	struct commit_list *newlist = NULL;
	struct commit_list **p = &newlist;
323
	while (list) {
324 325 326
		struct commit *commit = pop_most_recent_commit(&list, SEEN);
		struct object *obj = &commit->object;

327 328
		if (unpacked && has_sha1_pack(obj->sha1))
			obj->flags |= UNINTERESTING;
329
		if (obj->flags & UNINTERESTING) {
330 331 332 333 334 335
			mark_parents_uninteresting(commit);
			if (everybody_uninteresting(list))
				break;
			continue;
		}
		p = &commit_list_insert(commit, p)->next;
336
	}
337 338
	if (bisect_list)
		newlist = find_bisection(newlist);
339 340 341
	return newlist;
}

342 343 344 345 346
static void add_pending_object(struct object *obj, const char *name)
{
	add_object(obj, &pending_objects, name);
}

347 348 349
static struct commit *get_commit_reference(const char *name, unsigned int flags)
{
	unsigned char sha1[20];
350
	struct object *object;
351 352 353

	if (get_sha1(name, sha1))
		usage(rev_list_usage);
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
	object = parse_object(sha1);
	if (!object)
		die("bad object %s", name);

	/*
	 * Tag object? Look what it points to..
	 */
	if (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 = tag->tagged;
	}

	/*
	 * 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);
		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)
388
			return NULL;
389 390 391 392 393 394 395 396 397 398 399 400 401 402
		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)
403
			return NULL;
404 405 406 407 408 409 410 411
		if (flags & UNINTERESTING) {
			mark_blob_uninteresting(blob);
			return NULL;
		}
		add_pending_object(object, "");
		return NULL;
	}
	die("%s is unknown object", name);
412 413
}

414 415 416
int main(int argc, char **argv)
{
	struct commit_list *list = NULL;
417
	struct commit_list *(*insert)(struct commit *, struct commit_list **);
418
	int i, limited = 0;
419

420
	insert = insert_by_date;
421
	for (i = 1 ; i < argc; i++) {
422
		int flags;
423
		char *arg = argv[i];
424
		struct commit *commit;
425 426 427

		if (!strncmp(arg, "--max-count=", 12)) {
			max_count = atoi(arg + 12);
428 429 430
			continue;
		}
		if (!strncmp(arg, "--max-age=", 10)) {
431
			max_age = atoi(arg + 10);
432 433 434
			continue;
		}
		if (!strncmp(arg, "--min-age=", 10)) {
435
			min_age = atoi(arg + 10);
436
			continue;
437
		}
438 439 440 441
		if (!strcmp(arg, "--header")) {
			verbose_header = 1;
			continue;
		}
442 443
		if (!strncmp(arg, "--pretty", 8)) {
			commit_format = get_commit_format(arg+8);
444 445 446 447 448
			verbose_header = 1;
			hdr_termination = '\n';
			prefix = "commit ";
			continue;
		}
449 450 451 452
		if (!strcmp(arg, "--parents")) {
			show_parents = 1;
			continue;
		}
453 454 455 456
		if (!strcmp(arg, "--bisect")) {
			bisect_list = 1;
			continue;
		}
457
		if (!strcmp(arg, "--objects")) {
458
			tag_objects = 1;
459 460 461 462
			tree_objects = 1;
			blob_objects = 1;
			continue;
		}
463 464 465 466 467
		if (!strcmp(arg, "--unpacked")) {
			unpacked = 1;
			limited = 1;
			continue;
		}
468
		if (!strcmp(arg, "--merge-order")) {
469
		        merge_order = 1;
470
		        insert = commit_list_insert;
471 472
			continue;
		}
473
		if (!strcmp(arg, "--show-breaks")) {
474 475 476
			show_breaks = 1;
			continue;
		}
477

478 479 480 481 482 483
		flags = 0;
		if (*arg == '^') {
			flags = UNINTERESTING;
			arg++;
			limited = 1;
		}
484
		if (show_breaks && !merge_order)
485
			usage(rev_list_usage);
486 487 488
		commit = get_commit_reference(arg, flags);
		if (!commit)
			continue;
489 490 491
		if (commit->object.flags & DUPCHECK)
			continue;
		commit->object.flags |= DUPCHECK;
492
		insert(commit, &list);
493 494
	}

495
	if (!merge_order) {		
496
	        if (limited)
497 498 499 500 501 502 503
			list = limit_list(list);
		show_commit_list(list);
	} else {
		if (sort_list_in_merge_order(list, &process_commit)) {
			  die("merge order sort failed\n");
		}
	}
504

505 506
	return 0;
}