提交 c7481526 编写于 作者: J Junio C Hamano

Merge branch 'as/graph'

* as/graph:
  graph API: eliminate unnecessary indentation
  log and rev-list: add --graph option
  Add history graph API
  revision API: split parent rewriting and parent printing options
......@@ -75,6 +75,16 @@ you would get an output line this:
-xxxxxxx... 1st on a
-----------------------------------------------------------------------
--graph::
Draw a text-based graphical representation of the commit history
on the left hand side of the output. This may cause extra lines
to be printed in between commits, in order for the graph history
to be drawn properly.
+
This implies the '--topo-order' option by default, but the
'--date-order' option may also be specified.
Diff Formatting
~~~~~~~~~~~~~~~
......
history graph API
=================
The graph API is used to draw a text-based representation of the commit
history. The API generates the graph in a line-by-line fashion.
Functions
---------
Core functions:
* `graph_init()` creates a new `struct git_graph`
* `graph_release()` destroys a `struct git_graph`, and frees the memory
associated with it.
* `graph_update()` moves the graph to a new commit.
* `graph_next_line()` outputs the next line of the graph into a strbuf. It
does not add a terminating newline.
* `graph_padding_line()` outputs a line of vertical padding in the graph. It
is similar to `graph_next_line()`, but is guaranteed to never print the line
containing the current commit. Where `graph_next_line()` would print the
commit line next, `graph_padding_line()` prints a line that simply extends
all branch lines downwards one row, leaving their positions unchanged.
* `graph_is_commit_finished()` determines if the graph has output all lines
necessary for the current commit. If `graph_update()` is called before all
lines for the current commit have been printed, the next call to
`graph_next_line()` will output an ellipsis, to indicate that a portion of
the graph was omitted.
The following utility functions are wrappers around `graph_next_line()` and
`graph_is_commit_finished()`. They always print the output to stdout.
They can all be called with a NULL graph argument, in which case no graph
output will be printed.
* `graph_show_commit()` calls `graph_next_line()` until it returns non-zero.
This prints all graph lines up to, and including, the line containing this
commit. Output is printed to stdout. The last line printed does not contain
a terminating newline. This should not be called if the commit line has
already been printed, or it will loop forever.
* `graph_show_oneline()` calls `graph_next_line()` and prints the result to
stdout. The line printed does not contain a terminating newline.
* `graph_show_padding()` calls `graph_padding_line()` and prints the result to
stdout. The line printed does not contain a terminating newline.
* `graph_show_remainder()` calls `graph_next_line()` until
`graph_is_commit_finished()` returns non-zero. Output is printed to stdout.
The last line printed does not contain a terminating newline. Returns 1 if
output was printed, and 0 if no output was necessary.
* `graph_show_strbuf()` prints the specified strbuf to stdout, prefixing all
lines but the first with a graph line. The caller is responsible for
ensuring graph output for the first line has already been printed to stdout.
(This can be done with `graph_show_commit()` or `graph_show_oneline()`.) If
a NULL graph is supplied, the strbuf is printed as-is.
* `graph_show_commit_msg()` is similar to `graph_show_strbuf()`, but it also
prints the remainder of the graph, if more lines are needed after the strbuf
ends. It is better than directly calling `graph_show_strbuf()` followed by
`graph_show_remainder()` since it properly handles buffers that do not end in
a terminating newline. The output printed by `graph_show_commit_msg()` will
end in a newline if and only if the strbuf ends in a newline.
Data structure
--------------
`struct git_graph` is an opaque data type used to store the current graph
state.
Calling sequence
----------------
* Create a `struct git_graph` by calling `graph_init()`. When using the
revision walking API, this is done automatically by `setup_revisions()` if
the '--graph' option is supplied.
* Use the revision walking API to walk through a group of contiguous commits.
The `get_revision()` function automatically calls `graph_update()` each time
it is invoked.
* For each commit, call `graph_next_line()` repeatedly, until
`graph_is_commit_finished()` returns non-zero. Each call go
`graph_next_line()` will output a single line of the graph. The resulting
lines will not contain any newlines. `graph_next_line()` returns 1 if the
resulting line contains the current commit, or 0 if this is merely a line
needed to adjust the graph before or after the current commit. This return
value can be used to determine where to print the commit summary information
alongside the graph output.
Limitations
-----------
* `graph_update()` must be called with commits in topological order. It should
not be called on a commit if it has already been invoked with an ancestor of
that commit, or the graph output will be incorrect.
* `graph_update()` must be called on a contiguous group of commits. If
`graph_update()` is called on a particular commit, it should later be called
on all parents of that commit. Parents must not be skipped, or the graph
output will appear incorrect.
+
`graph_update()` may be used on a pruned set of commits only if the parent list
has been rewritten so as to include only ancestors from the pruned set.
* The graph API does not currently support reverse commit ordering. In
order to implement reverse ordering, the graphing API needs an
(efficient) mechanism to find the children of a commit.
Sample usage
------------
------------
struct commit *commit;
struct git_graph *graph = graph_init();
while ((commit = get_revision(opts)) != NULL) {
graph_update(graph, commit);
while (!graph_is_commit_finished(graph))
{
struct strbuf sb;
int is_commit_line;
strbuf_init(&sb, 0);
is_commit_line = graph_next_line(graph, &sb);
fputs(sb.buf, stdout);
if (is_commit_line)
log_tree_commit(opts, commit);
else
putchar(opts->diffopt.line_termination);
}
}
graph_release(graph);
------------
Sample output
-------------
The following is an example of the output from the graph API. This output does
not include any commit summary information--callers are responsible for
outputting that information, if desired.
------------
*
*
M
|\
* |
| | *
| \ \
| \ \
M-. \ \
|\ \ \ \
| | * | |
| | | | | *
| | | | | *
| | | | | M
| | | | | |\
| | | | | | *
| * | | | | |
| | | | | M \
| | | | | |\ |
| | | | * | | |
| | | | * | | |
* | | | | | | |
| |/ / / / / /
|/| / / / / /
* | | | | | |
|/ / / / / /
* | | | | |
| | | | | *
| | | | |/
| | | | *
------------
......@@ -346,6 +346,7 @@ LIB_H += diff.h
LIB_H += dir.h
LIB_H += fsck.h
LIB_H += git-compat-util.h
LIB_H += graph.h
LIB_H += grep.h
LIB_H += hash.h
LIB_H += list-objects.h
......@@ -411,6 +412,7 @@ LIB_OBJS += entry.o
LIB_OBJS += environment.o
LIB_OBJS += exec_cmd.o
LIB_OBJS += fsck.o
LIB_OBJS += graph.o
LIB_OBJS += grep.o
LIB_OBJS += hash.o
LIB_OBJS += help.o
......
......@@ -10,6 +10,7 @@
#include "list-objects.h"
#include "builtin.h"
#include "log-tree.h"
#include "graph.h"
/* bits #0-15 in revision.h */
......@@ -58,6 +59,8 @@ static const char *header_prefix;
static void finish_commit(struct commit *commit);
static void show_commit(struct commit *commit)
{
graph_show_commit(revs.graph);
if (show_timestamp)
printf("%lu ", commit->date);
if (header_prefix)
......@@ -77,7 +80,7 @@ static void show_commit(struct commit *commit)
stdout);
else
fputs(sha1_to_hex(commit->object.sha1), stdout);
if (revs.parents) {
if (revs.print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
printf(" %s", sha1_to_hex(parents->item->object.sha1));
......@@ -96,9 +99,48 @@ static void show_commit(struct commit *commit)
pretty_print_commit(revs.commit_format, commit,
&buf, revs.abbrev, NULL, NULL,
revs.date_mode, 0);
if (buf.len)
printf("%s%c", buf.buf, hdr_termination);
if (revs.graph) {
if (buf.len) {
if (revs.commit_format != CMIT_FMT_ONELINE)
graph_show_oneline(revs.graph);
graph_show_commit_msg(revs.graph, &buf);
/*
* Add a newline after the commit message.
*
* Usually, this newline produces a blank
* padding line between entries, in which case
* we need to add graph padding on this line.
*
* However, the commit message may not end in a
* newline. In this case the newline simply
* ends the last line of the commit message,
* and we don't need any graph output. (This
* always happens with CMIT_FMT_ONELINE, and it
* happens with CMIT_FMT_USERFORMAT when the
* format doesn't explicitly end in a newline.)
*/
if (buf.len && buf.buf[buf.len - 1] == '\n')
graph_show_padding(revs.graph);
putchar('\n');
} else {
/*
* If the message buffer is empty, just show
* the rest of the graph output for this
* commit.
*/
if (graph_show_remainder(revs.graph))
putchar('\n');
}
} else {
if (buf.len)
printf("%s%c", buf.buf, hdr_termination);
}
strbuf_release(&buf);
} else {
if (graph_show_remainder(revs.graph))
putchar('\n');
}
maybe_flush_or_die(stdout, "stdout");
finish_commit(commit);
......
此差异已折叠。
#ifndef GRAPH_H
#define GRAPH_H
/* A graph is a pointer to this opaque structure */
struct git_graph;
/*
* Create a new struct git_graph.
* The graph should be freed with graph_release() when no longer needed.
*/
struct git_graph *graph_init();
/*
* Destroy a struct git_graph and free associated memory.
*/
void graph_release(struct git_graph *graph);
/*
* Update a git_graph with a new commit.
* This will cause the graph to begin outputting lines for the new commit
* the next time graph_next_line() is called.
*
* If graph_update() is called before graph_is_commit_finished() returns 1,
* the next call to graph_next_line() will output an ellipsis ("...")
* to indicate that a portion of the graph is missing.
*/
void graph_update(struct git_graph *graph, struct commit *commit);
/*
* Output the next line for a graph.
* This formats the next graph line into the specified strbuf. It is not
* terminated with a newline.
*
* Returns 1 if the line includes the current commit, and 0 otherwise.
* graph_next_line() will return 1 exactly once for each time
* graph_update() is called.
*/
int graph_next_line(struct git_graph *graph, struct strbuf *sb);
/*
* Output a padding line in the graph.
* This is similar to graph_next_line(). However, it is guaranteed to
* never print the current commit line. Instead, if the commit line is
* next, it will simply output a line of vertical padding, extending the
* branch lines downwards, but leaving them otherwise unchanged.
*/
void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
/*
* Determine if a graph has finished outputting lines for the current
* commit.
*
* Returns 1 if graph_next_line() needs to be called again before
* graph_update() should be called. Returns 0 if no more lines are needed
* for this commit. If 0 is returned, graph_next_line() may still be
* called without calling graph_update(), and it will merely output
* appropriate "vertical padding" in the graph.
*/
int graph_is_commit_finished(struct git_graph const *graph);
/*
* graph_show_*: helper functions for printing to stdout
*/
/*
* If the graph is non-NULL, print the history graph to stdout,
* up to and including the line containing this commit.
* Does not print a terminating newline on the last line.
*/
void graph_show_commit(struct git_graph *graph);
/*
* If the graph is non-NULL, print one line of the history graph to stdout.
* Does not print a terminating newline on the last line.
*/
void graph_show_oneline(struct git_graph *graph);
/*
* If the graph is non-NULL, print one line of vertical graph padding to
* stdout. Does not print a terminating newline on the last line.
*/
void graph_show_padding(struct git_graph *graph);
/*
* If the graph is non-NULL, print the rest of the history graph for this
* commit to stdout. Does not print a terminating newline on the last line.
*/
int graph_show_remainder(struct git_graph *graph);
/*
* Print a strbuf to stdout. If the graph is non-NULL, all lines but the
* first will be prefixed with the graph output.
*
* If the strbuf ends with a newline, the output will end after this
* newline. A new graph line will not be printed after the final newline.
* If the strbuf is empty, no output will be printed.
*
* Since the first line will not include the graph ouput, the caller is
* responsible for printing this line's graph (perhaps via
* graph_show_commit() or graph_show_oneline()) before calling
* graph_show_strbuf().
*/
void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
/*
* Print a commit message strbuf and the remainder of the graph to stdout.
*
* This is similar to graph_show_strbuf(), but it always prints the
* remainder of the graph.
*
* If the strbuf ends with a newline, the output printed by
* graph_show_commit_msg() will end with a newline. If the strbuf is
* missing a terminating newline (including if it is empty), the output
* printed by graph_show_commit_msg() will also be missing a terminating
* newline.
*/
void graph_show_commit_msg(struct git_graph *graph, struct strbuf const *sb);
#endif /* GRAPH_H */
#include "cache.h"
#include "diff.h"
#include "commit.h"
#include "graph.h"
#include "log-tree.h"
#include "reflog-walk.h"
......@@ -165,11 +166,16 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
}
printf("From %s Mon Sep 17 00:00:00 2001\n", name);
if (opt->message_id)
graph_show_oneline(opt->graph);
if (opt->message_id) {
printf("Message-Id: <%s>\n", opt->message_id);
if (opt->ref_message_id)
graph_show_oneline(opt->graph);
}
if (opt->ref_message_id) {
printf("In-Reply-To: <%s>\nReferences: <%s>\n",
opt->ref_message_id, opt->ref_message_id);
graph_show_oneline(opt->graph);
}
if (opt->mime_boundary) {
static char subject_buffer[1024];
static char buffer[1024];
......@@ -220,6 +226,8 @@ void show_log(struct rev_info *opt)
opt->loginfo = NULL;
if (!opt->verbose_header) {
graph_show_commit(opt->graph);
if (commit->object.flags & BOUNDARY)
putchar('-');
else if (commit->object.flags & UNINTERESTING)
......@@ -231,9 +239,13 @@ void show_log(struct rev_info *opt)
putchar('>');
}
fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
if (opt->parents)
if (opt->print_parents)
show_parents(commit, abbrev_commit);
show_decorations(commit);
if (opt->graph && !graph_is_commit_finished(opt->graph)) {
putchar('\n');
graph_show_remainder(opt->graph);
}
putchar(opt->diffopt.line_termination);
return;
}
......@@ -243,10 +255,32 @@ void show_log(struct rev_info *opt)
* Otherwise, add a diffopt.line_termination character before all
* entries but the first. (IOW, as a separator between entries)
*/
if (opt->shown_one && !opt->use_terminator)
if (opt->shown_one && !opt->use_terminator) {
/*
* If entries are separated by a newline, the output
* should look human-readable. If the last entry ended
* with a newline, print the graph output before this
* newline. Otherwise it will end up as a completely blank
* line and will look like a gap in the graph.
*
* If the entry separator is not a newline, the output is
* primarily intended for programmatic consumption, and we
* never want the extra graph output before the entry
* separator.
*/
if (opt->diffopt.line_termination == '\n' &&
!opt->missing_newline)
graph_show_padding(opt->graph);
putchar(opt->diffopt.line_termination);
}
opt->shown_one = 1;
/*
* If the history graph was requested,
* print the graph, up to this commit's line
*/
graph_show_commit(opt->graph);
/*
* Print header line of header..
*/
......@@ -271,7 +305,7 @@ void show_log(struct rev_info *opt)
}
fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
stdout);
if (opt->parents)
if (opt->print_parents)
show_parents(commit, abbrev_commit);
if (parent)
printf(" (from %s)",
......@@ -279,8 +313,19 @@ void show_log(struct rev_info *opt)
abbrev_commit));
show_decorations(commit);
printf("%s", diff_get_color_opt(&opt->diffopt, DIFF_RESET));
putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
if (opt->commit_format == CMIT_FMT_ONELINE) {
putchar(' ');
} else {
putchar('\n');
graph_show_oneline(opt->graph);
}
if (opt->reflog_info) {
/*
* setup_revisions() ensures that opt->reflog_info
* and opt->graph cannot both be set,
* so we don't need to worry about printing the
* graph info here.
*/
show_reflog_message(opt->reflog_info,
opt->commit_format == CMIT_FMT_ONELINE,
opt->date_mode);
......@@ -304,13 +349,30 @@ void show_log(struct rev_info *opt)
if (opt->add_signoff)
append_signoff(&msgbuf, opt->add_signoff);
if (opt->show_log_size)
if (opt->show_log_size) {
printf("log size %i\n", (int)msgbuf.len);
graph_show_oneline(opt->graph);
}
if (msgbuf.len)
/*
* Set opt->missing_newline if msgbuf doesn't
* end in a newline (including if it is empty)
*/
if (!msgbuf.len || msgbuf.buf[msgbuf.len - 1] != '\n')
opt->missing_newline = 1;
else
opt->missing_newline = 0;
if (opt->graph)
graph_show_commit_msg(opt->graph, &msgbuf);
else
fwrite(msgbuf.buf, sizeof(char), msgbuf.len, stdout);
if (opt->use_terminator)
if (opt->use_terminator) {
if (!opt->missing_newline)
graph_show_padding(opt->graph);
putchar('\n');
}
strbuf_release(&msgbuf);
}
......
......@@ -6,6 +6,7 @@
#include "diff.h"
#include "refs.h"
#include "revision.h"
#include "graph.h"
#include "grep.h"
#include "reflog-walk.h"
#include "patch-ids.h"
......@@ -1105,7 +1106,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
}
}
if (!strcmp(arg, "--parents")) {
revs->parents = 1;
revs->rewrite_parents = 1;
revs->print_parents = 1;
continue;
}
if (!strcmp(arg, "--dense")) {
......@@ -1202,6 +1204,12 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
get_commit_format(arg+8, revs);
continue;
}
if (!prefixcmp(arg, "--graph")) {
revs->topo_order = 1;
revs->rewrite_parents = 1;
revs->graph = graph_init();
continue;
}
if (!strcmp(arg, "--root")) {
revs->show_root_diff = 1;
continue;
......@@ -1396,6 +1404,15 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
if (revs->reverse && revs->reflog_info)
die("cannot combine --reverse with --walk-reflogs");
/*
* Limitations on the graph functionality
*/
if (revs->reverse && revs->graph)
die("cannot combine --reverse with --graph");
if (revs->reflog_info && revs->graph)
die("cannot combine --walk-reflogs with --graph");
return left;
}
......@@ -1524,13 +1541,13 @@ enum commit_action simplify_commit(struct rev_info *revs, struct commit *commit)
/* Commit without changes? */
if (commit->object.flags & TREESAME) {
/* drop merges unless we want parenthood */
if (!revs->parents)
if (!revs->rewrite_parents)
return commit_ignore;
/* non-merge - always ignore it */
if (!commit->parents || !commit->parents->next)
return commit_ignore;
}
if (revs->parents && rewrite_parents(revs, commit) < 0)
if (revs->rewrite_parents && rewrite_parents(revs, commit) < 0)
return commit_error;
}
return commit_show;
......@@ -1597,7 +1614,7 @@ static void gc_boundary(struct object_array *array)
}
}
struct commit *get_revision(struct rev_info *revs)
static struct commit *get_revision_internal(struct rev_info *revs)
{
struct commit *c = NULL;
struct commit_list *l;
......@@ -1704,3 +1721,11 @@ struct commit *get_revision(struct rev_info *revs)
return c;
}
struct commit *get_revision(struct rev_info *revs)
{
struct commit *c = get_revision_internal(revs);
if (c && revs->graph)
graph_update(revs->graph, c);
return c;
}
......@@ -46,7 +46,8 @@ struct rev_info {
unpacked:1, /* see also ignore_packed below */
boundary:2,
left_right:1,
parents:1,
rewrite_parents:1,
print_parents:1,
reverse:1,
cherry_pick:1,
first_parent_only:1;
......@@ -65,7 +66,8 @@ struct rev_info {
/* Format info */
unsigned int shown_one:1,
abbrev_commit:1,
use_terminator:1;
use_terminator:1,
missing_newline:1;
enum date_mode date_mode;
const char **ignore_packed; /* pretend objects in these are unpacked */
......@@ -88,6 +90,9 @@ struct rev_info {
/* Filter by commit log message */
struct grep_opt *grep_filter;
/* Display history graph */
struct git_graph *graph;
/* special limits */
int skip_count;
int max_count;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册