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