builtin-rev-list.c 8.2 KB
Newer Older
1
#include "cache.h"
2
#include "refs.h"
3
#include "tag.h"
4
#include "commit.h"
5 6
#include "tree.h"
#include "blob.h"
J
Junio C Hamano 已提交
7
#include "tree-walk.h"
8
#include "diff.h"
9
#include "revision.h"
10
#include "builtin.h"
11

12
/* bits #0-15 in revision.h */
13

14
#define COUNTED		(1u<<16)
15

16
static const char rev_list_usage[] =
17 18 19 20 21 22 23
"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
"  limiting output:\n"
"    --max-count=nr\n"
"    --max-age=epoch\n"
"    --min-age=epoch\n"
"    --sparse\n"
"    --no-merges\n"
24
"    --remove-empty\n"
25 26 27
"    --all\n"
"  ordering output:\n"
"    --topo-order\n"
28
"    --date-order\n"
29 30
"  formatting output:\n"
"    --parents\n"
J
Junio C Hamano 已提交
31
"    --objects | --objects-edge\n"
32 33
"    --unpacked\n"
"    --header | --pretty\n"
34
"    --abbrev=nr | --no-abbrev\n"
J
Junio C Hamano 已提交
35
"    --abbrev-commit\n"
36 37 38
"  special purpose:\n"
"    --bisect"
;
39

40
static struct rev_info revs;
41

42
static int bisect_list = 0;
J
Junio C Hamano 已提交
43
static int show_timestamp = 0;
44
static int hdr_termination = 0;
L
Linus Torvalds 已提交
45
static const char *header_prefix;
46

47 48
static void show_commit(struct commit *commit)
{
J
Junio C Hamano 已提交
49 50
	if (show_timestamp)
		printf("%lu ", commit->date);
L
Linus Torvalds 已提交
51 52
	if (header_prefix)
		fputs(header_prefix, stdout);
J
Junio C Hamano 已提交
53 54
	if (commit->object.flags & BOUNDARY)
		putchar('-');
J
Junio C Hamano 已提交
55 56 57
	if (revs.abbrev_commit && revs.abbrev)
		fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
		      stdout);
J
Junio C Hamano 已提交
58 59
	else
		fputs(sha1_to_hex(commit->object.sha1), stdout);
60
	if (revs.parents) {
61 62
		struct commit_list *parents = commit->parents;
		while (parents) {
63
			struct object *o = &(parents->item->object);
64
			parents = parents->next;
65 66 67 68
			if (o->flags & TMP_MARK)
				continue;
			printf(" %s", sha1_to_hex(o->sha1));
			o->flags |= TMP_MARK;
69
		}
70 71 72 73 74 75 76 77
		/* 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;
78
	}
J
Junio C Hamano 已提交
79
	if (revs.commit_format == CMIT_FMT_ONELINE)
80 81 82 83
		putchar(' ');
	else
		putchar('\n');

J
Junio C Hamano 已提交
84
	if (revs.verbose_header) {
85
		static char pretty_header[16384];
J
Junio C Hamano 已提交
86 87
		pretty_print_commit(revs.commit_format, commit, ~0,
				    pretty_header, sizeof(pretty_header),
88
				    revs.abbrev, NULL, NULL);
89
		printf("%s%c", pretty_header, hdr_termination);
90 91
	}
	fflush(stdout);
92 93
}

94 95 96 97
static struct object_list **process_blob(struct blob *blob,
					 struct object_list **p,
					 struct name_path *path,
					 const char *name)
98 99 100
{
	struct object *obj = &blob->object;

101
	if (!revs.blob_objects)
102 103 104 105
		return p;
	if (obj->flags & (UNINTERESTING | SEEN))
		return p;
	obj->flags |= SEEN;
106
	name = strdup(name);
107
	return add_object(obj, p, path, name);
108 109
}

110 111 112 113
static struct object_list **process_tree(struct tree *tree,
					 struct object_list **p,
					 struct name_path *path,
					 const char *name)
114 115 116
{
	struct object *obj = &tree->object;
	struct tree_entry_list *entry;
117
	struct name_path me;
118

119
	if (!revs.tree_objects)
120 121 122 123 124 125
		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;
126
	name = strdup(name);
127 128 129 130
	p = add_object(obj, p, path, name);
	me.up = path;
	me.elem = name;
	me.elem_len = strlen(name);
131 132 133 134
	entry = tree->entries;
	tree->entries = NULL;
	while (entry) {
		struct tree_entry_list *next = entry->next;
135
		if (entry->directory)
136
			p = process_tree(entry->item.tree, p, &me, entry->name);
137
		else
138
			p = process_blob(entry->item.blob, p, &me, entry->name);
139
		free(entry->name);
140 141
		free(entry);
		entry = next;
142 143 144 145
	}
	return p;
}

146
static void show_commit_list(struct rev_info *revs)
147
{
148
	struct commit *commit;
149
	struct object_list *objects = NULL, **p = &objects, *pending;
150

151
	while ((commit = get_revision(revs)) != NULL) {
152
		p = process_tree(commit->tree, p, NULL, "");
153
		show_commit(commit);
154
	}
155
	for (pending = revs->pending_objects; pending; pending = pending->next) {
156 157 158 159 160 161
		struct object *obj = pending->item;
		const char *name = pending->name;
		if (obj->flags & (UNINTERESTING | SEEN))
			continue;
		if (obj->type == tag_type) {
			obj->flags |= SEEN;
162
			p = add_object(obj, p, NULL, name);
163 164 165
			continue;
		}
		if (obj->type == tree_type) {
166
			p = process_tree((struct tree *)obj, p, NULL, name);
167 168 169
			continue;
		}
		if (obj->type == blob_type) {
170
			p = process_blob((struct blob *)obj, p, NULL, name);
171 172 173 174
			continue;
		}
		die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
	}
175
	while (objects) {
176 177
		/* An object with name "foo\n0000000..." can be used to
		 * confuse downstream git-pack-objects very badly.
J
Junio C Hamano 已提交
178 179 180 181 182 183 184 185 186
		 */
		const char *ep = strchr(objects->name, '\n');
		if (ep) {
			printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
			       (int) (ep - objects->name),
			       objects->name);
		}
		else
			printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
