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
This commit is contained in:
Junio C Hamano 2013-07-01 12:41:22 -07:00
commit 534f0e0996
16 changed files with 549 additions and 162 deletions

1
.gitignore vendored
View file

@ -191,6 +191,7 @@
/test-mktemp /test-mktemp
/test-parse-options /test-parse-options
/test-path-utils /test-path-utils
/test-prio-queue
/test-read-cache /test-read-cache
/test-regex /test-regex
/test-revision-walking /test-revision-walking

View file

@ -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 Show no parents before all of its children are shown, but
otherwise show commits in the commit timestamp order. 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:: --topo-order::
Show no parents before all of its children are shown, and Show no parents before all of its children are shown, and
avoid showing commits on multiple lines of history avoid showing commits on multiple lines of history

View file

@ -569,6 +569,7 @@ TEST_PROGRAMS_NEED_X += test-mergesort
TEST_PROGRAMS_NEED_X += test-mktemp TEST_PROGRAMS_NEED_X += test-mktemp
TEST_PROGRAMS_NEED_X += test-parse-options TEST_PROGRAMS_NEED_X += test-parse-options
TEST_PROGRAMS_NEED_X += test-path-utils 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-read-cache
TEST_PROGRAMS_NEED_X += test-regex TEST_PROGRAMS_NEED_X += test-regex
TEST_PROGRAMS_NEED_X += test-revision-walking TEST_PROGRAMS_NEED_X += test-revision-walking
@ -705,6 +706,7 @@ LIB_H += parse-options.h
LIB_H += patch-ids.h LIB_H += patch-ids.h
LIB_H += pathspec.h LIB_H += pathspec.h
LIB_H += pkt-line.h LIB_H += pkt-line.h
LIB_H += prio-queue.h
LIB_H += progress.h LIB_H += progress.h
LIB_H += prompt.h LIB_H += prompt.h
LIB_H += quote.h LIB_H += quote.h
@ -846,6 +848,7 @@ LIB_OBJS += pathspec.o
LIB_OBJS += pkt-line.o LIB_OBJS += pkt-line.o
LIB_OBJS += preload-index.o LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o LIB_OBJS += pretty.o
LIB_OBJS += prio-queue.o
LIB_OBJS += progress.o LIB_OBJS += progress.o
LIB_OBJS += prompt.o LIB_OBJS += prompt.o
LIB_OBJS += quote.o LIB_OBJS += quote.o

View file

