Skip to content

Commit 534f0e0

Browse files
committed
Merge branch 'jc/topo-author-date-sort'
"git log" learned the "--author-date-order" option, with which the output is topologically sorted and commits in parallel histories are shown intermixed together based on the author timestamp. * jc/topo-author-date-sort: t6003: add --author-date-order test topology tests: teach a helper to set author dates as well t6003: add --date-order test topology tests: teach a helper to take abbreviated timestamps t/lib-t6000: style fixes log: --author-date-order sort-in-topological-order: use prio-queue prio-queue: priority queue of pointers to structs toposort: rename "lifo" field
2 parents 55f34c8 + aff2e7c commit 534f0e0

16 files changed

+550
-163
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@
191191
/test-mktemp
192192
/test-parse-options
193193
/test-path-utils
194+
/test-prio-queue
194195
/test-read-cache
195196
/test-regex
196197
/test-revision-walking

Documentation/rev-list-options.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,10 @@ By default, the commits are shown in reverse chronological order.
625625
Show no parents before all of its children are shown, but
626626
otherwise show commits in the commit timestamp order.
627627

628+
--author-date-order::
629+
Show no parents before all of its children are shown, but
630+
otherwise show commits in the author timestamp order.
631+
628632
--topo-order::
629633
Show no parents before all of its children are shown, and
630634
avoid showing commits on multiple lines of history

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
569569
TEST_PROGRAMS_NEED_X += test-mktemp
570570
TEST_PROGRAMS_NEED_X += test-parse-options
571571
TEST_PROGRAMS_NEED_X += test-path-utils
572+
TEST_PROGRAMS_NEED_X += test-prio-queue
572573
TEST_PROGRAMS_NEED_X += test-read-cache
573574
TEST_PROGRAMS_NEED_X += test-regex
574575
TEST_PROGRAMS_NEED_X += test-revision-walking
@@ -705,6 +706,7 @@ LIB_H += parse-options.h
705706
LIB_H += patch-ids.h
706707
LIB_H += pathspec.h
707708
LIB_H += pkt-line.h
709+
LIB_H += prio-queue.h
708710
LIB_H += progress.h
709711
LIB_H += prompt.h
710712
LIB_H += quote.h
@@ -846,6 +848,7 @@ LIB_OBJS += pathspec.o
846848
LIB_OBJS += pkt-line.o
847849
LIB_OBJS += preload-index.o
848850
LIB_OBJS += pretty.o
851+
LIB_OBJS += prio-queue.o
849852
LIB_OBJS += progress.o
850853
LIB_OBJS += prompt.o
851854
LIB_OBJS += quote.o

builtin/log.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
237237
int i = revs->early_output;
238238
int show_header = 1;
239239

240-
sort_in_topological_order(&list, revs->lifo);
240+
sort_in_topological_order(&list, revs->sort_order);
241241
while (list && i) {
242242
struct commit *commit = list->item;
243243
switch (simplify_commit(revs, commit)) {

builtin/show-branch.c

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
630630
int num_rev, i, extra = 0;
631631
int all_heads = 0, all_remotes = 0;
632632
int all_mask, all_revs;
633-
int lifo = 1;
633+
enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
634634
char head[128];
635635
const char *head_p;
636636
int head_len;
@@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
665665
N_("show possible merge bases")),
666666
OPT_BOOLEAN(0, "independent", &independent,
667667
N_("show refs unreachable from any other ref")),
668-
OPT_BOOLEAN(0, "topo-order", &lifo,
669-
N_("show commits in topological order")),
668+
OPT_SET_INT(0, "topo-order", &sort_order,
669+
N_("show commits in topological order"),
670+
REV_SORT_IN_GRAPH_ORDER),
670671
OPT_BOOLEAN(0, "topics", &topics,
671672
N_("show only commits not on the first branch")),
672673
OPT_SET_INT(0, "sparse", &dense,
673674
N_("show merges reachable from only one tip"), 0),
674-
OPT_SET_INT(0, "date-order", &lifo,
675+
OPT_SET_INT(0, "date-order", &sort_order,
675676
N_("show commits where no parent comes before its "
676-
"children"), 0),
677+
"children"),
678+
REV_SORT_BY_COMMIT_DATE),
677679
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
678680
N_("show <n> most recent ref-log entries starting at "
679681
"base"),
@@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
900902
exit(0);
901903

