commit.con commit Make it possible to set up libgit directly (instead of from the environment) (0270083)
   1#include "cache.h"
   2#include "tag.h"
   3#include "commit.h"
   4
   5int save_commit_buffer = 1;
   6
   7struct sort_node
   8{
   9        /*
  10         * the number of children of the associated commit
  11         * that also occur in the list being sorted.
  12         */
  13        unsigned int indegree;
  14
  15        /*
  16         * reference to original list item that we will re-use
  17         * on output.
  18         */
  19        struct commit_list * list_item;
  20
  21};
  22
  23const char *commit_type = "commit";
  24
  25struct cmt_fmt_map {
  26        const char *n;
  27        size_t cmp_len;
  28        enum cmit_fmt v;
  29} cmt_fmts[] = {
  30        { "raw",        1,      CMIT_FMT_RAW },
  31        { "medium",     1,      CMIT_FMT_MEDIUM },
  32        { "short",      1,      CMIT_FMT_SHORT },
  33        { "email",      1,      CMIT_FMT_EMAIL },
  34        { "full",       5,      CMIT_FMT_FULL },
  35        { "fuller",     5,      CMIT_FMT_FULLER },
  36        { "oneline",    1,      CMIT_FMT_ONELINE },
  37};
  38
  39enum cmit_fmt get_commit_format(const char *arg)
  40{
  41        int i;
  42
  43        if (!arg || !*arg)
  44                return CMIT_FMT_DEFAULT;
  45        if (*arg == '=')
  46                arg++;
  47        for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
  48                if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len))
  49                        return cmt_fmts[i].v;
  50        }
  51
  52        die("invalid --pretty format: %s", arg);
  53}
  54
  55static struct commit *check_commit(struct object *obj,
  56                                   const unsigned char *sha1,
  57                                   int quiet)
  58{
  59        if (obj->type != TYPE_COMMIT) {
  60                if (!quiet)
  61                        error("Object %s is a %s, not a commit",
  62                              sha1_to_hex(sha1), typename(obj->type));
  63                return NULL;
  64        }
  65        return (struct commit *) obj;
  66}
  67
  68struct commit *lookup_commit_reference_gently(const unsigned char *sha1,
  69                                              int quiet)
  70{
  71        struct object *obj = deref_tag(parse_object(sha1), NULL, 0);
  72
  73        if (!obj)
  74                return NULL;
  75        return check_commit(obj, sha1, quiet);
  76}
  77
  78struct commit *lookup_commit_reference(const unsigned char *sha1)
  79{
  80        return lookup_commit_reference_gently(sha1, 0);
  81}
  82
  83struct commit *lookup_commit(const unsigned char *sha1)
  84{
  85        struct object *obj = lookup_object(sha1);
  86        if (!obj) {
  87                struct commit *ret = alloc_commit_node();
  88                created_object(sha1, &ret->object);
  89                ret->object.type = TYPE_COMMIT;
  90                return ret;
  91        }
  92        if (!obj->type)
  93                obj->type = TYPE_COMMIT;
  94        return check_commit(obj, sha1, 0);
  95}
  96
  97static unsigned long parse_commit_date(const char *buf)
  98{
  99        unsigned long date;
 100
 101        if (memcmp(buf, "author", 6))
 102                return 0;
 103        while (*buf++ != '\n')
 104                /* nada */;
 105        if (memcmp(buf, "committer", 9))
 106                return 0;
 107        while (*buf++ != '>')
 108                /* nada */;
 109        date = strtoul(buf, NULL, 10);
 110        if (date == ULONG_MAX)
 111                date = 0;
 112        return date;
 113}
 114
 115static struct commit_graft **commit_graft;
 116static int commit_graft_alloc, commit_graft_nr;
 117
 118static int commit_graft_pos(const unsigned char *sha1)
 119{
 120        int lo, hi;
 121        lo = 0;
 122        hi = commit_graft_nr;
 123        while (lo < hi) {
 124                int mi = (lo + hi) / 2;
 125                struct commit_graft *graft = commit_graft[mi];
 126                int cmp = memcmp(sha1, graft->sha1, 20);
 127                if (!cmp)
 128                        return mi;
 129                if (cmp < 0)
 130                        hi = mi;
 131                else
 132                        lo = mi + 1;
 133        }
 134        return -lo - 1;
 135}
 136
 137int register_commit_graft(struct commit_graft *graft, int ignore_dups)
 138{
 139        int pos = commit_graft_pos(graft->sha1);
 140        
 141        if (0 <= pos) {
 142                if (ignore_dups)
 143                        free(graft);
 144                else {
 145                        free(commit_graft[pos]);
 146                        commit_graft[pos] = graft;
 147                }
 148                return 1;
 149        }
 150        pos = -pos - 1;
 151        if (commit_graft_alloc <= ++commit_graft_nr) {
 152                commit_graft_alloc = alloc_nr(commit_graft_alloc);
 153                commit_graft = xrealloc(commit_graft,
 154                                        sizeof(*commit_graft) *
 155                                        commit_graft_alloc);
 156        }
 157        if (pos < commit_graft_nr)
 158                memmove(commit_graft + pos + 1,
 159                        commit_graft + pos,
 160                        (commit_graft_nr - pos - 1) *
 161                        sizeof(*commit_graft));
 162        commit_graft[pos] = graft;
 163        return 0;
 164}
 165
 166void free_commit_grafts(void)
 167{
 168        int pos = commit_graft_nr;
 169        while (pos >= 0)
 170                free(commit_graft[pos--]);
 171        commit_graft_nr = 0;
 172}
 173
 174struct commit_graft *read_graft_line(char *buf, int len)
 175{
 176        /* The format is just "Commit Parent1 Parent2 ...\n" */
 177        int i;
 178        struct commit_graft *graft = NULL;
 179
 180        if (buf[len-1] == '\n')
 181                buf[--len] = 0;
 182        if (buf[0] == '#' || buf[0] == '\0')
 183                return NULL;
 184        if ((len + 1) % 41) {
 185        bad_graft_data:
 186                error("bad graft data: %s", buf);
 187                free(graft);
 188                return NULL;
 189        }
 190        i = (len + 1) / 41 - 1;
 191        graft = xmalloc(sizeof(*graft) + 20 * i);
 192        graft->nr_parent = i;
 193        if (get_sha1_hex(buf, graft->sha1))
 194                goto bad_graft_data;
 195        for (i = 40; i < len; i += 41) {
 196                if (buf[i] != ' ')
 197                        goto bad_graft_data;
 198                if (get_sha1_hex(buf + i + 1, graft->parent[i/41]))
 199                        goto bad_graft_data;
 200        }
 201        return graft;
 202}
 203
 204int read_graft_file(const char *graft_file)
 205{
 206        FILE *fp = fopen(graft_file, "r");
 207        char buf[1024];
 208        if (!fp)
 209                return -1;
 210        while (fgets(buf, sizeof(buf), fp)) {
 211                /* The format is just "Commit Parent1 Parent2 ...\n" */
 212                int len = strlen(buf);
 213                struct commit_graft *graft = read_graft_line(buf, len);
 214                if (!graft)
 215                        continue;
 216                if (register_commit_graft(graft, 1))
 217                        error("duplicate graft data: %s", buf);
 218        }
 219        fclose(fp);
 220        return 0;
 221}
 222
 223static void prepare_commit_graft(void)
 224{
 225        static int commit_graft_prepared;
 226        static char *last_graft_file;
 227        char *graft_file = get_graft_file();
 228
 229        if (last_graft_file) {
 230                if (!strcmp(graft_file, last_graft_file))
 231                        return;
 232                free_commit_grafts();
 233        }
 234        if (last_graft_file)
 235                free(last_graft_file);
 236        last_graft_file = strdup(graft_file);
 237
 238        read_graft_file(graft_file);
 239        commit_graft_prepared = 1;
 240}
 241
 242static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
 243{
 244        int pos;
 245        prepare_commit_graft();
 246        pos = commit_graft_pos(sha1);
 247        if (pos < 0)
 248                return NULL;
 249        return commit_graft[pos];
 250}
 251
 252int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
 253{
 254        char *tail = buffer;
 255        char *bufptr = buffer;
 256        unsigned char parent[20];
 257        struct commit_list **pptr;
 258        struct commit_graft *graft;
 259        unsigned n_refs = 0;
 260
 261        if (item->object.parsed)
 262                return 0;
 263        item->object.parsed = 1;
 264        tail += size;
 265        if (tail <= bufptr + 5 || memcmp(bufptr, "tree ", 5))
 266                return error("bogus commit object %s", sha1_to_hex(item->object.sha1));
 267        if (tail <= bufptr + 45 || get_sha1_hex(bufptr + 5, parent) < 0)
 268                return error("bad tree pointer in commit %s",
 269                             sha1_to_hex(item->object.sha1));
 270        item->tree = lookup_tree(parent);
 271        if (item->tree)
 272                n_refs++;
 273        bufptr += 46; /* "tree " + "hex sha1" + "\n" */
 274        pptr = &item->parents;
 275
 276        graft = lookup_commit_graft(item->object.sha1);
 277        while (bufptr + 48 < tail && !memcmp(bufptr, "parent ", 7)) {
 278                struct commit *new_parent;
 279
 280                if (tail <= bufptr + 48 ||
 281                    get_sha1_hex(bufptr + 7, parent) ||
 282                    bufptr[47] != '\n')
 283                        return error("bad parents in commit %s", sha1_to_hex(item->object.sha1));
 284                bufptr += 48;
 285                if (graft)
 286                        continue;
 287                new_parent = lookup_commit(parent);
 288                if (new_parent) {
 289                        pptr = &commit_list_insert(new_parent, pptr)->next;
 290                        n_refs++;
 291                }
 292        }
 293        if (graft) {
 294                int i;
 295                struct commit *new_parent;
 296                for (i = 0; i < graft->nr_parent; i++) {
 297                        new_parent = lookup_commit(graft->parent[i]);
 298                        if (!new_parent)
 299                                continue;
 300                        pptr = &commit_list_insert(new_parent, pptr)->next;
 301                        n_refs++;
 302                }
 303        }
 304        item->date = parse_commit_date(bufptr);
 305
 306        if (track_object_refs) {
 307                unsigned i = 0;
 308                struct commit_list *p;
 309                struct object_refs *refs = alloc_object_refs(n_refs);
 310                if (item->tree)
 311                        refs->ref[i++] = &item->tree->object;
 312                for (p = item->parents; p; p = p->next)
 313                        refs->ref[i++] = &p->item->object;
 314                set_object_refs(&item->object, refs);
 315        }
 316
 317        return 0;
 318}
 319
 320int parse_commit(struct commit *item)
 321{
 322        char type[20];
 323        void *buffer;
 324        unsigned long size;
 325        int ret;
 326
 327        if (item->object.parsed)
 328                return 0;
 329        buffer = read_sha1_file(item->object.sha1, type, &size);
 330        if (!buffer)
 331                return error("Could not read %s",
 332                             sha1_to_hex(item->object.sha1));
 333        if (strcmp(type, commit_type)) {
 334                free(buffer);
 335                return error("Object %s not a commit",
 336                             sha1_to_hex(item->object.sha1));
 337        }
 338        ret = parse_commit_buffer(item, buffer, size);
 339        if (save_commit_buffer && !ret) {
 340                item->buffer = buffer;
 341                return 0;
 342        }
 343        free(buffer);
 344        return ret;
 345}
 346
 347struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p)
 348{
 349        struct commit_list *new_list = xmalloc(sizeof(struct commit_list));
 350        new_list->item = item;
 351        new_list->next = *list_p;
 352        *list_p = new_list;
 353        return new_list;
 354}
 355
 356void free_commit_list(struct commit_list *list)
 357{
 358        while (list) {
 359                struct commit_list *temp = list;
 360                list = temp->next;
 361                free(temp);
 362        }
 363}
 364
 365struct commit_list * insert_by_date(struct commit *item, struct commit_list **list)
 366{
 367        struct commit_list **pp = list;
 368        struct commit_list *p;
 369        while ((p = *pp) != NULL) {
 370                if (p->item->date < item->date) {
 371                        break;
 372                }
 373                pp = &p->next;
 374        }
 375        return commit_list_insert(item, pp);
 376}
 377
 378        
 379void sort_by_date(struct commit_list **list)
 380{
 381        struct commit_list *ret = NULL;
 382        while (*list) {
 383                insert_by_date((*list)->item, &ret);
 384                *list = (*list)->next;
 385        }
 386        *list = ret;
 387}
 388
 389struct commit *pop_most_recent_commit(struct commit_list **list,
 390                                      unsigned int mark)
 391{
 392        struct commit *ret = (*list)->item;
 393        struct commit_list *parents = ret->parents;
 394        struct commit_list *old = *list;
 395
 396        *list = (*list)->next;
 397        free(old);
 398
 399        while (parents) {
 400                struct commit *commit = parents->item;
 401                parse_commit(commit);
 402                if (!(commit->object.flags & mark)) {
 403                        commit->object.flags |= mark;
 404                        insert_by_date(commit, list);
 405                }
 406                parents = parents->next;
 407        }
 408        return ret;
 409}
 410
 411void clear_commit_marks(struct commit *commit, unsigned int mark)
 412{
 413        struct commit_list *parents;
 414
 415        parents = commit->parents;
 416        commit->object.flags &= ~mark;
 417        while (parents) {
 418                struct commit *parent = parents->item;
 419                if (parent && parent->object.parsed &&
 420                    (parent->object.flags & mark))
 421                        clear_commit_marks(parent, mark);
 422                parents = parents->next;
 423        }
 424}
 425
 426/*
 427 * Generic support for pretty-printing the header
 428 */
 429static int get_one_line(const char *msg, unsigned long len)
 430{
 431        int ret = 0;
 432
 433        while (len--) {
 434                char c = *msg++;
 435                if (!c)
 436                        break;
 437                ret++;
 438                if (c == '\n')
 439                        break;
 440        }
 441        return ret;
 442}
 443
 444static int is_rfc2047_special(char ch)
 445{
 446        return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_'));
 447}
 448
 449static int add_rfc2047(char *buf, const char *line, int len)
 450{
 451        char *bp = buf;
 452        int i, needquote;
 453        static const char q_utf8[] = "=?utf-8?q?";
 454
 455        for (i = needquote = 0; !needquote && i < len; i++) {
 456                unsigned ch = line[i];
 457                if (ch & 0x80)
 458                        needquote++;
 459                if ((i + 1 < len) &&
 460                    (ch == '=' && line[i+1] == '?'))
 461                        needquote++;
 462        }
 463        if (!needquote)
 464                return sprintf(buf, "%.*s", len, line);
 465
 466        memcpy(bp, q_utf8, sizeof(q_utf8)-1);
 467        bp += sizeof(q_utf8)-1;
 468        for (i = 0; i < len; i++) {
 469                unsigned ch = line[i] & 0xFF;
 470                if (is_rfc2047_special(ch)) {
 471                        sprintf(bp, "=%02X", ch);
 472                        bp += 3;
 473                }
 474                else if (ch == ' ')
 475                        *bp++ = '_';
 476                else
 477                        *bp++ = ch;
 478        }
 479        memcpy(bp, "?=", 2);
 480        bp += 2;
 481        return bp - buf;
 482}
 483
 484static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf, const char *line)
 485{
 486        char *date;
 487        int namelen;
 488        unsigned long time;
 489        int tz, ret;
 490        const char *filler = "    ";
 491
 492        if (fmt == CMIT_FMT_ONELINE)
 493                return 0;
 494        date = strchr(line, '>');
 495        if (!date)
 496                return 0;
 497        namelen = ++date - line;
 498        time = strtoul(date, &date, 10);
 499        tz = strtol(date, NULL, 10);
 500
 501        if (fmt == CMIT_FMT_EMAIL) {
 502                char *name_tail = strchr(line, '<');
 503                int display_name_length;
 504                if (!name_tail)
 505                        return 0;
 506                while (line < name_tail && isspace(name_tail[-1]))
 507                        name_tail--;
 508                display_name_length = name_tail - line;
 509                filler = "";
 510                strcpy(buf, "From: ");
 511                ret = strlen(buf);
 512                ret += add_rfc2047(buf + ret, line, display_name_length);
 513                memcpy(buf + ret, name_tail, namelen - display_name_length);
 514                ret += namelen - display_name_length;
 515                buf[ret++] = '\n';
 516        }
 517        else {
 518                ret = sprintf(buf, "%s: %.*s%.*s\n", what,
 519                              (fmt == CMIT_FMT_FULLER) ? 4 : 0,
 520                              filler, namelen, line);
 521        }
 522        switch (fmt) {
 523        case CMIT_FMT_MEDIUM:
 524                ret += sprintf(buf + ret, "Date:   %s\n", show_date(time, tz));
 525                break;
 526        case CMIT_FMT_EMAIL:
 527                ret += sprintf(buf + ret, "Date: %s\n",
 528                               show_rfc2822_date(time, tz));
 529                break;
 530        case CMIT_FMT_FULLER:
 531                ret += sprintf(buf + ret, "%sDate: %s\n", what, show_date(time, tz));
 532                break;
 533        default:
 534                /* notin' */
 535                break;
 536        }
 537        return ret;
 538}
 539
 540static int is_empty_line(const char *line, int *len_p)
 541{
 542        int len = *len_p;
 543        while (len && isspace(line[len-1]))
 544                len--;
 545        *len_p = len;
 546        return !len;
 547}
 548
 549static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev)
 550{
 551        struct commit_list *parent = commit->parents;
 552        int offset;
 553
 554        if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
 555            !parent || !parent->next)
 556                return 0;
 557
 558        offset = sprintf(buf, "Merge:");
 559
 560        while (parent) {
 561                struct commit *p = parent->item;
 562                const char *hex = abbrev
 563                        ? find_unique_abbrev(p->object.sha1, abbrev)
 564                        : sha1_to_hex(p->object.sha1);
 565                const char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
 566                parent = parent->next;
 567
 568                offset += sprintf(buf + offset, " %s%s", hex, dots);
 569        }
 570        buf[offset++] = '\n';
 571        return offset;
 572}
 573
 574unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject)
 575{
 576        int hdr = 1, body = 0;
 577        unsigned long offset = 0;
 578        int indent = 4;
 579        int parents_shown = 0;
 580        const char *msg = commit->buffer;
 581        int plain_non_ascii = 0;
 582
 583        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
 584                indent = 0;
 585
 586        /* After-subject is used to pass in Content-Type: multipart
 587         * MIME header; in that case we do not have to do the
 588         * plaintext content type even if the commit message has
 589         * non 7-bit ASCII character.  Otherwise, check if we need
 590         * to say this is not a 7-bit ASCII.
 591         */
 592        if (fmt == CMIT_FMT_EMAIL && !after_subject) {
 593                int i, ch, in_body;
 594
 595                for (in_body = i = 0; (ch = msg[i]) && i < len; i++) {
 596                        if (!in_body) {
 597                                /* author could be non 7-bit ASCII but
 598                                 * the log may so; skip over the
 599                                 * header part first.
 600                                 */
 601                                if (ch == '\n' &&
 602                                    i + 1 < len && msg[i+1] == '\n')
 603                                        in_body = 1;
 604                        }
 605                        else if (ch & 0x80) {
 606                                plain_non_ascii = 1;
 607                                break;
 608                        }
 609                }
 610        }
 611
 612        for (;;) {
 613                const char *line = msg;
 614                int linelen = get_one_line(msg, len);
 615
 616                if (!linelen)
 617                        break;
 618
 619                /*
 620                 * We want some slop for indentation and a possible
 621                 * final "...". Thus the "+ 20".
 622                 */
 623                if (offset + linelen + 20 > space) {
 624                        memcpy(buf + offset, "    ...\n", 8);
 625                        offset += 8;
 626                        break;
 627                }
 628
 629                msg += linelen;
 630                len -= linelen;
 631                if (hdr) {
 632                        if (linelen == 1) {
 633                                hdr = 0;
 634                                if ((fmt != CMIT_FMT_ONELINE) && !subject)
 635                                        buf[offset++] = '\n';
 636                                continue;
 637                        }
 638                        if (fmt == CMIT_FMT_RAW) {
 639                                memcpy(buf + offset, line, linelen);
 640                                offset += linelen;
 641                                continue;
 642                        }
 643                        if (!memcmp(line, "parent ", 7)) {
 644                                if (linelen != 48)
 645                                        die("bad parent line in commit");
 646                                continue;
 647                        }
 648
 649                        if (!parents_shown) {
 650                                offset += add_merge_info(fmt, buf + offset,
 651                                                         commit, abbrev);
 652                                parents_shown = 1;
 653                                continue;
 654                        }
 655                        /*
 656                         * MEDIUM == DEFAULT shows only author with dates.
 657                         * FULL shows both authors but not dates.
 658                         * FULLER shows both authors and dates.
 659                         */
 660                        if (!memcmp(line, "author ", 7))
 661                                offset += add_user_info("Author", fmt,
 662                                                        buf + offset,
 663                                                        line + 7);
 664                        if (!memcmp(line, "committer ", 10) &&
 665                            (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
 666                                offset += add_user_info("Commit", fmt,
 667                                                        buf + offset,
 668                                                        line + 10);
 669                        continue;
 670                }
 671
 672                if (is_empty_line(line, &linelen)) {
 673                        if (!body)
 674                                continue;
 675                        if (subject)
 676                                continue;
 677                        if (fmt == CMIT_FMT_SHORT)
 678                                break;
 679                } else {
 680                        body = 1;
 681                }
 682
 683                if (subject) {
 684                        int slen = strlen(subject);
 685                        memcpy(buf + offset, subject, slen);
 686                        offset += slen;
 687                        offset += add_rfc2047(buf + offset, line, linelen);
 688                }
 689                else {
 690                        memset(buf + offset, ' ', indent);
 691                        memcpy(buf + offset + indent, line, linelen);
 692                        offset += linelen + indent;
 693                }
 694                buf[offset++] = '\n';
 695                if (fmt == CMIT_FMT_ONELINE)
 696                        break;
 697                if (subject && plain_non_ascii) {
 698                        static const char header[] =
 699                                "Content-Type: text/plain; charset=UTF-8\n"
 700                                "Content-Transfer-Encoding: 8bit\n";
 701                        memcpy(buf + offset, header, sizeof(header)-1);
 702                        offset += sizeof(header)-1;
 703                }
 704                if (after_subject) {
 705                        int slen = strlen(after_subject);
 706                        if (slen > space - offset - 1)
 707                                slen = space - offset - 1;
 708                        memcpy(buf + offset, after_subject, slen);
 709                        offset += slen;
 710                        after_subject = NULL;
 711                }
 712                subject = NULL;
 713        }
 714        while (offset && isspace(buf[offset-1]))
 715                offset--;
 716        /* Make sure there is an EOLN for the non-oneline case */
 717        if (fmt != CMIT_FMT_ONELINE)
 718                buf[offset++] = '\n';
 719        buf[offset] = '\0';
 720        return offset;
 721}
 722
 723struct commit *pop_commit(struct commit_list **stack)
 724{
 725        struct commit_list *top = *stack;
 726        struct commit *item = top ? top->item : NULL;
 727
 728        if (top) {
 729                *stack = top->next;
 730                free(top);
 731        }
 732        return item;
 733}
 734
 735int count_parents(struct commit * commit)
 736{
 737        int count = 0;
 738        struct commit_list * parents = commit->parents;
 739        for (count=0;parents; parents=parents->next,count++)
 740          ;
 741        return count;
 742}
 743
 744void topo_sort_default_setter(struct commit *c, void *data)
 745{
 746        c->util = data;
 747}
 748
 749void *topo_sort_default_getter(struct commit *c)
 750{
 751        return c->util;
 752}
 753
 754/*
 755 * Performs an in-place topological sort on the list supplied.
 756 */
 757void sort_in_topological_order(struct commit_list ** list, int lifo)
 758{
 759        sort_in_topological_order_fn(list, lifo, topo_sort_default_setter,
 760                                     topo_sort_default_getter);
 761}
 762
 763void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
 764                                  topo_sort_set_fn_t setter,
 765                                  topo_sort_get_fn_t getter)
 766{
 767        struct commit_list * next = *list;
 768        struct commit_list * work = NULL, **insert;
 769        struct commit_list ** pptr = list;
 770        struct sort_node * nodes;
 771        struct sort_node * next_nodes;
 772        int count = 0;
 773
 774        /* determine the size of the list */
 775        while (next) {
 776                next = next->next;
 777                count++;
 778        }
 779        
 780        if (!count)
 781                return;
 782        /* allocate an array to help sort the list */
 783        nodes = xcalloc(count, sizeof(*nodes));
 784        /* link the list to the array */
 785        next_nodes = nodes;
 786        next=*list;
 787        while (next) {
 788                next_nodes->list_item = next;
 789                setter(next->item, next_nodes);
 790                next_nodes++;
 791                next = next->next;
 792        }
 793        /* update the indegree */
 794        next=*list;
 795        while (next) {
 796                struct commit_list * parents = next->item->parents;
 797                while (parents) {
 798                        struct commit * parent=parents->item;
 799                        struct sort_node * pn = (struct sort_node *) getter(parent);
 800
 801                        if (pn)
 802                                pn->indegree++;
 803                        parents=parents->next;
 804                }
 805                next=next->next;
 806        }
 807        /* 
 808         * find the tips
 809         *
 810         * tips are nodes not reachable from any other node in the list 
 811         * 
 812         * the tips serve as a starting set for the work queue.
 813         */
 814        next=*list;
 815        insert = &work;
 816        while (next) {
 817                struct sort_node * node = (struct sort_node *) getter(next->item);
 818
 819                if (node->indegree == 0) {
 820                        insert = &commit_list_insert(next->item, insert)->next;
 821                }
 822                next=next->next;
 823        }
 824
 825        /* process the list in topological order */
 826        if (!lifo)
 827                sort_by_date(&work);
 828        while (work) {
 829                struct commit * work_item = pop_commit(&work);
 830                struct sort_node * work_node = (struct sort_node *) getter(work_item);
 831                struct commit_list * parents = work_item->parents;
 832
 833                while (parents) {
 834                        struct commit * parent=parents->item;
 835                        struct sort_node * pn = (struct sort_node *) getter(parent);
 836
 837                        if (pn) {
 838                                /*
 839                                 * parents are only enqueued for emission 
 840                                 * when all their children have been emitted thereby
 841                                 * guaranteeing topological order.
 842                                 */
 843                                pn->indegree--;
 844                                if (!pn->indegree) {
 845                                        if (!lifo)
 846                                                insert_by_date(parent, &work);
 847                                        else
 848                                                commit_list_insert(parent, &work);
 849                                }
 850                        }
 851                        parents=parents->next;
 852                }
 853                /*
 854                 * work_item is a commit all of whose children
 855                 * have already been emitted. we can emit it now.
 856                 */
 857                *pptr = work_node->list_item;
 858                pptr = &(*pptr)->next;
 859                *pptr = NULL;
 860                setter(work_item, NULL);
 861        }
 862        free(nodes);
 863}