@ -237,7 +237,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output; int i = revs->early_output;
int show_header = 1; int show_header = 1;
sort_in_topological_order(&list, revs->lifo); sort_in_topological_order(&list, revs->sort_order);
while (list && i) { while (list && i) {
struct commit *commit = list->item; struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) { switch (simplify_commit(revs, commit)) {

View file

@ -630,7 +630,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
int num_rev, i, extra = 0; int num_rev, i, extra = 0;
int all_heads = 0, all_remotes = 0; int all_heads = 0, all_remotes = 0;
int all_mask, all_revs; int all_mask, all_revs;
int lifo = 1; enum rev_sort_order sort_order = REV_SORT_IN_GRAPH_ORDER;
char head[128]; char head[128];
const char *head_p; const char *head_p;
int head_len; int head_len;
@ -665,15 +665,17 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
N_("show possible merge bases")), N_("show possible merge bases")),
OPT_BOOLEAN(0, "independent", &independent, OPT_BOOLEAN(0, "independent", &independent,
N_("show refs unreachable from any other ref")), N_("show refs unreachable from any other ref")),
OPT_BOOLEAN(0, "topo-order", &lifo, OPT_SET_INT(0, "topo-order", &sort_order,
N_("show commits in topological order")), N_("show commits in topological order"),
REV_SORT_IN_GRAPH_ORDER),
OPT_BOOLEAN(0, "topics", &topics, OPT_BOOLEAN(0, "topics", &topics,
N_("show only commits not on the first branch")), N_("show only commits not on the first branch")),
OPT_SET_INT(0, "sparse", &dense, OPT_SET_INT(0, "sparse", &dense,
N_("show merges reachable from only one tip"), 0), 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 " 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>]"), { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"),
N_("show <n> most recent ref-log entries starting at " N_("show <n> most recent ref-log entries starting at "
"base"), "base"),
@ -900,7 +902,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
exit(0); exit(0);
/* Sort topologically */ /* Sort topologically */
sort_in_topological_order(&seen, lifo); sort_in_topological_order(&seen, sort_order);
/* Give names to commits */ /* Give names to commits */
if (!sha1_name && !no_name) if (!sha1_name && !no_name)

145
commit.c
View file

@ -9,6 +9,7 @@
#include "gpg-interface.h" #include "gpg-interface.h"
#include "mergesort.h" #include "mergesort.h"
#include "commit-slab.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 **); static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **);
@ -518,31 +519,124 @@ struct commit *pop_commit(struct commit_list **stack)
/* count number of children that have not been emitted */ /* count number of children that have not been emitted */
define_commit_slab(indegree_slab, int); 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. * 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 *next, *orig = *list;
struct commit_list *work, **insert;
struct commit_list **pptr; struct commit_list **pptr;
struct indegree_slab indegree; struct indegree_slab indegree;
struct prio_queue queue;
struct commit *commit;
struct author_date_slab author_date;
if (!orig) if (!orig)
return; return;
*list = NULL; *list = NULL;
init_indegree_slab(&indegree); 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 */ /* Mark them and clear the indegree */
for (next = orig; next; next = next->next) { for (next = orig; next; next = next->next) {
struct commit *commit = next->item; struct commit *commit = next->item;
*(indegree_slab_at(&indegree, commit)) = 1; *(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 */ /* update the indegree */
for (next = orig; next; next = next->next) { for (next = orig; next; next = next->next) {
struct commit_list * parents = next->item->parents; struct commit_list *parents = next->item->parents;
while (parents) { while (parents) {
struct commit *parent = parents->item; struct commit *parent = parents->item;
int *pi = indegree_slab_at(&indegree, parent); int *pi = indegree_slab_at(&indegree, parent);
@ -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. * the tips serve as a starting set for the work queue.
*/ */
work = NULL;
insert = &work;
for (next = orig; next; next = next->next) { for (next = orig; next; next = next->next) {
struct commit *commit = next->item; struct commit *commit = next->item;
if (*(indegree_slab_at(&indegree, commit)) == 1) 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) * This is unfortunate; the initial tips need to be shown
commit_list_sort_by_date(&work); * 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; pptr = list;
*list = NULL; *list = NULL;
while (work) { while ((commit = prio_queue_get(&queue)) != NULL) {
struct commit *commit; struct commit_list *parents;
struct commit_list *parents, *work_item;
work_item = work;
work = work_item->next;
work_item->next = NULL;
commit = work_item->item;
for (parents = commit->parents; parents ; parents = parents->next) { for (parents = commit->parents; parents ; parents = parents->next) {
struct commit *parent = parents->item; struct commit *parent = parents->item;
int *pi = indegree_slab_at(&indegree, parent); int *pi = indegree_slab_at(&indegree, parent);
@ -596,23 +688,22 @@ void sort_in_topological_order(struct commit_list ** list, int lifo)
* when all their children have been emitted thereby * when all their children have been emitted thereby
* guaranteeing topological order. * guaranteeing topological order.
*/ */
if (--(*pi) == 1) { if (--(*pi) == 1)
if (!lifo) prio_queue_put(&queue, parent);
commit_list_insert_by_date(parent, &work);
else
commit_list_insert(parent, &work);
}
} }
/* /*
* work_item is a commit all of whose children * all children of commit have already been
* have already been emitted. we can emit it now. * emitted. we can emit it now.
*/ */
*(indegree_slab_at(&indegree, commit)) = 0; *(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_indegree_slab(&indegree);
clear_prio_queue(&queue);
if (sort_order == REV_SORT_BY_AUTHOR_DATE)
clear_author_date_slab(&author_date);
} }
/* merge-base stuff */ /* merge-base stuff */

View file

@ -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_many(int nr, struct commit **commit, unsigned int mark);
void clear_commit_marks_for_object_array(struct object_array *a, unsigned 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. * Performs an in-place topological sort of list supplied.
* *
* invariant of resulting list is: * invariant of resulting list is:
* a reachable from b => ord(b) < ord(a) * a reachable from b => ord(b) < ord(a)
* in addition, when lifo == 0, commits on parallel tracks are * sort_order further specifies:
* sorted in the dates order. * 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 { struct commit_graft {
unsigned char sha1[20]; unsigned char sha1[20];

84
prio-queue.c Normal file
View file

@ -0,0 +1,84 @@
#include "cache.h"
#include "commit.h"
#include "prio-queue.h"
void prio_queue_reverse(struct prio_queue *queue)
{
int i, j;
if (queue->compare != NULL)
die("BUG: prio_queue_reverse() on non-LIFO queue");
for (i = 0; i <= (j = (queue->nr - 1) - i); i++) {
struct commit *swap = queue->array[i];
queue->array[i] = queue->array[j];
queue->array[j] = swap;
}
}
void clear_prio_queue(struct prio_queue *queue)
{
free(queue->array);
queue->nr = 0;
queue->alloc = 0;
queue->array = NULL;
}
void prio_queue_put(struct prio_queue *queue, void *thing)
{
prio_queue_compare_fn compare = queue->compare;
int ix, parent;
/* Append at the end */
ALLOC_GROW(queue->array, queue->nr + 1, queue->alloc);
queue->array[queue->nr++] = thing;
if (!compare)
return; /* LIFO */
/* Bubble up the new one */
for (ix = queue->nr - 1; ix; ix = parent) {
parent = (ix - 1) / 2;
if (compare(queue->array[parent], queue->array[ix],
queue->cb_data) <= 0)
break;
thing = queue->array[parent];
queue->array[parent] = queue->array[ix];
queue->array[ix] = thing;
}
}
void *prio_queue_get(struct prio_queue *queue)
{
void *result, *swap;
int ix, child;
prio_queue_compare_fn compare = queue->compare;
if (!queue->nr)
return NULL;
if (!compare)
return queue->array[--queue->nr]; /* LIFO */
result = queue->array[0];
if (!--queue->nr)
return result;
queue->array[0] = queue->array[queue->nr];
/* Push down the one at the root */
for (ix = 0; ix * 2 + 1 < queue->nr; ix = child) {
child = ix * 2 + 1; /* left */
if ((child + 1 < queue->nr) &&
(compare(queue->array[child], queue->array[child + 1],
queue->cb_data) >= 0))
child++; /* use right child */
if (compare(queue->array[ix], queue->array[child],
queue->cb_data) <= 0)
break;
swap = queue->array[child];
queue->array[child] = queue->array[ix];
queue->array[ix] = swap;
}
return result;
}

48
prio-queue.h Normal file
View file

@ -0,0 +1,48 @@
#ifndef PRIO_QUEUE_H
#define PRIO_QUEUE_H
/*
* A priority queue implementation, primarily for keeping track of
* commits in the 'date-order' so that we process them from new to old
* as they are discovered, but can be used to hold any pointer to
* struct. The caller is responsible for supplying a function to
* compare two "things".
*
* Alternatively, this data structure can also be used as a LIFO stack
* by specifying NULL as the comparison function.
*/
/*
* Compare two "things", one and two; the third parameter is cb_data
* in the prio_queue structure. The result is returned as a sign of
* the return value, being the same as the sign of the result of
* subtracting "two" from "one" (i.e. negative if "one" sorts earlier
* than "two").
*/
typedef int (*prio_queue_compare_fn)(const void *one, const void *two, void *cb_data);
struct prio_queue {
prio_queue_compare_fn compare;
void *cb_data;
int alloc, nr;
void **array;
};
/*
* Add the "thing" to the queue.
*/
extern void prio_queue_put(struct prio_queue *, void *thing);
/*
* Extract the "thing" that compares the smallest out of the queue,
* or NULL. If compare function is NULL, the queue acts as a LIFO
* stack.
*/
extern void *prio_queue_get(struct prio_queue *);
extern void clear_prio_queue(struct prio_queue *);
/* Reverse the LIFO elements */
extern void prio_queue_reverse(struct prio_queue *);
#endif /* PRIO_QUEUE_H */

View file

@ -1296,7 +1296,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
DIFF_OPT_SET(&revs->pruning, QUICK); DIFF_OPT_SET(&revs->pruning, QUICK);
revs->pruning.add_remove = file_add_remove; revs->pruning.add_remove = file_add_remove;
revs->pruning.change = file_change; revs->pruning.change = file_change;
revs->lifo = 1; revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
revs->dense = 1; revs->dense = 1;
revs->prefix = prefix; revs->prefix = prefix;
revs->max_age = -1; revs->max_age = -1;
@ -1638,7 +1638,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if (!strcmp(arg, "--merge")) { } else if (!strcmp(arg, "--merge")) {
revs->show_merge = 1; revs->show_merge = 1;
} else if (!strcmp(arg, "--topo-order")) { } else if (!strcmp(arg, "--topo-order")) {
revs->lifo = 1; revs->sort_order = REV_SORT_IN_GRAPH_ORDER;
revs->topo_order = 1; revs->topo_order = 1;
} else if (!strcmp(arg, "--simplify-merges")) { } else if (!strcmp(arg, "--simplify-merges")) {
revs->simplify_merges = 1; revs->simplify_merges = 1;
@ -1656,7 +1656,10 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->prune = 1; revs->prune = 1;
load_ref_decorations(DECORATE_SHORT_REFS); load_ref_decorations(DECORATE_SHORT_REFS);
} else if (!strcmp(arg, "--date-order")) { } else if (!strcmp(arg, "--date-order")) {
revs->lifo = 0; revs->sort_order = REV_SORT_BY_COMMIT_DATE;
revs->topo_order = 1;
} else if (!strcmp(arg, "--author-date-order")) {
revs->sort_order = REV_SORT_BY_AUTHOR_DATE;
revs->topo_order = 1; revs->topo_order = 1;
} else if (!prefixcmp(arg, "--early-output")) { } else if (!prefixcmp(arg, "--early-output")) {
int count = 100; int count = 100;
@ -2606,7 +2609,7 @@ int prepare_revision_walk(struct rev_info *revs)
if (limit_list(revs) < 0) if (limit_list(revs) < 0)
return -1; return -1;
if (revs->topo_order) if (revs->topo_order)
sort_in_topological_order(&revs->commits, revs->lifo); sort_in_topological_order(&revs->commits, revs->sort_order);
if (revs->line_level_traverse) if (revs->line_level_traverse)
line_log_filter(revs); line_log_filter(revs);
if (revs->simplify_merges) if (revs->simplify_merges)
@ -2924,7 +2927,7 @@ static void create_boundary_commit_list(struct rev_info *revs)
* If revs->topo_order is set, sort the boundary commits * If revs->topo_order is set, sort the boundary commits
* in topological order * in topological order
*/ */
sort_in_topological_order(&revs->commits, revs->lifo); sort_in_topological_order(&revs->commits, revs->sort_order);
} }
static struct commit *get_revision_internal(struct rev_info *revs) static struct commit *get_revision_internal(struct rev_info *revs)

View file

@ -4,6 +4,7 @@
#include "parse-options.h" #include "parse-options.h"
#include "grep.h" #include "grep.h"
#include "notes.h" #include "notes.h"
#include "commit.h"
#define SEEN (1u<<0) #define SEEN (1u<<0)
#define UNINTERESTING (1u<<1) #define UNINTERESTING (1u<<1)
@ -62,6 +63,10 @@ struct rev_info {
const char *prefix; const char *prefix;
const char *def; const char *def;
struct pathspec prune_data; struct pathspec prune_data;
/* topo-sort */
enum rev_sort_order sort_order;
unsigned int early_output:1, unsigned int early_output:1,
ignore_missing:1; ignore_missing:1;
@ -72,7 +77,6 @@ struct rev_info {
show_all:1, show_all:1,
remove_empty_trees:1, remove_empty_trees:1,
simplify_history:1, simplify_history:1,
lifo:1,
topo_order:1, topo_order:1,
simplify_merges:1, simplify_merges:1,
simplify_by_decoration:1, simplify_by_decoration:1,

View file

@ -1,55 +1,50 @@
: included from 6002 and others : included from 6002 and others
[ -d .git/refs/tags ] || mkdir -p .git/refs/tags mkdir -p .git/refs/tags
:> sed.script >sed.script
# Answer the sha1 has associated with the tag. The tag must exist in .git or .git/refs/tags # Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags
tag() tag () {
{
_tag=$1 _tag=$1
[ -f .git/refs/tags/$_tag ] || error "tag: \"$_tag\" does not exist" test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist"
cat .git/refs/tags/$_tag cat ".git/refs/tags/$_tag"
} }
# Generate a commit using the text specified to make it unique and the tree # Generate a commit using the text specified to make it unique and the tree
# named by the tag specified. # named by the tag specified.
unique_commit() unique_commit () {
{
_text=$1 _text=$1
_tree=$2 _tree=$2
shift 2 shift 2
echo $_text | git commit-tree $(tag $_tree) "$@" echo "$_text" | git commit-tree $(tag "$_tree") "$@"
} }
# Save the output of a command into the tag specified. Prepend # Save the output of a command into the tag specified. Prepend
# a substitution script for the tag onto the front of sed.script # a substitution script for the tag onto the front of sed.script
save_tag() save_tag () {
{
_tag=$1 _tag=$1
[ -n "$_tag" ] || error "usage: save_tag tag commit-args ..." test -n "$_tag" || error "usage: save_tag tag commit-args ..."
shift 1 shift 1
"$@" >.git/refs/tags/$_tag "$@" >".git/refs/tags/$_tag"
echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp echo "s/$(tag $_tag)/$_tag/g" >sed.script.tmp
cat sed.script >> sed.script.tmp cat sed.script >>sed.script.tmp
rm sed.script rm sed.script
mv sed.script.tmp sed.script mv sed.script.tmp sed.script
} }
# Replace unhelpful sha1 hashses with their symbolic equivalents # Replace unhelpful sha1 hashses with their symbolic equivalents
entag() entag () {
{
sed -f sed.script sed -f sed.script
} }
# Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL # Execute a command after first saving, then setting the GIT_AUTHOR_EMAIL
# tag to a specified value. Restore the original value on return. # tag to a specified value. Restore the original value on return.
as_author() as_author () {
{
_author=$1 _author=$1
shift 1 shift 1
_save=$GIT_AUTHOR_EMAIL _save=$GIT_AUTHOR_EMAIL
GIT_AUTHOR_EMAIL="$_author" GIT_AUTHOR_EMAIL="$_author"
export GIT_AUTHOR_EMAIL export GIT_AUTHOR_EMAIL
@ -63,45 +58,58 @@ as_author()
fi fi
} }
commit_date() commit_date () {
{ _commit=$1
_commit=$1 git cat-file commit $_commit |
git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
} }
on_committer_date() # Assign the value of fake date to a variable, but
{ # allow fairly common "1971-08-16 00:00" to be omittd
_date=$1 assign_fake_date () {
shift 1 case "$2" in
GIT_COMMITTER_DATE="$_date" ??:??:??) eval "$1='1971-08-16 $2'" ;;
export GIT_COMMITTER_DATE ??:??) eval "$1='1971-08-16 00:$2'" ;;
"$@" ??) eval "$1='1971-08-16 00:00:$2'" ;;
unset GIT_COMMITTER_DATE *) eval "$1='$2'" ;;
esac
}
on_committer_date () {
assign_fake_date GIT_COMMITTER_DATE "$1"
export GIT_COMMITTER_DATE
shift 1
"$@"
}
on_dates () {
assign_fake_date GIT_COMMITTER_DATE "$1"
assign_fake_date GIT_AUTHOR_DATE "$2"
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
shift 2
"$@"
} }
# Execute a command and suppress any error output. # Execute a command and suppress any error output.
hide_error() hide_error () {
{
"$@" 2>/dev/null "$@" 2>/dev/null
} }
check_output() check_output () {
{
_name=$1 _name=$1
shift 1 shift 1
if eval "$*" | entag > $_name.actual if eval "$*" | entag >"$_name.actual"
then then
test_cmp $_name.expected $_name.actual test_cmp "$_name.expected" "$_name.actual"
else else
return 1; return 1
fi fi
} }
# Turn a reasonable test description into a reasonable test name. # Turn a reasonable test description into a reasonable test name.
# All alphanums translated into -'s which are then compressed and stripped # All alphanums translated into -'s which are then compressed and stripped
# from front and back. # from front and back.
name_from_description() name_from_description () {
{
perl -pe ' perl -pe '
s/[^A-Za-z0-9.]/-/g; s/[^A-Za-z0-9.]/-/g;
s/-+/-/g; s/-+/-/g;
@ -119,9 +127,11 @@ name_from_description()
test_output_expect_success() test_output_expect_success()
{ {
_description=$1 _description=$1
_test=$2 _test=$2
[ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF" test $# -eq 2 ||
_name=$(echo $_description | name_from_description) error "usage: test_output_expect_success description test <<EOF ... EOF"
cat > $_name.expected
_name=$(echo $_description | name_from_description)
cat >"$_name.expected"
test_expect_success "$_description" "check_output $_name \"$_test\"" test_expect_success "$_description" "check_output $_name \"$_test\""
} }

50
t/t0009-prio-queue.sh Executable file
View file

@ -0,0 +1,50 @@
#!/bin/sh
test_description='basic tests for priority queue implementation'
. ./test-lib.sh
cat >expect <<'EOF'
1
2
3
4
5
5
6
7
8
9
10
EOF
test_expect_success 'basic ordering' '
test-prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual &&
test_cmp expect actual
'
cat >expect <<'EOF'
2
3
4
1
5
6
EOF
test_expect_success 'mixed put and get' '
test-prio-queue 6 2 4 get 5 3 get get 1 dump >actual &&
test_cmp expect actual
'
cat >expect <<'EOF'
1
2
NULL
1
2
NULL
EOF
test_expect_success 'notice empty queue' '
test-prio-queue 1 2 get get get 1 2 get get get >actual &&
test_cmp expect actual
'
test_done

View file

@ -39,25 +39,25 @@ test_bisection_diff()
date >path0 date >path0
git update-index --add path0 git update-index --add path0
save_tag tree git write-tree save_tag tree git write-tree
on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree on_committer_date "00:00" hide_error save_tag root unique_commit root tree
on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root on_committer_date "00:01" save_tag l0 unique_commit l0 tree -p root
on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 on_committer_date "00:02" save_tag l1 unique_commit l1 tree -p l0
on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 on_committer_date "00:03" save_tag l2 unique_commit l2 tree -p l1
on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 on_committer_date "00:04" save_tag a0 unique_commit a0 tree -p l2
on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 on_committer_date "00:05" save_tag a1 unique_commit a1 tree -p a0
on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 on_committer_date "00:06" save_tag b1 unique_commit b1 tree -p a0
on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 on_committer_date "00:07" save_tag c1 unique_commit c1 tree -p b1
on_committer_date "1971-08-16 00:00:08" save_tag b2 unique_commit b2 tree -p b1 on_committer_date "00:08" save_tag b2 unique_commit b2 tree -p b1
on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2 on_committer_date "00:09" save_tag b3 unique_commit b2 tree -p b2
on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 on_committer_date "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 on_committer_date "00:11" save_tag c3 unique_commit c3 tree -p c2
on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 on_committer_date "00:12" save_tag a2 unique_commit a2 tree -p a1
on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 on_committer_date "00:13" save_tag a3 unique_commit a3 tree -p a2
on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 on_committer_date "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 on_committer_date "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 on_committer_date "00:16" save_tag l3 unique_commit l3 tree -p a4
on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 on_committer_date "00:17" save_tag l4 unique_commit l4 tree -p l3
on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 on_committer_date "00:18" save_tag l5 unique_commit l5 tree -p l4
git update-ref HEAD $(tag l5) git update-ref HEAD $(tag l5)
@ -90,29 +90,29 @@ git update-ref HEAD $(tag l5)
# F # F
on_committer_date "1971-08-16 00:00:00" hide_error save_tag F unique_commit F tree on_committer_date "00:00" hide_error save_tag F unique_commit F tree
on_committer_date "1971-08-16 00:00:01" save_tag e8 unique_commit e8 tree -p F on_committer_date "00:01" save_tag e8 unique_commit e8 tree -p F
on_committer_date "1971-08-16 00:00:02" save_tag e7 unique_commit e7 tree -p e8 on_committer_date "00:02" save_tag e7 unique_commit e7 tree -p e8
on_committer_date "1971-08-16 00:00:03" save_tag e6 unique_commit e6 tree -p e7 on_committer_date "00:03" save_tag e6 unique_commit e6 tree -p e7
on_committer_date "1971-08-16 00:00:04" save_tag e5 unique_commit e5 tree -p e6 on_committer_date "00:04" save_tag e5 unique_commit e5 tree -p e6
on_committer_date "1971-08-16 00:00:05" save_tag f4 unique_commit f4 tree -p F on_committer_date "00:05" save_tag f4 unique_commit f4 tree -p F
on_committer_date "1971-08-16 00:00:06" save_tag f3 unique_commit f3 tree -p f4 on_committer_date "00:06" save_tag f3 unique_commit f3 tree -p f4
on_committer_date "1971-08-16 00:00:07" save_tag f2 unique_commit f2 tree -p f3 on_committer_date "00:07" save_tag f2 unique_commit f2 tree -p f3
on_committer_date "1971-08-16 00:00:08" save_tag f1 unique_commit f1 tree -p f2 on_committer_date "00:08" save_tag f1 unique_commit f1 tree -p f2
on_committer_date "1971-08-16 00:00:09" save_tag e4 unique_commit e4 tree -p e5 on_committer_date "00:09" save_tag e4 unique_commit e4 tree -p e5
on_committer_date "1971-08-16 00:00:10" save_tag e3 unique_commit e3 tree -p e4 on_committer_date "00:10" save_tag e3 unique_commit e3 tree -p e4
on_committer_date "1971-08-16 00:00:11" save_tag e2 unique_commit e2 tree -p e3 on_committer_date "00:11" save_tag e2 unique_commit e2 tree -p e3
on_committer_date "1971-08-16 00:00:12" save_tag e1 unique_commit e1 tree -p e2 on_committer_date "00:12" save_tag e1 unique_commit e1 tree -p e2
on_committer_date "1971-08-16 00:00:13" save_tag E unique_commit E tree -p e1 -p f1 on_committer_date "00:13" save_tag E unique_commit E tree -p e1 -p f1
on_committer_date "1971-08-16 00:00:00" hide_error save_tag U unique_commit U tree on_committer_date "00:00" hide_error save_tag U unique_commit U tree
on_committer_date "1971-08-16 00:00:01" save_tag u0 unique_commit u0 tree -p U on_committer_date "00:01" save_tag u0 unique_commit u0 tree -p U
on_committer_date "1971-08-16 00:00:01" save_tag u1 unique_commit u1 tree -p u0 on_committer_date "00:01" save_tag u1 unique_commit u1 tree -p u0
on_committer_date "1971-08-16 00:00:02" save_tag u2 unique_commit u2 tree -p u0 on_committer_date "00:02" save_tag u2 unique_commit u2 tree -p u0
on_committer_date "1971-08-16 00:00:03" save_tag u3 unique_commit u3 tree -p u0 on_committer_date "00:03" save_tag u3 unique_commit u3 tree -p u0
on_committer_date "1971-08-16 00:00:04" save_tag u4 unique_commit u4 tree -p u0 on_committer_date "00:04" save_tag u4 unique_commit u4 tree -p u0
on_committer_date "1971-08-16 00:00:05" save_tag u5 unique_commit u5 tree -p u0 on_committer_date "00:05" save_tag u5 unique_commit u5 tree -p u0
on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5 on_committer_date "00:06" save_tag V unique_commit V tree -p u1 -p u2 -p u3 -p u4 -p u5
test_sequence() test_sequence()
{ {

View file

@ -16,39 +16,34 @@ list_duplicates()
date >path0 date >path0
git update-index --add path0 git update-index --add path0
save_tag tree git write-tree save_tag tree git write-tree
on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree on_dates "00:00" "00:00" hide_error save_tag root unique_commit root tree
on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root on_dates "00:01" "00:01" save_tag l0 unique_commit l0 tree -p root
on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 on_dates "00:02" "00:02" save_tag l1 unique_commit l1 tree -p l0
on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 on_dates "00:03" "00:03" save_tag l2 unique_commit l2 tree -p l1
on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 on_dates "00:04" "00:04" save_tag a0 unique_commit a0 tree -p l2
on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 on_dates "00:05" "00:05" save_tag a1 unique_commit a1 tree -p a0
on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 on_dates "00:06" "00:06" save_tag b1 unique_commit b1 tree -p a0
on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 on_dates "00:07" "00:07" save_tag c1 unique_commit c1 tree -p b1
on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1 on_dates "00:08" "00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b3 tree -p b2 on_dates "00:09" "00:09" save_tag b3 unique_commit b3 tree -p b2
on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 on_dates "00:10" "00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 on_dates "00:11" "00:11" save_tag c3 unique_commit c3 tree -p c2
on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 on_dates "00:12" "00:00" save_tag a2 unique_commit a2 tree -p a1
on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 on_dates "00:13" "00:01" save_tag a3 unique_commit a3 tree -p a2
on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 on_dates "00:14" "00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 on_dates "00:15" "00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 on_dates "00:16" "00:16" save_tag l3 unique_commit l3 tree -p a4
on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 on_dates "00:17" "00:17" save_tag l4 unique_commit l4 tree -p l3
on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 on_dates "00:18" "00:18" save_tag l5 unique_commit l5 tree -p l4
on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3 on_dates "00:19" "00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4 on_dates "00:20" "00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree on_dates "00:21" "00:21" hide_error save_tag alt_root unique_commit alt_root tree
on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root on_dates "00:22" "00:22" save_tag r0 unique_commit r0 tree -p alt_root
on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0 on_dates "00:23" "00:23" save_tag r1 unique_commit r1 tree -p r0
on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1 on_dates "00:24" "00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5 on_dates "00:25" "00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
#
# note: as of 20/6, it isn't possible to create duplicate parents, so this
# can't be tested.
#
#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
hide_error save_tag e1 as_author e@example.com unique_commit e1 tree hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
save_tag e2 as_author e@example.com unique_commit e2 tree -p e1 save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
save_tag f1 as_author f@example.com unique_commit f1 tree -p e1 save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
@ -105,6 +100,50 @@ l0
root root
EOF EOF
test_output_expect_success 'simple date order' 'git rev-list --date-order HEAD' <<EOF
l5
l4
l3
a4
b4
a3
a2
c3
c2
b3
b2
c1
b1
a1
a0
l2
l1
l0
root
EOF
test_output_expect_success 'simple author-date order' 'git rev-list --author-date-order HEAD' <<EOF
l5
l4
l3
a4
b4
c3
c2
b3
b2
c1
b1
a3
a2
a1
a0
l2
l1
l0
root
EOF
test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order g4' <<EOF test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order g4' <<EOF
g4 g4
h2 h2

39
test-prio-queue.c Normal file
View file

@ -0,0 +1,39 @@
#include "cache.h"
#include "prio-queue.h"
static int intcmp(const void *va, const void *vb, void *data)
{
const int *a = va, *b = vb;
return *a - *b;
}
static void show(int *v)
{
if (!v)
printf("NULL\n");
else
printf("%d\n", *v);
free(v);
}
int main(int argc, char **argv)
{
struct prio_queue pq = { intcmp };
while (*++argv) {
if (!strcmp(*argv, "get"))
show(prio_queue_get(&pq));
else if (!strcmp(*argv, "dump")) {
int *v;
while ((v = prio_queue_get(&pq)))
show(v);
}
else {
int *v = malloc(sizeof(*v));
*v = atoi(*argv);
prio_queue_put(&pq, v);
}
}
return 0;
}