Skip to content

Commit

Permalink
Merge branch 'jc/topo-author-date-sort'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
Junio C Hamano committed Jul 1, 2013
2 parents 55f34c8 + aff2e7c commit 534f0e0
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 163 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
/test-mktemp
/test-parse-options
/test-path-utils
/test-prio-queue
/test-read-cache
/test-regex
/test-revision-walking
Expand Down
4 changes: 4 additions & 0 deletions Documentation/rev-list-options.txt
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,10 @@ By default, the commits are shown in reverse chronological order.
Show no parents before all of its children are shown, but
otherwise show commits in the commit timestamp order.

--author-date-order::
Show no parents before all of its children are shown, but
otherwise show commits in the author timestamp order.

--topo-order::
Show no parents before all of its children are shown, and
avoid showing commits on multiple lines of history
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils
TEST_PROGRAMS_NEED_X += test-prio-queue
TEST_PROGRAMS_NEED_X += test-read-cache
TEST_PROGRAMS_NEED_X += test-regex
TEST_PROGRAMS_NEED_X += test-revision-walking
Expand Down Expand Up @@ -705,6 +706,7 @@ LIB_H += parse-options.h
LIB_H += patch-ids.h
LIB_H += pathspec.h
LIB_H += pkt-line.h
LIB_H += prio-queue.h
LIB_H += progress.h
LIB_H += prompt.h
LIB_H += quote.h
Expand Down Expand Up @@ -846,6 +848,7 @@ LIB_OBJS += pathspec.o
LIB_OBJS += pkt-line.o
LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o
LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o
LIB_OBJS += prompt.o
LIB_OBJS += quote.o
Expand Down
2 changes: 1 addition & 1 deletion builtin/log.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output;
int show_header = 1;

sort_in_topological_order(&list, revs->lifo);
sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) {
Expand Down
14 changes: 8 additions & 6 deletions builtin/show-branch.c
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0;
int all_mask, all_revs;
int lifo = 1;
enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char head[128];
const char *head_p;
int head_len;
Expand Down Expand Up @@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
N_("show possible merge bases")),
OPT_BOOLEAN(0, "independent", &independent,
N_("show refs unreachable from any other ref")),
OPT_BOOLEAN(0, "topo-order", &lifo,
N_("show commits in topological order")),
OPT_SET_INT(0, "topo-order", &sort_order,
N_("show commits in topological order"),
REV_SORT_IN_GRAPH_ORDER),
OPT_BOOLEAN(0, "topics", &topics,
N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0),
OPT_SET_INT(0, "date-order", &lifo,
OPT_SET_INT(0, "date-order", &sort_order,
N_("show commits where no parent comes before its "
"children"), 0),
"children"),
REV_SORT_BY_COMMIT_DATE),
{ OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at "
"base"),
Expand Down Expand Up @@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
exit(0);

/* Sort topologically */
sort_in_topological_order(&seen, lifo);
sort_in_topological_order(&seen, sort_order);

/* Give names to commits */
if (!sha1_name && !no_name)
Expand Down
145 changes: 118 additions & 27 deletions commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "gpg-interface.h"
#include "mergesort.h"
#include "commit-slab.h"
#include "prio-queue.h"

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

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

/* record author-date for each commit object */
define_commit_slab(author_date_slab, unsigned long);

static void record_author_date(struct author_date_slab *author_date,
struct commit *commit)
{
const char *buf, *line_end;
char *buffer = NULL;
struct ident_split ident;
char *date_end;
unsigned long date;

if (!commit->buffer) {
unsigned long size;
enum object_type type;
buffer = read_sha1_file(commit->object.sha1, &type, &size);
if (!buffer)
return;
}

for (buf = commit->buffer ? commit->buffer : buffer;
buf;
buf = line_end + 1) {
line_end = strchrnul(buf, '\n');
if (prefixcmp(buf, "author ")) {
if (!line_end[0] || line_end[1] == '\n')
return; /* end of header */
continue;
}
if (split_ident_line(&ident,
buf + strlen("author "),
line_end - (buf + strlen("author "))) ||
!ident.date_begin || !ident.date_end)
goto fail_exit; /* malformed "author" line */
break;
}

date = strtoul(ident.date_begin, &date_end, 10);
if (date_end != ident.date_end)
goto fail_exit; /* malformed date */
*(author_date_slab_at(author_date, commit)) = date;

fail_exit:
free(buffer);
}

static int compare_commits_by_author_date(const void *a_, const void *b_,
void *cb_data)
{
const struct commit *a = a_, *b = b_;
struct author_date_slab *author_date = cb_data;
unsigned long a_date = *(author_date_slab_at(author_date, a));
unsigned long b_date = *(author_date_slab_at(author_date, b));

/* newer commits with larger date first */
if (a_date < b_date)
return 1;
else if (a_date > b_date)
return -1;
return 0;
}