902904
/* Sort topologically */
903-
sort_in_topological_order(&seen, lifo);
905+
sort_in_topological_order(&seen, sort_order);
904906

905907
/* Give names to commits */
906908
if (!sha1_name && !no_name)

commit.c

Lines changed: 118 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "gpg-interface.h"
1010
#include "mergesort.h"
1111
#include "commit-slab.h"
12+
#include "prio-queue.h"
1213

1314
static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
1415

@@ -518,31 +519,124 @@ struct commit *pop_commit(struct commit_list **stack)
518519
/* count number of children that have not been emitted */
519520
define_commit_slab(indegree_slab, int);
520521

522+
/* record author-date for each commit object */
523+
define_commit_slab(author_date_slab, unsigned long);
524+
525+
static void record_author_date(struct author_date_slab *author_date,
526+
struct commit *commit)
527+
{
528+
const char *buf, *line_end;
529+
char *buffer = NULL;
530+
struct ident_split ident;
531+
char *date_end;
532+
unsigned long date;
533+
534+
if (!commit->buffer) {
535+
unsigned long size;
536+
enum object_type type;
537+
buffer = read_sha1_file(commit->object.sha1, &type, &size);
538+
if (!buffer)
539+
return;
540+
}
541+
542+
for (buf = commit->buffer ? commit->buffer : buffer;
543+
buf;
544+
buf = line_end + 1) {
545+
line_end = strchrnul(buf, '\n');
546+
if (prefixcmp(buf, "author ")) {
547+
if (!line_end[0] || line_end[1] == '\n')
548+
return; /* end of header */
549+
continue;
550+
}
551+
if (split_ident_line(&ident,
552+
buf + strlen("author "),
553+
line_end - (buf + strlen("author "))) ||
554+
!ident.date_begin || !ident.date_end)
555+
goto fail_exit; /* malformed "author" line */
556+
break;
557+
}
558+
559+
date = strtoul(ident.date_begin, &date_end, 10);
560+
if (date_end != ident.date_end)
561+
goto fail_exit; /* malformed date */
562+
*(author_date_slab_at(author_date, commit)) = date;
563+
564+
fail_exit:
565+
free(buffer);
566+
}
567+
568+
static int compare_commits_by_author_date(const void *a_, const void *b_,
569+
void *cb_data)
570+
{
571+
const struct commit *a = a_, *b = b_;
572+
struct author_date_slab *author_date = cb_data;
573+
unsigned long a_date = *(author_date_slab_at(author_date, a));
574+
unsigned long b_date = *(author_date_slab_at(author_date, b));
575+
576+
/* newer commits with larger date first */
577+
if (a_date < b_date)
578+
return 1;
579+
else if (a_date > b_date)
580+
return -1;
581+
return 0;
582+
}
583+
584+
static int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
585+
{
586+
const struct commit *a = a_, *b = b_;
587+
/* newer commits with larger date first */
588+
if (a->date < b->date)
589+
return 1;
590+
else if (a->date > b->date)
591+
return -1;
592+
return 0;
593+
}
594+
521595
/*
522596
* Performs an in-place topological sort on the list supplied.
523597
*/
524-
void sort_in_topological_order(struct commit_list ** list, int lifo)
598+
void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order)
525599
{
526600
struct commit_list *next, *orig = *list;
527-
struct commit_list *work, **insert;
528601
struct commit_list **pptr;
529602
struct indegree_slab indegree;
603+
struct prio_queue queue;
604+
struct commit *commit;
605+
struct author_date_slab author_date;
530606

531607
if (!orig)
532608
return;
533609
*list = NULL;
534610

535611
init_indegree_slab(&indegree);
612+
memset(&queue, '\0', sizeof(queue));
613+
614+
switch (sort_order) {
615+
default: /* REV_SORT_IN_GRAPH_ORDER */
616+
queue.compare = NULL;
617+
break;
618+
case REV_SORT_BY_COMMIT_DATE:
619+
queue.compare = compare_commits_by_commit_date;
620+
break;
621+
case REV_SORT_BY_AUTHOR_DATE:
622+
init_author_date_slab(&author_date);
623+
queue.compare = compare_commits_by_author_date;
624+
queue.cb_data = &author_date;
625+
break;
626+
}
536627

537628
/* Mark them and clear the indegree */
538629
for (next = orig; next; next = next->next) {
539630
struct commit *commit = next->item;
540631
*(indegree_slab_at(&indegree, commit)) = 1;
632+
/* also record the author dates, if needed */
633+
if (sort_order == REV_SORT_BY_AUTHOR_DATE)
634+
record_author_date(&author_date, commit);
541635
}
542636

543637
/* update the indegree */
544638
for (next = orig; next; next = next->next) {
545-
struct commit_list * parents = next->item->parents;
639+
struct commit_list *parents = next->item->parents;
546640
while (parents) {
547641
struct commit *parent = parents->item;
548642
int *pi = indegree_slab_at(&indegree, parent);
@@ -560,30 +654,28 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
560654
*
561655
* the tips serve as a starting set for the work queue.
562656
*/
563-
work = NULL;
564-
insert = &work;
565657
for (next = orig; next; next = next->next) {
566658
struct commit *commit = next->item;
567659

568660
if (*(indegree_slab_at(&indegree, commit)) == 1)
569-
insert = &commit_list_insert(commit, insert)->next;
661+
prio_queue_put(&queue, commit);
570662
}
571663

572-
/* process the list in topological order */
573-
if (!lifo)
574-
commit_list_sort_by_date(&work);
664+
/*
665+
* This is unfortunate; the initial tips need to be shown
666+
* in the order given from the revision traversal machinery.
667+
*/
668+
if (sort_order == REV_SORT_IN_GRAPH_ORDER)
669+
prio_queue_reverse(&queue);
670+
671+
/* We no longer need the commit list */
672+
free_commit_list(orig);
575673

576674
pptr = list;
577675
*list = NULL;
578-
while (work) {
579-
struct commit *commit;
580-
struct commit_list *parents, *work_item;
581-
582-
work_item = work;
583-
work = work_item->next;
584-
work_item->next = NULL;
676+
while ((commit = prio_queue_get(&queue)) != NULL) {
677+
struct commit_list *parents;
585678

586-
commit = work_item->item;
587679
for (parents = commit->parents; parents ; parents = parents->next) {
588680
struct commit *parent = parents->item;
589681
int *pi = indegree_slab_at(&indegree, parent);
@@ -596,23 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
596688
* when all their children have been emitted thereby
597689
* guaranteeing topological order.
598690
*/
599-
if (--(*pi) == 1) {
600-
if (!lifo)
601-
commit_list_insert_by_date(parent, &work);
602-
else
603-
commit_list_insert(parent, &work);
604-
}
691+
if (--(*pi) == 1)
692+
prio_queue_put(&queue, parent);
605693
}
606694
/*
607-
* work_item is a commit all of whose children
608-
* have already been emitted. we can emit it now.
695+
* all children of commit have already been
696+
* emitted. we can emit it now.
609697
*/
610698
*(indegree_slab_at(&indegree, commit)) = 0;
611-
*pptr = work_item;
612-
pptr = &work_item->next;
699+
700+
pptr = &commit_list_insert(commit, pptr)->next;
613701
}
614702

615703
clear_indegree_slab(&indegree);
704+
clear_prio_queue(&queue);
705+
if (sort_order == REV_SORT_BY_AUTHOR_DATE)
706+
clear_author_date_slab(&author_date);
616707
}
617708

618709
/* merge-base stuff */

commit.h

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,24 @@ void clear_commit_marks(struct commit *commit, unsigned int mark);
142142
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
143143
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
144144

145+
146+
enum rev_sort_order {
147+
REV_SORT_IN_GRAPH_ORDER = 0,
148+
REV_SORT_BY_COMMIT_DATE,
149+
REV_SORT_BY_AUTHOR_DATE
150+
};
151+
145152
/*
146153
* Performs an in-place topological sort of list supplied.
147154
*
148155
* invariant of resulting list is:
149156
* a reachable from b => ord(b) < ord(a)
150-
* in addition, when lifo == 0, commits on parallel tracks are
151-
* sorted in the dates order.
157+
* sort_order further specifies:
158+
* REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent
159+
* chain together.
160+
* REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order.
152161
*/
153-
void sort_in_topological_order(struct commit_list ** list, int lifo);
162+
void sort_in_topological_order(struct commit_list **, enum rev_sort_order);
154163

155164
struct commit_graft {
156165
unsigned char sha1[20];

0 commit comments

Comments
 (0)