Merge branch 'jk/asciidoctor-section-heading-markup-fix'
[git] / builtin / name-rev.c
1 #include "builtin.h"
2 #include "cache.h"
3 #include "commit.h"
4 #include "tag.h"
5 #include "refs.h"
6 #include "parse-options.h"
7 #include "sha1-lookup.h"
8
9 #define CUTOFF_DATE_SLOP 86400 /* one day */
10
11 typedef struct rev_name {
12         const char *tip_name;
13         int generation;
14         int distance;
15 } rev_name;
16
17 static long cutoff = LONG_MAX;
18
19 /* How many generations are maximally preferred over _one_ merge traversal? */
20 #define MERGE_TRAVERSAL_WEIGHT 65535
21
22 static void name_rev(struct commit *commit,
23                 const char *tip_name, int generation, int distance,
24                 int deref)
25 {
26         struct rev_name *name = (struct rev_name *)commit->util;
27         struct commit_list *parents;
28         int parent_number = 1;
29
30         parse_commit(commit);
31
32         if (commit->date < cutoff)
33                 return;
34
35         if (deref) {
36                 tip_name = xstrfmt("%s^0", tip_name);
37
38                 if (generation)
39                         die("generation: %d, but deref?", generation);
40         }
41
42         if (name == NULL) {
43                 name = xmalloc(sizeof(rev_name));
44                 commit->util = name;
45                 goto copy_data;
46         } else if (name->distance > distance) {
47 copy_data:
48                 name->tip_name = tip_name;
49                 name->generation = generation;
50                 name->distance = distance;
51         } else
52                 return;
53
54         for (parents = commit->parents;
55                         parents;
56                         parents = parents->next, parent_number++) {
57                 if (parent_number > 1) {
58                         int len = strlen(tip_name);
59                         char *new_name = xmalloc(len +
60                                 1 + decimal_length(generation) +  /* ~<n> */
61                                 1 + 2 +                           /* ^NN */
62                                 1);
63
64                         if (len > 2 && !strcmp(tip_name + len - 2, "^0"))
65                                 len -= 2;
66                         if (generation > 0)
67                                 sprintf(new_name, "%.*s~%d^%d", len, tip_name,
68                                                 generation, parent_number);
69                         else
70                                 sprintf(new_name, "%.*s^%d", len, tip_name,
71                                                 parent_number);
72
73                         name_rev(parents->item, new_name, 0,
74                                 distance + MERGE_TRAVERSAL_WEIGHT, 0);
75                 } else {
76                         name_rev(parents->item, tip_name, generation + 1,
77                                 distance + 1, 0);
78                 }
79         }
80 }
81
82 static int subpath_matches(const char *path, const char *filter)
83 {
84         const char *subpath = path;
85
86         while (subpath) {
87                 if (!wildmatch(filter, subpath, 0, NULL))
88                         return subpath - path;
89                 subpath = strchr(subpath, '/');
90                 if (subpath)
91                         subpath++;
92         }
93         return -1;
94 }
95
96 static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous)
97 {
98         if (shorten_unambiguous)
99                 refname = shorten_unambiguous_ref(refname, 0);
100         else if (starts_with(refname, "refs/heads/"))
101                 refname = refname + 11;
102         else if (starts_with(refname, "refs/"))
103                 refname = refname + 5;
104         return refname;
105 }
106
107 struct name_ref_data {
108         int tags_only;
109         int name_only;
110         const char *ref_filter;
111 };
112
113 static struct tip_table {
114         struct tip_table_entry {
115                 unsigned char sha1[20];
116                 const char *refname;
117         } *table;
118         int nr;
119         int alloc;
120         int sorted;
121 } tip_table;
122
123 static void add_to_tip_table(const unsigned char *sha1, const char *refname,
124                              int shorten_unambiguous)
125 {
126         refname = name_ref_abbrev(refname, shorten_unambiguous);
127
128         ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc);
129         hashcpy(tip_table.table[tip_table.nr].sha1, sha1);
130         tip_table.table[tip_table.nr].refname = xstrdup(refname);
131         tip_table.nr++;
132         tip_table.sorted = 0;
133 }
134
135 static int tipcmp(const void *a_, const void *b_)
136 {
137         const struct tip_table_entry *a = a_, *b = b_;
138         return hashcmp(a->sha1, b->sha1);
139 }
140
141 static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data)
142 {
143         struct object *o = parse_object(oid->hash);
144         struct name_ref_data *data = cb_data;
145         int can_abbreviate_output = data->tags_only && data->name_only;
146         int deref = 0;
147
148         if (data->tags_only && !starts_with(path, "refs/tags/"))
149                 return 0;
150
151         if (data->ref_filter) {
152                 switch (subpath_matches(path, data->ref_filter)) {
153                 case -1: /* did not match */
154                         return 0;
155                 case 0:  /* matched fully */
156                         break;
157                 default: /* matched subpath */
158                         can_abbreviate_output = 1;
159                         break;
160                 }
161         }
162
163         add_to_tip_table(oid->hash, path, can_abbreviate_output);
164
165         while (o && o->type == OBJ_TAG) {
166                 struct tag *t = (struct tag *) o;
167                 if (!t->tagged)
168                         break; /* broken repository */
169                 o = parse_object(t->tagged->sha1);
170                 deref = 1;
171         }
172         if (o && o->type == OBJ_COMMIT) {
173                 struct commit *commit = (struct commit *)o;
174
175                 path = name_ref_abbrev(path, can_abbreviate_output);
176                 name_rev(commit, xstrdup(path), 0, 0, deref);
177         }
178         return 0;
179 }
180
181 static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
182 {
183         struct tip_table_entry *table = table_;
184         return table[ix].sha1;
185 }
186
187 static const char *get_exact_ref_match(const struct object *o)
188 {
189         int found;
190
191         if (!tip_table.table || !tip_table.nr)
192                 return NULL;
193
194         if (!tip_table.sorted) {
195                 qsort(tip_table.table, tip_table.nr, sizeof(*tip_table.table),
196                       tipcmp);
197                 tip_table.sorted = 1;
198         }
199
200         found = sha1_pos(o->sha1, tip_table.table, tip_table.nr,
201                          nth_tip_table_ent);
202         if (0 <= found)
203                 return tip_table.table[found].refname;
204         return NULL;
205 }
206
207 /* returns a static buffer */
208 static const char *get_rev_name(const struct object *o)
209 {
210         static char buffer[1024];
211         struct rev_name *n;
212         struct commit *c;
213
214         if (o->type != OBJ_COMMIT)
215                 return get_exact_ref_match(o);
216         c = (struct commit *) o;
217         n = c->util;
218         if (!n)
219                 return NULL;
220
221         if (!n->generation)
222                 return n->tip_name;
223         else {
224                 int len = strlen(n->tip_name);
225                 if (len > 2 && !strcmp(n->tip_name + len - 2, "^0"))
226                         len -= 2;
227                 snprintf(buffer, sizeof(buffer), "%.*s~%d", len, n->tip_name,
228                                 n->generation);
229
230                 return buffer;
231         }
232 }
233
234 static void show_name(const struct object *obj,
235                       const char *caller_name,
236                       int always, int allow_undefined, int name_only)
237 {
238         const char *name;
239         const unsigned char *sha1 = obj->sha1;
240
241         if (!name_only)
242                 printf("%s ", caller_name ? caller_name : sha1_to_hex(sha1));
243         name = get_rev_name(obj);
244         if (name)
245                 printf("%s\n", name);
246         else if (allow_undefined)
247                 printf("undefined\n");
248         else if (always)
249                 printf("%s\n", find_unique_abbrev(sha1, DEFAULT_ABBREV));
250         else
251                 die("cannot describe '%s'", sha1_to_hex(sha1));
252 }
253
254 static char const * const name_rev_usage[] = {
255         N_("git name-rev [<options>] <commit>..."),
256         N_("git name-rev [<options>] --all"),
257         N_("git name-rev [<options>] --stdin"),
258         NULL
259 };
260
261 static void name_rev_line(char *p, struct name_ref_data *data)
262 {
263         int forty = 0;
264         char *p_start;
265         for (p_start = p; *p; p++) {
266 #define ishex(x) (isdigit((x)) || ((x) >= 'a' && (x) <= 'f'))
267                 if (!ishex(*p))
268                         forty = 0;
269                 else if (++forty == 40 &&
270                          !ishex(*(p+1))) {
271                         unsigned char sha1[40];
272                         const char *name = NULL;
273                         char c = *(p+1);
274                         int p_len = p - p_start + 1;
275
276                         forty = 0;
277
278                         *(p+1) = 0;
279                         if (!get_sha1(p - 39, sha1)) {
280                                 struct object *o =
281                                         lookup_object(sha1);
282                                 if (o)
283                                         name = get_rev_name(o);
284                         }
285                         *(p+1) = c;
286
287                         if (!name)
288                                 continue;
289
290                         if (data->name_only)
291                                 printf("%.*s%s", p_len - 40, p_start, name);
292                         else
293                                 printf("%.*s (%s)", p_len, p_start, name);
294                         p_start = p + 1;
295                 }
296         }
297
298         /* flush */
299         if (p_start != p)
300                 fwrite(p_start, p - p_start, 1, stdout);
301 }
302
303 int cmd_name_rev(int argc, const char **argv, const char *prefix)
304 {
305         struct object_array revs = OBJECT_ARRAY_INIT;
306         int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0;
307         struct name_ref_data data = { 0, 0, NULL };
308         struct option opts[] = {
309                 OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")),
310                 OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")),
311                 OPT_STRING(0, "refs", &data.ref_filter, N_("pattern"),
312                                    N_("only use refs matching <pattern>")),
313                 OPT_GROUP(""),
314                 OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")),
315                 OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")),
316                 OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")),
317                 OPT_BOOL(0, "always",     &always,
318                            N_("show abbreviated commit object as fallback")),
319                 {
320                         /* A Hidden OPT_BOOL */
321                         OPTION_SET_INT, 0, "peel-tag", &peel_tag, NULL,
322                         N_("dereference tags in the input (internal use)"),
323                         PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, NULL, 1,
324                 },
325                 OPT_END(),
326         };
327
328         git_config(git_default_config, NULL);
329         argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0);
330         if (all + transform_stdin + !!argc > 1) {
331                 error("Specify either a list, or --all, not both!");
332                 usage_with_options(name_rev_usage, opts);
333         }
334         if (all || transform_stdin)
335                 cutoff = 0;
336
337         for (; argc; argc--, argv++) {
338                 unsigned char sha1[20];
339                 struct object *object;
340                 struct commit *commit;
341
342                 if (get_sha1(*argv, sha1)) {
343                         fprintf(stderr, "Could not get sha1 for %s. Skipping.\n",
344                                         *argv);
345                         continue;
346                 }
347
348                 commit = NULL;
349                 object = parse_object(sha1);
350                 if (object) {
351                         struct object *peeled = deref_tag(object, *argv, 0);
352                         if (peeled && peeled->type == OBJ_COMMIT)
353                                 commit = (struct commit *)peeled;
354                 }
355
356                 if (!object) {
357                         fprintf(stderr, "Could not get object for %s. Skipping.\n",
358                                         *argv);
359                         continue;
360                 }
361
362                 if (commit) {
363                         if (cutoff > commit->date)
364                                 cutoff = commit->date;
365                 }
366
367                 if (peel_tag) {
368                         if (!commit) {
369                                 fprintf(stderr, "Could not get commit for %s. Skipping.\n",
370                                         *argv);
371                                 continue;
372                         }
373                         object = (struct object *)commit;
374                 }
375                 add_object_array(object, *argv, &revs);
376         }
377
378         if (cutoff)
379                 cutoff = cutoff - CUTOFF_DATE_SLOP;
380         for_each_ref(name_ref, &data);
381
382         if (transform_stdin) {
383                 char buffer[2048];
384
385                 while (!feof(stdin)) {
386                         char *p = fgets(buffer, sizeof(buffer), stdin);
387                         if (!p)
388                                 break;
389                         name_rev_line(p, &data);
390                 }
391         } else if (all) {
392                 int i, max;
393
394                 max = get_max_object_index();
395                 for (i = 0; i < max; i++) {
396                         struct object *obj = get_indexed_object(i);
397                         if (!obj || obj->type != OBJ_COMMIT)
398                                 continue;
399                         show_name(obj, NULL,
400                                   always, allow_undefined, data.name_only);
401                 }
402         } else {
403                 int i;
404                 for (i = 0; i < revs.nr; i++)
405                         show_name(revs.objects[i].item, revs.objects[i].name,
406                                   always, allow_undefined, data.name_only);
407         }
408
409         return 0;
410 }