187 188 189 190
		objects = objects->next;
	}
}

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
/*
 * 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;
208
		if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
209
			nr++;
210 211 212 213 214 215 216 217 218 219 220
		commit->object.flags |= COUNTED;
		p = commit->parents;
		entry = p;
		if (p) {
			p = p->next;
			while (p) {
				nr += count_distance(p);
				p = p->next;
			}
		}
	}
221

222 223 224
	return nr;
}

225
static void clear_distance(struct commit_list *list)
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
{
	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) {
242
		if (!revs.prune_fn || (p->item->object.flags & TREECHANGE))
243
			nr++;
244 245 246 247 248
		p = p->next;
	}
	closest = 0;
	best = list;

249 250 251
	for (p = list; p; p = p->next) {
		int distance;

252
		if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
253 254 255
			continue;

		distance = count_distance(p);
256 257 258 259 260 261 262 263 264 265 266 267 268
		clear_distance(list);
		if (nr - distance < distance)
			distance = nr - distance;
		if (distance > closest) {
			best = p;
			closest = distance;
		}
	}
	if (best)
		best->next = NULL;
	return best;
}

J
Junio C Hamano 已提交
269 270 271 272 273 274 275 276 277
static void mark_edge_parents_uninteresting(struct commit *commit)
{
	struct commit_list *parents;

	for (parents = commit->parents; parents; parents = parents->next) {
		struct commit *parent = parents->item;
		if (!(parent->object.flags & UNINTERESTING))
			continue;
		mark_tree_uninteresting(parent->tree);
278
		if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
279
			parent->object.flags |= SHOWN;
J
Junio C Hamano 已提交
280
			printf("-%s\n", sha1_to_hex(parent->object.sha1));
281
		}
J
Junio C Hamano 已提交
282 283 284
	}
}

285 286 287
static void mark_edges_uninteresting(struct commit_list *list)
{
	for ( ; list; list = list->next) {
J
Junio C Hamano 已提交
288
		struct commit *commit = list->item;
289

J
Junio C Hamano 已提交
290 291 292
		if (commit->object.flags & UNINTERESTING) {
			mark_tree_uninteresting(commit->tree);
			continue;
293
		}
J
Junio C Hamano 已提交
294
		mark_edge_parents_uninteresting(commit);
295 296 297
	}
}

298
int cmd_rev_list(int argc, const char **argv, char **envp)
299
{
300
	struct commit_list *list;
301
	int i;
302

J
Junio C Hamano 已提交
303 304 305
	init_revisions(&revs);
	revs.abbrev = 0;
	revs.commit_format = CMIT_FMT_UNSPECIFIED;
306
	argc = setup_revisions(argc, argv, &revs, NULL);
307

308
	for (i = 1 ; i < argc; i++) {
309
		const char *arg = argv[i];
310

311
		if (!strcmp(arg, "--header")) {
J
Junio C Hamano 已提交
312
			revs.verbose_header = 1;
313 314
			continue;
		}
J
Junio C Hamano 已提交
315 316 317 318
		if (!strcmp(arg, "--timestamp")) {
			show_timestamp = 1;
			continue;
		}
319 320 321 322
		if (!strcmp(arg, "--bisect")) {
			bisect_list = 1;
			continue;
		}
323
		usage(rev_list_usage);
324

325
	}
J
Junio C Hamano 已提交
326 327 328 329
	if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
		/* The command line has a --pretty  */
		hdr_termination = '\n';
		if (revs.commit_format == CMIT_FMT_ONELINE)
L
Linus Torvalds 已提交
330
			header_prefix = "";
J
Junio C Hamano 已提交
331
		else
L
Linus Torvalds 已提交
332
			header_prefix = "commit ";
J
Junio C Hamano 已提交
333
	}
334 335 336
	else if (revs.verbose_header)
		/* Only --header was specified */
		revs.commit_format = CMIT_FMT_RAW;
337

338 339
	list = revs.commits;

J
Junio C Hamano 已提交
340 341 342 343
	if ((!list &&
	     (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
	      !revs.pending_objects)) ||
	    revs.diff)
344 345
		usage(rev_list_usage);

J
Junio C Hamano 已提交
346
	save_commit_buffer = revs.verbose_header;
347
	track_object_refs = 0;
348 349
	if (bisect_list)
		revs.limited = 1;
350

351 352 353 354 355 356
	prepare_revision_walk(&revs);
	if (revs.tree_objects)
		mark_edges_uninteresting(revs.commits);

	if (bisect_list)
		revs.commits = find_bisection(revs.commits);
357

358
	show_commit_list(&revs);
359

360 361
	return 0;
}