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