git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 2/7] Introduce new methods into the epoch_methods structure.
@ 2005-06-14  2:04 Jon Seymour
  0 siblings, 0 replies; only message in thread
From: Jon Seymour @ 2005-06-14  2:04 UTC (permalink / raw)
  To: git; +Cc: jon.seymour


The epoch_methods in epoch.h is modified to include several new methods:

visit_commit - This method is called during the sort phase of a merge order traversal.
Implementors may allocate resources and reference them by object.util during this call.

visit_edge - This method is called each time an edge is visited during the sort phase
of a merge order traversal.

clean_commit - This method allows implementors to cleanup and release any resources
allocated during visit_commit.

This change starts to move responsibility for output limiting back into rev-list.c and
starts to generalize epoch.c as a general purpose commit graph traversal algorithm.

sort_list_in_merge_order now clears all flags it uses (masked by EPOCH_FLAGS),
rather than leaving them in an undefined state as it did previously.

Signed-off-by: Jon Seymour <jon.seymour@gmail.com>
---

 epoch.c    |  145 ++++++++++++++++++++++++++++++++++++++++--------------------
 epoch.h    |   35 ++++++++++++--
 rev-list.c |   66 +++++++++++++++------------
 3 files changed, 163 insertions(+), 83 deletions(-)

diff --git a/epoch.c b/epoch.c
--- a/epoch.c
+++ b/epoch.c
@@ -197,17 +197,65 @@ static void free_mass_counter(struct mas
 	free(counter);
 }
 
-static void mark_commit(struct epoch_methods * methods, struct commit * commit)
+/*
+ * This function calls the supplied commit_visitor method, if there is one.
+ */
+static void visit_commit(struct epoch_methods * methods, struct commit * commit, int first_visit)
 {
-	if (methods->marker)
-		(*(methods->marker))(commit);
+	/*
+	 * pre-condition:
+	 * 	first_visit => object.util == NULL
+	 */
+	if (methods->commit_visitor)
+		(*(methods->commit_visitor))(commit, first_visit);
 }
 
+/*
+ * This function is called as each edge in the graph is identified. It 
+ * will always be called after visit_commit(...,from, 1). No guarantees
+ * are offered about when this call will be made w.r.t. to the call
+ * to visit_commit(..., to, 1)
+ */
+static void visit_edge(struct epoch_methods * methods, struct commit * from, struct commit * to)
+{
+	/*
+	 * pre-condition:
+	 * 	from->object.flags & VISITED &&
+	 * 	to->object.util => (to->object.flags & VISITED)
+	 */ 
+	if (methods->edge_visitor)
+		(*(methods->edge_visitor))(from, to);
+}
+
+/*
+ * This function is called exactly once for each commit discovered during
+ * the merge order traversal. If it ever returns a non-zero value, the
+ * merge order traversal will not continue past the current epoch boundary.
+ * It may, however, continue to emit nodes up until the epoch boundary.
+ * It is the implementer's responsibility to perform output limiting if 
+ * that is required.
+ */
 static int emit_commit(struct epoch_methods * methods, struct commit * commit)
 {
 	return (*(methods->emitter))(commit);
 }
 
