From 9219d12777baf67e001329cad98fa21c55d46b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Fri, 5 Apr 2019 02:45:36 +0200 Subject: [PATCH 1/4] progress: make display_progress() return void MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ever since the progress infrastructure was introduced in 96a02f8f6d (common progress display support, 2007-04-18), display_progress() has returned an int, telling callers whether it updated the progress bar or not. However, this is: - useless, because over the last dozen years there has never been a single caller that cared about that return value. - not quite true, because it doesn't print a progress bar when running in the background, yet it returns 1; see 85cb8906f0 (progress: no progress in background, 2015-04-13). The related display_throughput() function returned void already upon its introduction in cf84d51c43 (add throughput to progress display, 2007-10-30). Let's make display_progress() return void, too. While doing so several return statements in display() become unnecessary, remove them. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- progress.c | 13 +++++-------- progress.h | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/progress.c b/progress.c index 5a99c9fbf0..9010032446 100644 --- a/progress.c +++ b/progress.c @@ -78,12 +78,12 @@ static int is_foreground_fd(int fd) return tpgrp < 0 || tpgrp == getpgid(0); } -static int display(struct progress *progress, uint64_t n, const char *done) +static void display(struct progress *progress, uint64_t n, const char *done) { const char *eol, *tp; if (progress->delay && (!progress_update || --progress->delay)) - return 0; + return; progress->last_value = n; tp = (progress->throughput) ? progress->throughput->display.buf : ""; @@ -100,7 +100,6 @@ static int display(struct progress *progress, uint64_t n, const char *done) fflush(stderr); } progress_update = 0; - return 1; } } else if (progress_update) { if (is_foreground_fd(fileno(stderr)) || done) { @@ -109,10 +108,7 @@ static int display(struct progress *progress, uint64_t n, const char *done) fflush(stderr); } progress_update = 0; - return 1; } - - return 0; } static void throughput_string(struct strbuf *buf, uint64_t total, @@ -188,9 +184,10 @@ void display_throughput(struct progress *progress, uint64_t total) display(progress, progress->last_value, NULL); } -int display_progress(struct progress *progress, uint64_t n) +void display_progress(struct progress *progress, uint64_t n) { - return progress ? display(progress, n, NULL) : 0; + if (progress) + display(progress, n, NULL); } static struct progress *start_progress_delay(const char *title, uint64_t total, diff --git a/progress.h b/progress.h index 70a4d4a0d6..59e40cc4fd 100644 --- a/progress.h +++ b/progress.h @@ -4,7 +4,7 @@ struct progress; void display_throughput(struct progress *progress, uint64_t total); -int display_progress(struct progress *progress, uint64_t n); +void display_progress(struct progress *progress, uint64_t n); struct progress *start_progress(const char *title, uint64_t total); struct progress *start_delayed_progress(const char *title, uint64_t total); void stop_progress(struct progress **progress); From d53ba841d4feec0096f5f019ae2d304f1edd226e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Fri, 5 Apr 2019 02:45:37 +0200 Subject: [PATCH 2/4] progress: assemble percentage and counters in a strbuf before printing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following patches in this series want to handle the progress bar's title and changing parts (i.e. the counter and the optional percentage and throughput combined) differently, and need to know the length of the changing parts of the previously displayed progress bar. To prepare for those changes assemble the changing parts in a separate strbuf kept in 'struct progress' before printing. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- progress.c | 35 +++++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/progress.c b/progress.c index 9010032446..564845a36b 100644 --- a/progress.c +++ b/progress.c @@ -36,6 +36,7 @@ struct progress { unsigned delay; struct throughput *throughput; uint64_t start_ns; + struct strbuf counters_sb; }; static volatile sig_atomic_t progress_update; @@ -80,31 +81,39 @@ static int is_foreground_fd(int fd) static void display(struct progress *progress, uint64_t n, const char *done) { - const char *eol, *tp; + const char *tp; + struct strbuf *counters_sb = &progress->counters_sb; + int show_update = 0; if (progress->delay && (!progress_update || --progress->delay)) return; progress->last_value = n; tp = (progress->throughput) ? progress->throughput->display.buf : ""; - eol = done ? done : " \r"; if (progress->total) { unsigned percent = n * 100 / progress->total; if (percent != progress->last_percent || progress_update) { progress->last_percent = percent; - if (is_foreground_fd(fileno(stderr)) || done) { - fprintf(stderr, "%s: %3u%% (%"PRIuMAX"/%"PRIuMAX")%s%s", - progress->title, percent, - (uintmax_t)n, (uintmax_t)progress->total, - tp, eol); - fflush(stderr); - } - progress_update = 0; + + strbuf_reset(counters_sb); + strbuf_addf(counters_sb, + "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent, + (uintmax_t)n, (uintmax_t)progress->total, + tp); + show_update = 1; } } else if (progress_update) { + strbuf_reset(counters_sb); + strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp); + show_update = 1; + } + + if (show_update) { if (is_foreground_fd(fileno(stderr)) || done) { - fprintf(stderr, "%s: %"PRIuMAX"%s%s", - progress->title, (uintmax_t)n, tp, eol); + const char *eol = done ? done : " \r"; + + fprintf(stderr, "%s: %s%s", progress->title, + counters_sb->buf, eol); fflush(stderr); } progress_update = 0; @@ -207,6 +216,7 @@ static struct progress *start_progress_delay(const char *title, uint64_t total, progress->delay = delay; progress->throughput = NULL; progress->start_ns = getnanotime(); + strbuf_init(&progress->counters_sb, 0); set_progress_signal(); return progress; } @@ -250,6 +260,7 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) free(buf); } clear_progress_signal(); + strbuf_release(&progress->counters_sb); if (progress->throughput) strbuf_release(&progress->throughput->display); free(progress->throughput); From 9f1fd84e15a8109f02867e369c747a1cbe766ba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Fri, 12 Apr 2019 21:45:14 +0200 Subject: [PATCH 3/4] progress: clear previous progress update dynamically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the progress bar includes throughput, its length can shorten as the unit of display changes from KiB to MiB. To cover up remnants of the previous progress bar when such a change of units happens we always print three spaces at the end of the progress bar. Alas, covering only three characters is not quite enough: when both the total and the throughput happen to change units from KiB to MiB in the same update, then the progress bar's length is shortened by four characters (or maybe even more!): Receiving objects: 25% (2901/11603), 772.01 KiB | 733.00 KiB/s Receiving objects: 27% (3133/11603), 1.43 MiB | 1.16 MiB/s s and a stray 's' is left behind. So instead of hard-coding the three characters to cover, let's compare the length of the current progress bar with the previous one, and cover up as many characters as needed. Sure, it would be much simpler to just print more spaces at the end of the progress bar, but this approach is more future-proof, and it won't print extra spaces when none are needed, notably when the progress bar doesn't show throughput and thus never shrinks. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- progress.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/progress.c b/progress.c index 564845a36b..949a2a576d 100644 --- a/progress.c +++ b/progress.c @@ -84,6 +84,7 @@ static void display(struct progress *progress, uint64_t n, const char *done) const char *tp; struct strbuf *counters_sb = &progress->counters_sb; int show_update = 0; + int last_count_len = counters_sb->len; if (progress->delay && (!progress_update || --progress->delay)) return; @@ -110,10 +111,12 @@ static void display(struct progress *progress, uint64_t n, const char *done) if (show_update) { if (is_foreground_fd(fileno(stderr)) || done) { - const char *eol = done ? done : " \r"; - - fprintf(stderr, "%s: %s%s", progress->title, - counters_sb->buf, eol); + const char *eol = done ? done : "\r"; + size_t clear_len = counters_sb->len < last_count_len ? + last_count_len - counters_sb->len + 1 : + 0; + fprintf(stderr, "%s: %s%*s", progress->title, + counters_sb->buf, (int) clear_len, eol); fflush(stderr); } progress_update = 0; From 545dc345ebd00adbd96229e8e46b2e8c0b2f1b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?SZEDER=20G=C3=A1bor?= Date: Fri, 12 Apr 2019 21:45:15 +0200 Subject: [PATCH 4/4] progress: break too long progress bar lines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the recently added progress indicators have quite long titles, which might be even longer when translated to some languages, and when they are shown while operating on bigger repositories, then the progress bar grows longer than the default 80 column terminal width. When the progress bar exceeds the width of the terminal it gets line-wrapped, and after that the CR at the end doesn't return to the beginning of the progress bar, but to the first column of its last line. Consequently, the first line of the previously shown progress bar is not overwritten by the next, and we end up with a bunch of truncated progress bar lines scrolling past: $ LANG=es_ES.UTF-8 git commit-graph write Encontrando commits para commit graph entre los objetos empaquetados: 2% (1599 Encontrando commits para commit graph entre los objetos empaquetados: 3% (1975 Encontrando commits para commit graph entre los objetos empaquetados: 4% (2633 Encontrando commits para commit graph entre los objetos empaquetados: 5% (3292 [...] Prevent this by breaking progress bars after the title once they exceed the width of the terminal, so the counter and optional percentage and throughput, i.e. all changing parts, are on the last line. Subsequent updates will from then on only refresh the changing parts, but not the title, and it will look like this: $ LANG=es_ES.UTF-8 ~/src/git/git commit-graph write Encontrando commits para commit graph entre los objetos empaquetados: 100% (6584502/6584502), listo. Calculando números de generación de commit graph: 100% (824705/824705), listo. Escribiendo commit graph en 4 pasos: 100% (3298820/3298820), listo. Note that the number of columns in the terminal is cached by term_columns(), so this might not kick in when it should when a terminal window is resized while the operation is running. Furthermore, this change won't help if the terminal is so narrow that the counters don't fit on one line, but I would put this in the "If it hurts, don't do it" box. Signed-off-by: SZEDER Gábor Signed-off-by: Junio C Hamano --- progress.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/progress.c b/progress.c index 949a2a576d..2d8022a622 100644 --- a/progress.c +++ b/progress.c @@ -8,11 +8,12 @@ * published by the Free Software Foundation. */ -#include "git-compat-util.h" +#include "cache.h" #include "gettext.h" #include "progress.h" #include "strbuf.h" #include "trace.h" +#include "utf8.h" #define TP_IDX_MAX 8 @@ -37,6 +38,8 @@ struct progress { struct throughput *throughput; uint64_t start_ns; struct strbuf counters_sb; + int title_len; + int split; }; static volatile sig_atomic_t progress_update; @@ -115,8 +118,24 @@ static void display(struct progress *progress, uint64_t n, const char *done) size_t clear_len = counters_sb->len < last_count_len ? last_count_len - counters_sb->len + 1 : 0; - fprintf(stderr, "%s: %s%*s", progress->title, - counters_sb->buf, (int) clear_len, eol); + size_t progress_line_len = progress->title_len + + counters_sb->len + 2; + int cols = term_columns(); + + if (progress->split) { + fprintf(stderr, " %s%*s", counters_sb->buf, + (int) clear_len, eol); + } else if (!done && cols < progress_line_len) { + clear_len = progress->title_len + 1 < cols ? + cols - progress->title_len : 0; + fprintf(stderr, "%s:%*s\n %s%s", + progress->title, (int) clear_len, "", + counters_sb->buf, eol); + progress->split = 1; + } else { + fprintf(stderr, "%s: %s%*s", progress->title, + counters_sb->buf, (int) clear_len, eol); + } fflush(stderr); } progress_update = 0; @@ -220,6 +239,8 @@ static struct progress *start_progress_delay(const char *title, uint64_t total, progress->throughput = NULL; progress->start_ns = getnanotime(); strbuf_init(&progress->counters_sb, 0); + progress->title_len = utf8_strwidth(title); + progress->split = 0; set_progress_signal(); return progress; }