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