* [PATCH] Add support for --wrt flag to git-rev-list
@ 2005-06-07 6:00 Jon Seymour
0 siblings, 0 replies; only message in thread
From: Jon Seymour @ 2005-06-07 6:00 UTC (permalink / raw)
To: git; +Cc: torvalds, jon.seymour
[PATCH] Add support for --wrt flag to git-rev-list
This patch adds support for a --wrt flag to git-rev-list.
When --wrt=author@domain is specified, --merge-order is implied but
the linearisation of non-linear epochs is adjusted so that branches
contributed to by author@domain sort after branches contributed by
others. This has effect of presenting a linear history changes as seen
by author@domain's workspace.
For example, given this commit history:
a4 ---
| \ \
| b4 |
|/ | |
a3 | |
| | |
a2 | |
| | c3
| | |
| | c2
| b3 |
| | /|
| b2 |
| | c1
| | /
| b1 <-- commit authored by author@domain
a1 |
| |
a0 |
| /
root
--merge-order alone would sort it as follows:
= a4
| c3
| c2
| c1
^ b4
| b3
| b2
| b1
^ a3
| a2
| a1
| a0
= root
however, with --wrt=author@domain, git-rev-list sorts as follows:
= a4
| c3
| c2
| c1
^ b4
| a3
| a2
| a1
| a0
^ b3
| b2
| b1
= root
To support this change, some additional functions were added to commit.c to allow
the extraction of author from the commit header.
Also, --show-breaks, like --wrt now implies --merge-order rather than requires it.
This change also updates Documentation/git-rev-list.txt to include documentation for
--wrt and other git-rev-list options not currently documented.
A test case has been included in t/t6000-rev-list.sh to verify this change works as
expected.
Use of --wrt implies a small overhead (linear with respect to nodes printed) compared with --merge-order.
Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
Diverged from ed37b5b2b94398f3ab8312dfdf23cfd25549e3ec by Linus Torvalds <torvalds@ppc970.osdl.org>
---
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -9,13 +9,25 @@ git-rev-list - Lists commit objects in r
SYNOPSIS
--------
-'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--merge-order* [ *--show-breaks* ] ] <commit>
+'git-rev-list' [ *--max-count*=number ] [ *--max-age*=timestamp ] [ *--min-age*=timestamp ] [ *--pretty* ] [ *--parents* ] [ *--header* ] [ *--merge-order* [ *--show-breaks* ] ] [ *--wrt=author@domain* ] <head-to-include> <head-to-include> ...
+^<base-to-exclude>
+^<base-to-exclude> ...
DESCRIPTION
-----------
-Lists commit objects in reverse chronological order starting at the
-given commit, taking ancestry relationship into account. This is
-useful to produce human-readable log output.
+
+Lists commit objects in reverse chronological order starting at the commits specified by head-to-include values,
+taking ancestry relationship into account. git-rev-list will not print any commits that are reachable
+by any of the base-to-exclude values.
+
+If *--pretty* is specified, git-rev-list prints a short summary of each commit on stdout. This can be usefully
+parsed by git-shortlog to produce a short log of changes.
+
+If *--parents* is specified, git-rev-list prints a space separated list of parents on the same line as the
+commit identifier.
+
+If *--header* is specified, git-rev-list prints to stdout, on line after the identifier of each commit,
+the contents of the commit's header followed by a trailing NUL.
If *--merge-order* is specified, the commit history is decomposed into a unique sequence of minimal, non-linear
epochs and maximal, linear epochs. Non-linear epochs are then linearised by sorting them into merge order, which
@@ -42,17 +54,22 @@ Commits marked with (=) represent the bo
Commits marked with (|) are direct parents of commits immediately preceding the marked commit in the list.
Commits marked with (^) are not parents of the immediately preceding commit. These "breaks" represent necessary discontinuities implied by trying to represent an arbtirary DAG in a linear form.
-*--show-breaks* is only valid if *--merge-order* is also specified.
+*--show-breaks* implies *--merge-order*.
+
+If *--wrt=author@domain* is specified, then --merge-order is implied but git-rev-list sorts commits
+so that contemporaneously parallel commits (w.r.t. author@domain) appear to occur after (that is, sort before)
+any commits authored by author@domain. This reflects the order in which author@domain's workspace sees contemporaneously
+parallel changes.
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
+Original *--merge-order* and *--wrt* logic by Jon Seymour <jon.seymour@gmail.com>
Documentation
--------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+Documentation by David Greaves, Junio C Hamano, Jon Seymour and the git-list <git@vger.kernel.org>.
GIT
---
diff --git a/commit.c b/commit.c
--- a/commit.c
+++ b/commit.c
@@ -302,3 +302,65 @@ int count_parents(struct commit * commit
return count;
}
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len)
+{
+ char * p = commit->buffer;
+
+ while (*p) {
+
+ char * q = header;
+ if (*p == '\n') {
+ break;
+ }
+
+ int matched;
+
+ for (matched = 1; *p != ' ' && *p != '\n'; p++, q++) {
+ matched = matched & (*q==*p);
+ }
+
+ if (matched && index) {
+ // if we matched but we haven't seen the index'th element yet,
+ // just decrement the index then pretend we didn't match
+ index--;
+ matched = 0;
+ }
+
+ if (!matched) {
+
+ // skip to start of next header line
+
+ for (;*p!='\n';p++)
+ ;
+ p++;
+
+ } else {
+
+ int count = 0; // number of characters in value
+
+ if (*p == ' ') {
+ p++;
+
+ for (count = 0; *p != '\n'; p++, count++, len--) {
+ if (len > 0) {
+ *buffer++=*p;
+ }
+ }
+
+ }
+
+ if (len > 0) {
+ *buffer = 0;
+ }
+
+ return (len > 0) ? count+1 : -(count+1);
+
+ }
+ }
+ return 0;
+}
+
+int copy_author(struct commit * commit, char * buffer, int len)
+{
+ return copy_commit_header(commit, "author", 0, buffer, len);
+}
diff --git a/commit.h b/commit.h
--- a/commit.h
+++ b/commit.h
@@ -53,4 +53,17 @@ struct commit *pop_most_recent_commit(st
struct commit *pop_commit(struct commit_list **stack);
int count_parents(struct commit * commit);
+
+//
+// Copies the value of the (index+1)'th commit header matching the name specified
+// into the buffer supplied and append a trailing NUL.
+//
+// Returns n<0 if the buffer was too short (-n is the required length)
+// Returns 0 if the header doesn't exist.
+// Returns n>0 where n is the length of the copied zero-terminated value, including the terminating zero.
+//
+int copy_commit_header(struct commit * commit, char * header, int index, char * buffer, int len);
+
+// Copies the commit's author value into the buffer supplied. Return values as per copy_commit_header.
+int copy_author(struct commit * commit, char * buffer, int len);
#endif /* COMMIT_H */
diff --git a/epoch.c b/epoch.c
--- a/epoch.c
+++ b/epoch.c
@@ -464,17 +464,21 @@ static void sort_first_epoch(struct comm
// so we need to reverse this list to output the oldest (or most "local") commits last.
//
- for (parents = head->parents; parents; parents = parents->next)
- commit_list_insert(parents->item, &reversed_parents);
-
- //
- // todo: by sorting the parents in a different order, we can alter the
- // merge order to show contemporaneous changes in parallel branches
- // occurring after "local" changes. This is useful for a developer
- // when a developer wants to see all changes that were incorporated
- // into the same merge as her own changes occur after her own
- // changes.
- //
+ if (!head->object.util) {
+
+ for (parents = head->parents; parents; parents = parents->next)
+ commit_list_insert(parents->item, &reversed_parents);
+
+ } else {
+
+ // reverse the locally sorted parents and reset utility pointer to NULL
+
+ parents = (struct commit_list *)head->object.util;
+ head->object.util = NULL;
+ while (parents) {
+ commit_list_insert(pop_commit(&parents), &reversed_parents);
+ }
+ }
while (reversed_parents) {
@@ -555,13 +559,82 @@ static int emit_stack(struct commit_list
}
//
+// Sort a list of commits in local last order. A commit is "local" if any of its ancestors (except the base)
+// causes (*local_test)() to return a non-zero value.
+//
+// The sorted list is returned in *sorted. A side effect of this function is to set each object.util
+// pointer to a list of parents sorted in local_last order.
+//
+// Does nothing except set *sorted to NULL if either list or local_test are NULL.
+//
+static void sort_list_in_local_last_order(struct commit_list * list, local_test_func local_test, struct commit_list ** sorted)
+{
+ if (!list || !local_test) {
+ *sorted = NULL;
+ return;
+ }
+
+ struct commit_list * next;
+
+ for ( next = list ; next; next = next->next) {
+
+ struct commit * item = next->item;
+
+ if (item->object.flags & BOUNDARY || item->object.util) {
+ // we have already visited this item or we have
+ // reached the boundary.
+ continue;
+ }
+
+ if ((*local_test)(item)) {
+ item->object.flags |= LOCAL;
+ }
+
+ sort_list_in_local_last_order(item->parents, local_test, (struct commit_list **)&item->object.util);
+
+ struct commit_list * sorted_parents = (struct commit_list *)&item->object.util;
+
+ if (sorted_parents && (sorted_parents->item->object.flags & LOCAL)) {
+ item->object.flags |= LOCAL;
+ }
+ }
+
+ //
+ // Iterate over the original list and generate two lists - a list of the
+ // non-local parents and list of local parents. Then splice the list
+ // of local parents onto the end of the non-local parents to form
+ // a new list.
+ //
+
+ struct commit_list * non_local = NULL;
+ struct commit_list * local = NULL;
+ struct commit_list **p = &local;
+ struct commit_list **q = &non_local;
+
+ for (next = list; next; next = next->next) {
+
+ if (next->item->object.flags & LOCAL) {
+ *p = commit_list_insert(next->item, p);
+ p = &(*p)->next;
+ } else {
+ *q = commit_list_insert(next->item, q);
+ q = &(*q)->next;
+ }
+ }
+
+ *q = local;
+ *sorted = non_local;
+
+}
+
+//
// Sorts an arbitrary epoch into merge order by sorting each epoch
// of its epoch sequence into order.
//
// Note: this algorithm currently leaves traces of its execution in the
// object flags of nodes it discovers. This should probably be fixed.
//
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
+static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter, local_test_func local_test)
{
struct commit *next = head_of_epoch;
int ret = 0;
@@ -602,6 +675,9 @@ static int sort_in_merge_order(struct co
} else {
struct commit_list *stack = NULL;
+
+ sort_list_in_local_last_order(next->parents, local_test, (struct commit_list **)&next->object.util);
+
sort_first_epoch(next, &stack);
action = emit_stack(&stack, emitter);
next = base;
@@ -623,7 +699,7 @@ static int sort_in_merge_order(struct co
// subgraph using the sort_first_epoch algorithm. Once we have reached the base
// we can continue sorting using sort_in_merge_order.
//
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter, local_test_func local_test)
{
struct commit_list *stack = NULL;
struct commit *base;
@@ -631,7 +707,8 @@ int sort_list_in_merge_order(struct comm
int ret = 0;
int action = CONTINUE;
- struct commit_list *reversed = NULL;
+ struct commit_list *filtered = NULL;
+ struct commit_list **p = &filtered;
for (; list; list = list->next) {
@@ -642,37 +719,50 @@ int sort_list_in_merge_order(struct comm
fprintf(stderr, "%s: duplicate commit %s ignored\n", __FUNCTION__, sha1_to_hex(next->object.sha1));
} else {
next->object.flags |= DUPCHECK;
- commit_list_insert(list->item, &reversed);
+ *p = commit_list_insert(list->item, p);
+ p = &(*p)->next;
}
}
}
- if (!reversed->next) {
+ if (!filtered->next) {
// if there is only one element in the list, we can sort it using
// sort_in_merge_order.
- base = reversed->item;
+ base = filtered->item;
} else {
// otherwise, we search for the base of the list
- if ((ret = find_base_for_list(reversed, &base)))
+ if ((ret = find_base_for_list(filtered, &base)))
return ret;
if (base) {
base->object.flags |= BOUNDARY;
}
-
+
+ struct commit_list * sorted;
+
+ sort_list_in_local_last_order(filtered, local_test, &sorted);
+
+ if (!sorted) {
+ sorted = filtered;
+ }
+
+ struct commit_list * reversed = NULL;
+ while (sorted)
+ commit_list_insert(pop_commit(&sorted), &reversed);
+
while (reversed) {
sort_first_epoch(pop_commit(&reversed), &stack);
if (reversed) {
//
// if we have more commits to push, then the
- // first push for the next parent may (or may not)
+ // first push for the next commit may (or may not)
// represent a discontinuity with respect to the
- // parent currently on the top of the stack.
+ // commit currently on the top of the stack.
//
// mark it for checking here, and check it
// with the next push...see sort_first_epoch for
@@ -686,8 +776,9 @@ int sort_list_in_merge_order(struct comm
}
if (base && (action != STOP)) {
- ret = sort_in_merge_order(base, emitter);
+ ret = sort_in_merge_order(base, emitter, local_test);
}
return ret;
}
+
diff --git a/epoch.h b/epoch.h
--- a/epoch.h
+++ b/epoch.h
@@ -8,13 +8,18 @@
#define DO 2
typedef int (*emitter_func) (struct commit *);
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
+// functions of this type return non-zero if the commit is "local" by some private assessment of
+// what it is for a commit to be local.
+typedef int (*local_test_func) (struct commit *);
+
+int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter, local_test_func local_test);
#define UNINTERESTING (1u<<2)
#define BOUNDARY (1u<<3)
#define VISITED (1u<<4)
#define DISCONTINUITY (1u<<5)
#define DUPCHECK (1u<<6)
+#define LOCAL (1u<<7)
#endif /* EPOCH_H */
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -11,8 +11,11 @@ static const char rev_list_usage[] =
" --max-age=epoch\n"
" --min-age=epoch\n"
" --header\n"
+ " --parents\n"
" --pretty\n"
- " --merge-order [ --show-breaks ]";
+ " --merge-order\n"
+ " --wrt=author@domain\n"
+ " --show-breaks ]";
static int verbose_header = 0;
static int show_parents = 0;
@@ -24,6 +27,7 @@ static int max_count = -1;
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
static int merge_order = 0;
static int show_breaks = 0;
+static char * local_author = NULL;
static void show_commit(struct commit *commit)
{
@@ -147,6 +151,16 @@ static enum cmit_fmt get_commit_format(c
usage(rev_list_usage);
}
+static int is_local_author(struct commit * commit)
+{
+ static char author[16384];
+ if (copy_author(commit, author, sizeof(author)) > 0) {
+ if (strstr(author, local_author)) {
+ return 1;
+ }
+ }
+ return 0;
+}
int main(int argc, char **argv)
{
@@ -192,6 +206,13 @@ int main(int argc, char **argv)
}
if (!strncmp(arg, "--show-breaks", 13)) {
show_breaks = 1;
+ merge_order = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--wrt=", 6)) {
+ local_author=xmalloc(strlen(&arg[6])+3);
+ sprintf(local_author, "<%s>", &arg[6]);
+ merge_order = 1;
continue;
}
@@ -201,7 +222,7 @@ int main(int argc, char **argv)
arg++;
limited = 1;
}
- if (get_sha1(arg, sha1) || (show_breaks && !merge_order))
+ if (get_sha1(arg, sha1))
usage(rev_list_usage);
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit) < 0)
@@ -221,7 +242,7 @@ int main(int argc, char **argv)
} else {
- if (sort_list_in_merge_order(list, &process_commit)) {
+ if (sort_list_in_merge_order(list, &process_commit, local_author ? &is_local_author : NULL)) {
die("merge order sort failed\n");
}
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2005-06-07 5:59 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2005-06-07 6:00 [PATCH] Add support for --wrt flag to git-rev-list Jon Seymour
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).