1#include "cache.h"
2#include "refs.h"
3#include "tag.h"
4#include "commit.h"
5#include "tree.h"
6#include "blob.h"
7#include "tree-walk.h"
8#include "revision.h"
9
10/* bits #0-5 in revision.h */
11
12#define COUNTED (1u<<6)
13
14static const char rev_list_usage[] =
15"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
16" limiting output:\n"
17" --max-count=nr\n"
18" --max-age=epoch\n"
19" --min-age=epoch\n"
20" --sparse\n"
21" --no-merges\n"
22" --remove-empty\n"
23" --all\n"
24" ordering output:\n"
25" --topo-order\n"
26" --date-order\n"
27" formatting output:\n"
28" --parents\n"
29" --objects | --objects-edge\n"
30" --unpacked\n"
31" --header | --pretty\n"
32" --abbrev=nr | --no-abbrev\n"
33" special purpose:\n"
34" --bisect"
35;
36
37struct rev_info revs;
38
39static int bisect_list = 0;
40static int verbose_header = 0;
41static int abbrev = DEFAULT_ABBREV;
42static int show_timestamp = 0;
43static int hdr_termination = 0;
44static const char *commit_prefix = "";
45static enum cmit_fmt commit_format = CMIT_FMT_RAW;
46
47static void show_commit(struct commit *commit)
48{
49 if (show_timestamp)
50 printf("%lu ", commit->date);
51 if (commit_prefix[0])
52 fputs(commit_prefix, stdout);
53 if (commit->object.flags & BOUNDARY)
54 putchar('-');
55 fputs(sha1_to_hex(commit->object.sha1), stdout);
56 if (revs.parents) {
57 struct commit_list *parents = commit->parents;
58 while (parents) {
59 struct object *o = &(parents->item->object);
60 parents = parents->next;
61 if (o->flags & TMP_MARK)
62 continue;
63 printf(" %s", sha1_to_hex(o->sha1));
64 o->flags |= TMP_MARK;
65 }
66 /* TMP_MARK is a general purpose flag that can
67 * be used locally, but the user should clean
68 * things up after it is done with them.
69 */
70 for (parents = commit->parents;
71 parents;
72 parents = parents->next)
73 parents->item->object.flags &= ~TMP_MARK;
74 }
75 if (commit_format == CMIT_FMT_ONELINE)
76 putchar(' ');
77 else
78 putchar('\n');
79
80 if (verbose_header) {
81 static char pretty_header[16384];
82 pretty_print_commit(commit_format, commit, ~0, pretty_header, sizeof(pretty_header), abbrev);
83 printf("%s%c", pretty_header, hdr_termination);
84 }
85 fflush(stdout);
86}
87
88static struct object_list **process_blob(struct blob *blob,
89 struct object_list **p,
90 struct name_path *path,
91 const char *name)
92{
93 struct object *obj = &blob->object;
94
95 if (!revs.blob_objects)
96 return p;
97 if (obj->flags & (UNINTERESTING | SEEN))
98 return p;
99 obj->flags |= SEEN;
100 return add_object(obj, p, path, name);
101}
102
103static struct object_list **process_tree(struct tree *tree,
104 struct object_list **p,
105 struct name_path *path,
106 const char *name)
107{
108 struct object *obj = &tree->object;
109 struct tree_entry_list *entry;
110 struct name_path me;
111
112 if (!revs.tree_objects)
113 return p;
114 if (obj->flags & (UNINTERESTING | SEEN))
115 return p;
116 if (parse_tree(tree) < 0)
117 die("bad tree object %s", sha1_to_hex(obj->sha1));
118 obj->flags |= SEEN;
119 p = add_object(obj, p, path, name);
120 me.up = path;
121 me.elem = name;
122 me.elem_len = strlen(name);
123 entry = tree->entries;
124 tree->entries = NULL;
125 while (entry) {
126 struct tree_entry_list *next = entry->next;
127 if (entry->directory)
128 p = process_tree(entry->item.tree, p, &me, entry->name);
129 else
130 p = process_blob(entry->item.blob, p, &me, entry->name);
131 free(entry);
132 entry = next;
133 }
134 return p;
135}
136
137static void show_commit_list(struct rev_info *revs)
138{
139 struct commit *commit;
140 struct object_list *objects = NULL, **p = &objects, *pending;
141
142 while ((commit = get_revision(revs)) != NULL) {
143 p = process_tree(commit->tree, p, NULL, "");
144 show_commit(commit);
145 }
146 for (pending = revs->pending_objects; pending; pending = pending->next) {
147 struct object *obj = pending->item;
148 const char *name = pending->name;
149 if (obj->flags & (UNINTERESTING | SEEN))
150 continue;
151 if (obj->type == tag_type) {
152 obj->flags |= SEEN;
153 p = add_object(obj, p, NULL, name);
154 continue;
155 }
156 if (obj->type == tree_type) {
157 p = process_tree((struct tree *)obj, p, NULL, name);
158 continue;
159 }
160 if (obj->type == blob_type) {
161 p = process_blob((struct blob *)obj, p, NULL, name);
162 continue;
163 }
164 die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
165 }
166 while (objects) {
167 /* An object with name "foo\n0000000..." can be used to
168 * confuse downstream git-pack-objects very badly.
169 */
170 const char *ep = strchr(objects->name, '\n');
171 if (ep) {
172 printf("%s %.*s\n", sha1_to_hex(objects->item->sha1),
173 (int) (ep - objects->name),
174 objects->name);
175 }
176 else
177 printf("%s %s\n", sha1_to_hex(objects->item->sha1), objects->name);
178 objects = objects->next;
179 }
180}
181
182/*
183 * This is a truly stupid algorithm, but it's only
184 * used for bisection, and we just don't care enough.
185 *
186 * We care just barely enough to avoid recursing for
187 * non-merge entries.
188 */
189static int count_distance(struct commit_list *entry)
190{
191 int nr = 0;
192
193 while (entry) {
194 struct commit *commit = entry->item;
195 struct commit_list *p;
196
197 if (commit->object.flags & (UNINTERESTING | COUNTED))
198 break;
199 if (!revs.prune_fn || (commit->object.flags & TREECHANGE))
200 nr++;
201 commit->object.flags |= COUNTED;
202 p = commit->parents;
203 entry = p;
204 if (p) {
205 p = p->next;
206 while (p) {
207 nr += count_distance(p);
208 p = p->next;
209 }
210 }
211 }
212
213 return nr;
214}
215
216static void clear_distance(struct commit_list *list)
217{
218 while (list) {
219 struct commit *commit = list->item;
220 commit->object.flags &= ~COUNTED;
221 list = list->next;
222 }
223}
224
225static struct commit_list *find_bisection(struct commit_list *list)
226{
227 int nr, closest;
228 struct commit_list *p, *best;
229
230 nr = 0;
231 p = list;
232 while (p) {
233 if (!revs.prune_fn || (p->item->object.flags & TREECHANGE))
234 nr++;
235 p = p->next;
236 }
237 closest = 0;
238 best = list;
239
240 for (p = list; p; p = p->next) {
241 int distance;
242
243 if (revs.prune_fn && !(p->item->object.flags & TREECHANGE))
244 continue;
245
246 distance = count_distance(p);
247 clear_distance(list);
248 if (nr - distance < distance)
249 distance = nr - distance;
250 if (distance > closest) {
251 best = p;
252 closest = distance;
253 }
254 }
255 if (best)
256 best->next = NULL;
257 return best;
258}
259
260static void mark_edge_parents_uninteresting(struct commit *commit)
261{
262 struct commit_list *parents;
263
264 for (parents = commit->parents; parents; parents = parents->next) {
265 struct commit *parent = parents->item;
266 if (!(parent->object.flags & UNINTERESTING))
267 continue;
268 mark_tree_uninteresting(parent->tree);
269 if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
270 parent->object.flags |= SHOWN;
271 printf("-%s\n", sha1_to_hex(parent->object.sha1));
272 }
273 }
274}
275
276static void mark_edges_uninteresting(struct commit_list *list)
277{
278 for ( ; list; list = list->next) {
279 struct commit *commit = list->item;
280
281 if (commit->object.flags & UNINTERESTING) {
282 mark_tree_uninteresting(commit->tree);
283 continue;
284 }
285 mark_edge_parents_uninteresting(commit);
286 }
287}
288
289int main(int argc, const char **argv)
290{
291 struct commit_list *list;
292 int i;
293
294 argc = setup_revisions(argc, argv, &revs, NULL);
295
296 for (i = 1 ; i < argc; i++) {
297 const char *arg = argv[i];
298
299 /* accept -<digit>, like traditilnal "head" */
300 if ((*arg == '-') && isdigit(arg[1])) {
301 revs.max_count = atoi(arg + 1);
302 continue;
303 }
304 if (!strcmp(arg, "-n")) {
305 if (++i >= argc)
306 die("-n requires an argument");
307 revs.max_count = atoi(argv[i]);
308 continue;
309 }
310 if (!strncmp(arg,"-n",2)) {
311 revs.max_count = atoi(arg + 2);
312 continue;
313 }
314 if (!strcmp(arg, "--header")) {
315 verbose_header = 1;
316 continue;
317 }
318 if (!strcmp(arg, "--no-abbrev")) {
319 abbrev = 0;
320 continue;
321 }
322 if (!strncmp(arg, "--abbrev=", 9)) {
323 abbrev = strtoul(arg + 9, NULL, 10);
324 if (abbrev && abbrev < MINIMUM_ABBREV)
325 abbrev = MINIMUM_ABBREV;
326 else if (40 < abbrev)
327 abbrev = 40;
328 continue;
329 }
330 if (!strncmp(arg, "--pretty", 8)) {
331 commit_format = get_commit_format(arg+8);
332 verbose_header = 1;
333 hdr_termination = '\n';
334 if (commit_format == CMIT_FMT_ONELINE)
335 commit_prefix = "";
336 else
337 commit_prefix = "commit ";
338 continue;
339 }
340 if (!strcmp(arg, "--timestamp")) {
341 show_timestamp = 1;
342 continue;
343 }
344 if (!strcmp(arg, "--bisect")) {
345 bisect_list = 1;
346 continue;
347 }
348 usage(rev_list_usage);
349
350 }
351
352 list = revs.commits;
353
354 if (!list &&
355 (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
356 usage(rev_list_usage);
357
358 save_commit_buffer = verbose_header;
359 track_object_refs = 0;
360
361 prepare_revision_walk(&revs);
362 if (revs.tree_objects)
363 mark_edges_uninteresting(revs.commits);
364
365 if (bisect_list)
366 revs.commits = find_bisection(revs.commits);
367
368 show_commit_list(&revs);
369
370 return 0;
371}