+/*
+ * Invoke a call back method to allow the tactics to release and storage
+ * allocated during a previous visit_commit or visit_edge call.
+ */
+static void clean_commit(struct epoch_methods * methods, struct commit * commit)
+{
+	if (methods->clean)
+		(*(methods->clean))(commit);
+	commit->object.util = NULL;
+	commit->object.flags &= ~(EPOCH_FLAGS);
+	/*
+	 * post-condition:
+	 * 	commit->object.util == NULL && !(commit->object.flag & EPOCH_FLAGS)
+	 */		
+}
+
 void init_epoch_methods(struct epoch_methods * methods)
 {
 	memset(methods, 0, sizeof(*methods));
@@ -410,7 +458,7 @@ static void mark_ancestors_uninteresting
 	 * all uninteresting, unvisited parents as they are visited
 	 * so there is no need to duplicate that traversal here.
 	 *
-	 * Similarly, if we are already marked uninteresting
+	 * Similarly, if the commit is already marked uninteresting
 	 * then either all ancestors have already been marked
 	 * uninteresting or will be once the sort_unvisited
 	 * traverse reaches them.
@@ -432,10 +480,13 @@ static void sort_unvisited(
 	struct commit_list *parents = NULL;
         struct commit * top = *stack?(*stack)->item:NULL;
 
-	if (IS_VISITED(head)) 
+	if (IS_VISITED(head)) {
+		if (!IS_BASE(head)) {
+			visit_commit(methods, head, 0);
+		}
 		return;
+	}
 	head->object.flags |= VISITED;
-	mark_commit(methods, head);
         if (IS_BASE(head)) {
 		ASSERT(!top, "stack empty on visit to base", head);
 	} else {
@@ -445,57 +496,40 @@ static void sort_unvisited(
 			parents = (struct commit_list *)head->object.util;
                         head->object.util = NULL;
 		}
+		visit_commit(methods, head, 1);
 		while (parents) {
 			struct commit *parent = pop_commit(&parents);
 
 			if (IS_UNINTERESTING(head)) {
 				mark_ancestors_uninteresting(parent);
 			}
+			visit_edge(methods, head, parent);
 			sort_unvisited(parent, stack, methods);
 		}		
+		top=(*stack)?(*stack)->item:NULL;
+		if (top && !is_parent_of(top, head)) {
+			top->object.flags |= DISCONTINUITY;
+		}			
+		commit_list_insert(head, stack);	
         }
-	top=(*stack)?(*stack)->item:NULL;
-	if (top && !is_parent_of(top, head)) {
-		top->object.flags |= DISCONTINUITY;
-	}			
-	commit_list_insert(head, stack);	
 }
 
 /*
  * Emit the contents of the stack.
  * The stack is freed and replaced by NULL.
- * Sets the return value to STOP if no further output should be generated.
+ * Sets the return value to STOP if the traversal should stop.
  */
 static int emit_stack(struct commit_list **stack, struct epoch_methods * methods)
 {
-	unsigned int seen = 0;
-	int action = CONTINUE;
+	int stop = 0;
 
-	while (*stack && (action != STOP)) {
+	while (*stack) {
 		struct commit *next = pop_commit(stack);
-		seen |= next->object.flags;
-		if (*stack)
-			action = emit_commit(methods, next);
-	}
-	if (*stack) {
-		free_commit_list(*stack);
-		*stack = NULL;
-	}	
-	if (seen & UNINTERESTING) {
-		/**
-		 * We stop at the base of the stack, rather than
-		 * when we encounter the first UNINTERESTING flag.
-		 * 
-		 * The reason is that there may still be interesting stuff 
-		 * on the stack but once we reach the base there can be no 
-		 * more interesting stuff by definition of what the base 
-		 * of an epoch is - everything reachable from the base is 
-		 * also reachable from the UNINTERESTING node and hence 
-		 * is uninteresting.
-		 */		 
-		action = STOP;
+		
+		stop = (emit_commit(methods, next)==STOP) || (next->object.flags & UNINTERESTING) || stop ;
+		clean_commit(methods, next);
 	}
-	return action;
+	return stop ? STOP : CONTINUE;
 }
 
 /*
@@ -558,6 +592,7 @@ static unsigned int sort_local_branches_
                  */
 		if (!IS_BASE(item) && !item->object.util) {
 			struct commit_list ** copied;
+			
 			if ((*(methods->local_test)) (item))
 				item->object.flags |= LOCAL;
 			copied=copy_and_store_parents(item);
@@ -587,16 +622,33 @@ static unsigned int sort_local_branches_
  * reach the base and emitting as we go. We don't emit the base
  * now.
  */
-static int sort_maximal_linear_epoch(struct commit *next, struct epoch_methods * methods)
+static int sort_maximal_linear_epoch(struct commit *head, struct epoch_methods * methods)
 {
-	while (!IS_BASE(next)) {
-		mark_commit(methods, next);		
-		if (!IS_UNINTERESTING(next) && (emit_commit(methods, next) != STOP))
-			next = next->parents->item;
-		else			
-			return STOP;
+	struct commit * next;
+	struct commit * base;
+	int stop = 0;
+	
+	/* invoke the visitors, if any */
+	if (methods->commit_visitor || methods->edge_visitor) {
+		for (next = head; !IS_BASE(next); next = next->parents->item) {
+			visit_commit(methods, next, 1);
+			visit_edge(methods, next, next->parents->item);
+		}
+	}		
+	/* now emit the nodes, and mark the base*/
+	for (next = head; !IS_BASE(next); next = next->parents->item) {
+		stop = (emit_commit(methods, next)==STOP) || stop;
+		if (IS_UNINTERESTING(next)) {
+			next->parents->item->object.flags |= UNINTERESTING;
+			stop = 1;
+		}
 	}
-	return CONTINUE;
+	base = next;		
+	/* then clean the nodes */
+	for (next = head; next != base; next = next->parents->item) 
+		clean_commit(methods, next);
+		
+	return stop ? STOP : CONTINUE;
 }
 
 /*
@@ -617,9 +669,6 @@ static int sort_minimal_non_linear_epoch
 /*
  * 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, struct epoch_methods * methods)
 {
diff --git a/epoch.h b/epoch.h
--- a/epoch.h
+++ b/epoch.h
@@ -11,6 +11,9 @@
 #define DUPCHECK       (1u<<6)
 #define LOCAL          (1u<<7)
 #define BASE           (1u<<8)
+#define FORK           (1u<<9)
+
+#define EPOCH_FLAGS (UNINTERESTING|BOUNDARY|VISITED|DISCONTINUITY|DUPCHECK|LOCAL|BASE)
 
 /**
  * Return codes for emitter method. Also used by rev-list.c
@@ -28,13 +31,33 @@ struct epoch_methods {
 	 * Returns non-zero if the commit is regarded "local", 0 otherwise.
 	 */
 	int (*local_test)(struct commit *);
-	/* 
-	 * Implementers may use this method to mark commits uninteresting
-	 * according to some locally determined criteria. The tree
-	 * will be pruned at any commit so marked.
+
+	/*
+	 * If defined, called on each visit to a vertex during the 
+         * sort phase of the traversal. first_visit will be
+         * non-zero on the first visit, zero otherwise.
+         *
+         * object.util is available for use by the visitor.
+	 */
+	void (*commit_visitor)(struct commit *, int first_visit);
+
+	/*
+         * Called at some point prior to visiting 'to' from 'from'.
+         * commit_visitor will already have been called at least once for 
+         * from node. It may or may not have already been called for the
+         * to node.
+	 */
+	void (*edge_visitor)(struct commit * from, struct commit * to);
+
+	/*
+         * Called sometime after the emitter function has been called.
+         * Once this call completes the object.util pointer will be set to NULL.
+         * Implementers should use this call to free any data structure
+         * allocated by the commit_visitor method.
 	 */
-	void (*marker)(struct commit *);
-};
+	void (*clean)(struct commit *);
+}
+;
 
 /**
  * Initializes an epoch_methods structure which
diff --git a/rev-list.c b/rev-list.c
--- a/rev-list.c
+++ b/rev-list.c
@@ -18,7 +18,7 @@ static const char rev_list_usage[] =
 			"  --wrt-author\n"
 			"  --prune-at-author\n"
 			"  --author=author@domain\n"
-			"  --show-breaks ]";
+			"  --show-breaks";
 
 static int verbose_header = 0;
 static int show_parents = 0;
@@ -167,9 +167,9 @@ static int is_local_author(struct commit
 	return 0;
 }
 
-static void mark_authors_own_uninteresting(struct commit * commit)
+static void mark_authors_own_uninteresting(struct commit * commit, int first_visit)
 {
-        if (is_local_author(commit)) {
+        if (first_visit && is_local_author(commit)) {
 		struct commit_list * parents = commit->parents;
 		for (;parents;parents=parents->next) {
 			parents->item->object.flags |= UNINTERESTING;
@@ -177,6 +177,34 @@ static void mark_authors_own_uninteresti
         }
 }
 
+static void merge_order_traversal(struct commit_list * list)
+{
+	struct epoch_methods methods;
+
+	init_epoch_methods(&methods);
+	if ((prune_at_author|wrt_author) && !local_author) {
+		local_author = gitenv("GIT_AUTHOR_EMAIL") ? : NULL;			    
+		if (!local_author)
+			get_real_identity(&local_author, NULL);
+		else
+			local_author = strdup(local_author);
+	}
+	if (local_author) {
+		    /* add delimiters to improve accuracy of match */
+		    char * tmp=xmalloc(strlen(local_author)+3);
+		    sprintf(tmp, "<%s>", local_author);
+		    free(local_author);
+		    local_author = tmp;
+	}		 
+	methods.emitter = &process_commit;		  
+	if (wrt_author)
+		methods.local_test = &is_local_author;
+	if (prune_at_author)
+		methods.commit_visitor = &mark_authors_own_uninteresting;
+	if (sort_list_in_merge_order(list, &methods))
+		die("merge order sort failed\n");
+}
+
 int main(int argc, char **argv)
 {
 	struct commit_list *list = NULL;
@@ -254,38 +282,18 @@ int main(int argc, char **argv)
 	if (!list)
 		usage(rev_list_usage);
 
-        if (!merge_order) 
-        	merge_order = wrt_author || prune_at_author || show_breaks;        	
+       	merge_order = 
+		merge_order
+		|| wrt_author 
+		|| prune_at_author 
+		|| show_breaks;
 
 	if (!merge_order) {		
 	        if (limited)
 			list = limit_list(list);
 		show_commit_list(list);
 	} else {		
-		struct epoch_methods methods;
-	
-		init_epoch_methods(&methods);
-		if ((prune_at_author|wrt_author) && !local_author) {
-			local_author = gitenv("GIT_AUTHOR_EMAIL") ? : NULL;			    
-			if (!local_author)
-				 get_real_identity(&local_author, NULL);
-			else
-				 local_author = strdup(local_author);
-		}		
-		if (local_author) {
-			/* add delimiters to improve accuracy of match */
-			char * tmp=xmalloc(strlen(local_author)+3);
-			sprintf(tmp, "<%s>", local_author);
-			free(local_author);
-			local_author = tmp;
-		}		 
-		methods.emitter = &process_commit;		  
-		if (wrt_author)
-			methods.local_test = &is_local_author;
-		if (prune_at_author)
-			methods.marker = &mark_authors_own_uninteresting;
-		if (sort_list_in_merge_order(list, &methods))
-			die("merge order sort failed\n");
+		merge_order_traversal(list);
 	}
 
 	return 0;
------------

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2005-06-14  2:01 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-06-14  2:04 [PATCH 2/7] Introduce new methods into the epoch_methods structure 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).