static int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused)
{
const struct commit *a = a_, *b = b_;
/* newer commits with larger date first */
if (a->date < b->date)
return 1;
else if (a->date > b->date)
return -1;
return 0;
}

/*
* Performs an in-place topological sort on the list supplied.
*/
void sort_in_topological_order(struct commit_list ** list, int lifo)
void sort_in_topological_order(struct commit_list **list, enum rev_sort_order sort_order)
{
struct commit_list *next, *orig = *list;
struct commit_list *work, **insert;
struct commit_list **pptr;
struct indegree_slab indegree;
struct prio_queue queue;
struct commit *commit;
struct author_date_slab author_date;

if (!orig)
return;
*list = NULL;

init_indegree_slab(&indegree);
memset(&queue, '\0', sizeof(queue));

switch (sort_order) {
default: /* REV_SORT_IN_GRAPH_ORDER */
queue.compare = NULL;
break;
case REV_SORT_BY_COMMIT_DATE:
queue.compare = compare_commits_by_commit_date;
break;
case REV_SORT_BY_AUTHOR_DATE:
init_author_date_slab(&author_date);
queue.compare = compare_commits_by_author_date;
queue.cb_data = &author_date;
break;
}

/* Mark them and clear the indegree */
for (next = orig; next; next = next->next) {
struct commit *commit = next->item;
*(indegree_slab_at(&indegree, commit)) = 1;
/* also record the author dates, if needed */
if (sort_order == REV_SORT_BY_AUTHOR_DATE)
record_author_date(&author_date, commit);
}

/* update the indegree */
for (next = orig; next; next = next->next) {
struct commit_list * parents = next->item->parents;
struct commit_list *parents = next->item->parents;
while (parents) {
struct commit *parent = parents->item;
int *pi = indegree_slab_at(&indegree, parent);
Expand All @@ -560,30 +654,28 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
*
* the tips serve as a starting set for the work queue.
*/
work = NULL;
insert = &work;
for (next = orig; next; next = next->next) {
struct commit *commit = next->item;

if (*(indegree_slab_at(&indegree, commit)) == 1)
insert = &commit_list_insert(commit, insert)->next;
prio_queue_put(&queue, commit);
}

/* process the list in topological order */
if (!lifo)
commit_list_sort_by_date(&work);
/*
* This is unfortunate; the initial tips need to be shown
* in the order given from the revision traversal machinery.
*/
if (sort_order == REV_SORT_IN_GRAPH_ORDER)
prio_queue_reverse(&queue);

/* We no longer need the commit list */
free_commit_list(orig);

pptr = list;
*list = NULL;
while (work) {
struct commit *commit;
struct commit_list *parents, *work_item;

work_item = work;
work = work_item->next;
work_item->next = NULL;
while ((commit = prio_queue_get(&queue)) != NULL) {
struct commit_list *parents;

commit = work_item->item;
for (parents = commit->parents; parents ; parents = parents->next) {
struct commit *parent = parents->item;
int *pi = indegree_slab_at(&indegree, parent);
Expand All @@ -596,23 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
* when all their children have been emitted thereby
* guaranteeing topological order.
*/
if (--(*pi) == 1) {
if (!lifo)
commit_list_insert_by_date(parent, &work);
else
commit_list_insert(parent, &work);
}
if (--(*pi) == 1)
prio_queue_put(&queue, parent);
}
/*
* work_item is a commit all of whose children
* have already been emitted. we can emit it now.
* all children of commit have already been
* emitted. we can emit it now.
*/
*(indegree_slab_at(&indegree, commit)) = 0;
*pptr = work_item;
pptr = &work_item->next;

pptr = &commit_list_insert(commit, pptr)->next;
}

clear_indegree_slab(&indegree);
clear_prio_queue(&queue);
if (sort_order == REV_SORT_BY_AUTHOR_DATE)
clear_author_date_slab(&author_date);
}

/* merge-base stuff */
Expand Down
15 changes: 12 additions & 3 deletions commit.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,15 +142,24 @@ void clear_commit_marks(struct commit *commit, unsigned int mark);
void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);


enum rev_sort_order {
REV_SORT_IN_GRAPH_ORDER = 0,
REV_SORT_BY_COMMIT_DATE,
REV_SORT_BY_AUTHOR_DATE
};

/*
* Performs an in-place topological sort of list supplied.
*
* invariant of resulting list is:
* a reachable from b => ord(b) < ord(a)
* in addition, when lifo == 0, commits on parallel tracks are
* sorted in the dates order.
* sort_order further specifies:
* REV_SORT_IN_GRAPH_ORDER: try to show a commit on a single-parent
* chain together.
* REV_SORT_BY_COMMIT_DATE: show eligible commits in committer-date order.
*/
void sort_in_topological_order(struct commit_list ** list, int lifo);
void sort_in_topological_order(struct commit_list **, enum rev_sort_order);

struct commit_graft {
unsigned char sha1[20];
Expand Down
Loading

0 comments on commit 534f0e0

Please sign in to comment.