From cdcefbc971d8fcdd293750af7571d4c715f86964 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sat, 3 Nov 2007 11:11:10 -0700 Subject: Add "--early-output" log flag for interactive GUI use This adds support for "--early-output[=n]" as a flag to the "git log" family of commands. This allows GUI programs to state that they want to get some output early, in order to be able to show at least something quickly, even if the full output may take longer to generate. If no count is specified, a default count of a hundred commits will be used, although the actual numbr of commits output may be smaller depending on how many commits were actually found in the first tenth of a second (or if *everything* was found before that, in which case no early output will be provided, and only the final list is made available). When the full list is generated, there will be a "Final output:" string prepended to it, regardless of whether any early commits were shown or not, so that the consumer can always know the difference between early output and the final list. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-log.c | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'builtin-log.c') diff --git a/builtin-log.c b/builtin-log.c index 8b2bf632c5..4e9d0cb69c 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -77,11 +77,78 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, } } +static void log_show_early(struct rev_info *revs, struct commit_list *list) +{ + int i = revs->early_output; + + sort_in_topological_order(&list, revs->lifo); + while (list && i) { + struct commit *commit = list->item; + log_tree_commit(revs, commit); + list = list->next; + i--; + } +} + +static void early_output(int signal) +{ + show_early_output = log_show_early; +} + +static void setup_early_output(struct rev_info *rev) +{ + struct sigaction sa; + struct itimerval v; + + /* + * Set up the signal handler, minimally intrusively: + * we only set a single volatile integer word (not + * using sigatomic_t - trying to avoid unnecessary + * system dependencies and headers), and using + * SA_RESTART. + */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = early_output; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + /* + * If we can get the whole output in less than a + * tenth of a second, don't even bother doing the + * early-output thing.. + * + * This is a one-time-only trigger. + */ + memset(&v, 0, sizeof(v)); + v.it_value.tv_sec = 0; + v.it_value.tv_usec = 100000; + setitimer(ITIMER_REAL, &v, NULL); +} + +static void finish_early_output(struct rev_info *rev) +{ + signal(SIGALRM, SIG_IGN); + if (rev->shown_one) { + rev->shown_one = 0; + if (rev->commit_format != CMIT_FMT_ONELINE) + putchar(rev->diffopt.line_termination); + } + printf("Final output:\n"); +} + static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; + if (rev->early_output) + setup_early_output(rev); + prepare_revision_walk(rev); + + if (rev->early_output) + finish_early_output(rev); + while ((commit = get_revision(rev)) != NULL) { log_tree_commit(rev, commit); if (!rev->reflog_info) { -- cgit v1.2.1 From 252a7c02354a3e2825cfde3c5053a04acc07be9c Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Sun, 4 Nov 2007 12:12:05 -0800 Subject: Enhance --early-output format This makes --early-output a bit more advanced, and actually makes it generate multiple "Final output:" headers as it updates things asynchronously. I realize that the "Final output:" line is now illogical, since it's not really final until it also says "done", but It now _always_ generates a "Final output:" header in front of any commit list, and that output header gives you a *guess* at the maximum number of commits available. However, it should be noted that the guess can be completely off: I do a reasonable job estimating it, but it is not meant to be exact. So what happens is that you may get output like this: - at 0.1 seconds: Final output: 2 incomplete .. 2 commits listed .. - half a second later: Final output: 33 incomplete .. 33 commits listed .. - another half a second after that: Final output: 71 incomplete .. 71 commits listed .. - another half second later: Final output: 136 incomplete .. 100 commits listed: we hit the --early-output limit, and .. will only output 100 commits, and after this you'll not .. see an "incomplete" report any more since you got as much .. early output as you asked for! - .. and then finally: Final output: 73106 done .. all the commits .. The above is a real-life scenario on my current kernel tree after having flushed all the caches. Tested with the experimental gitk patch that Paul sent out, and by looking at the actual log output (and verifying that my commit count guesses actually match real life fairly well). Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-log.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 13 deletions(-) (limited to 'builtin-log.c') diff --git a/builtin-log.c b/builtin-log.c index 4e9d0cb69c..981f38872c 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -77,17 +77,85 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, } } +/* + * This gives a rough estimate for how many commits we + * will print out in the list. + */ +static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) +{ + int n = 0; + + while (list) { + struct commit *commit = list->item; + unsigned int flags = commit->object.flags; + + list = list->next; + if (flags & UNINTERESTING) + continue; + if (rev->prune_fn && rev->dense && !(flags & TREECHANGE)) { + if (commit->parents && !commit->parents->next) + continue; + } + n++; + } + return n; +} + +static void show_early_header(struct rev_info *rev, const char *stage, int nr) +{ + if (rev->shown_one) { + rev->shown_one = 0; + if (rev->commit_format != CMIT_FMT_ONELINE) + putchar(rev->diffopt.line_termination); + } + printf("Final output: %d %s\n", nr, stage); +} + +struct itimerval early_output_timer; + 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); while (list && i) { struct commit *commit = list->item; - log_tree_commit(revs, commit); + switch (simplify_commit(revs, commit)) { + case commit_show: + if (show_header) { + int n = estimate_commit_count(revs, list); + show_early_header(revs, "incomplete", n); + show_header = 0; + } + log_tree_commit(revs, commit); + i--; + break; + case commit_ignore: + break; + case commit_error: + return; + } list = list->next; - i--; } + + /* Did we already get enough commits for the early output? */ + if (!i) + return; + + /* + * ..if no, then repeat it twice a second until we + * do. + * + * NOTE! We don't use "it_interval", because if the + * reader isn't listening, we want our output to be + * throttled by the writing, and not have the timer + * trigger every second even if we're blocked on a + * reader! + */ + early_output_timer.it_value.tv_sec = 0; + early_output_timer.it_value.tv_usec = 500000; + setitimer(ITIMER_REAL, &early_output_timer, NULL); } static void early_output(int signal) @@ -98,7 +166,6 @@ static void early_output(int signal) static void setup_early_output(struct rev_info *rev) { struct sigaction sa; - struct itimerval v; /* * Set up the signal handler, minimally intrusively: @@ -120,21 +187,16 @@ static void setup_early_output(struct rev_info *rev) * * This is a one-time-only trigger. */ - memset(&v, 0, sizeof(v)); - v.it_value.tv_sec = 0; - v.it_value.tv_usec = 100000; - setitimer(ITIMER_REAL, &v, NULL); + early_output_timer.it_value.tv_sec = 0; + early_output_timer.it_value.tv_usec = 100000; + setitimer(ITIMER_REAL, &early_output_timer, NULL); } static void finish_early_output(struct rev_info *rev) { + int n = estimate_commit_count(rev, rev->commits); signal(SIGALRM, SIG_IGN); - if (rev->shown_one) { - rev->shown_one = 0; - if (rev->commit_format != CMIT_FMT_ONELINE) - putchar(rev->diffopt.line_termination); - } - printf("Final output:\n"); + show_early_header(rev, "done", n); } static int cmd_log_walk(struct rev_info *rev) -- cgit v1.2.1 From 53b2c823f6e862e0c83a4a25bab43e8c32e9c289 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 5 Nov 2007 13:22:34 -0800 Subject: revision walker: mini clean-up This removes the unnecessary indirection of "revs->prune_fn", since that function is always the same one (or NULL), and there is in fact not even an abstraction reason to make it a function (i.e. its not called from some other file and doesn't allow us to keep the function itself static or anything like that). It then just replaces it with a bit that says "prune or not", and if not pruning, every commit gets TREECHANGE. That in turn means that - if (!revs->prune_fn || (flags & TREECHANGE)) - if (revs->prune_fn && !(flags & TREECHANGE)) just become - if (flags & TREECHANGE) - if (!(flags & TREECHANGE)) respectively. Together with adding the "single_parent()" helper function, the "complex" conditional now becomes if (!(flags & TREECHANGE) && rev->dense && single_parent(commit)) continue; Also indirection of "revs->dense" checking is thrown away the same way, because TREECHANGE bit is set appropriately now. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-log.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'builtin-log.c') diff --git a/builtin-log.c b/builtin-log.c index 981f38872c..d6845bc7f8 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -88,15 +88,9 @@ static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) while (list) { struct commit *commit = list->item; unsigned int flags = commit->object.flags; - list = list->next; - if (flags & UNINTERESTING) - continue; - if (rev->prune_fn && rev->dense && !(flags & TREECHANGE)) { - if (commit->parents && !commit->parents->next) - continue; - } - n++; + if ((flags & TREECHANGE) && !(flags & UNINTERESTING)) + n++; } return n; } -- cgit v1.2.1 From 7dc0fe3be5c949e83e96a1b829be0e72eafffb47 Mon Sep 17 00:00:00 2001 From: Linus Torvalds Date: Mon, 12 Nov 2007 23:16:08 -0800 Subject: Fix parent rewriting in --early-output We cannot tell a node that has been checked and found not to be interesting (which does not have the TREECHANGE flag) from a node that hasn't been checked if it is interesting or not, without relying on something else, such as object->parsed. But an object can get the "parsed" flag for other reasons. Which means that "TREECHANGE" has the wrong polarity. This changes the way how the path pruning logic marks an uninteresting commits. From now on, we consider a commit interesting by default, and explicitly mark the ones we decided to prune. The flag is renamed to "TREESAME". Then, this fixes the logic to show the early output with incomplete pruning. It basically says "a commit that has TREESAME set is kind-of-UNINTERESTING", but obviously in a different way than an outright UNINTERESTING commit. Until we parse and examine enough parents to determine if a commit becomes surely "kind-of-UNINTERESTING", we avoid rewriting the ancestry so that later rounds can fix things up. Signed-off-by: Linus Torvalds Signed-off-by: Junio C Hamano --- builtin-log.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'builtin-log.c') diff --git a/builtin-log.c b/builtin-log.c index d6845bc7f8..54ddaad0ec 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -89,7 +89,7 @@ static int estimate_commit_count(struct rev_info *rev, struct commit_list *list) struct commit *commit = list->item; unsigned int flags = commit->object.flags; list = list->next; - if ((flags & TREECHANGE) && !(flags & UNINTERESTING)) + if (!(flags & (TREESAME | UNINTERESTING))) n++; } return n; -- cgit v1.2.1