Subject: [PATCH v2 0/4] Progress display fixes

From: SZEDER Gábor <[email-masked]>
Date: Mon, 1 Apr 2019 13:52:13 +0200

This patch series fixes two progress display issues:

  - When showing throughput, and the both the total and the throughput
    change units in the same update, than the previously shown
    progress bar is not cleaned up properly:

      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

  - When the progress bar is longer than the width of the terminal,
    then 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
      [...]

Patches 3 and 4 fix these two issues, while the first three are
minor preparatory cleanups and refactorings.

Changes since v1:

  - Use utf8_strwidth().

  - Dropped patch 2/5 (progress: return early when in the background).

    Consequently, the new patch 2/4 (progress: assemble percentage and
    counters in a strbuf before printing; was patch 3/5 in v1) moves
    the is_foreground_fd() check around a bit, resulting in enough
    changes that range-diff can't match the new patch 3/4 to the old
    4/5.  With increased '--creation-factor' it's able to line up
    those two patches, and shows the updates to the commit message,
    but the resulting diff-of-a-diff looks utterly unreadable to me.
    I've included an interdiff as well, as I find it much more
    telling.

  - Commit message updates.

SZEDER Gábor (4):
  progress: make display_progress() return void
  progress: assemble percentage and counters in a strbuf before printing
  progress: clear previous progress update dynamically
  progress: break too long progress bar lines

 progress.c | 73 +++++++++++++++++++++++++++++++++++++++---------------
 progress.h |  2 +-
 2 files changed, 54 insertions(+), 21 deletions(-)

Interdiff:
diff --git a/progress.c b/progress.c
index 97bc4b04e8..e28ccdafd2 100644
--- a/progress.c
+++ b/progress.c
@@ -86,19 +86,13 @@ static void display(struct progress *progress, uint64_t n, const char *done)
 {
 	const char *tp;
 	struct strbuf *counters_sb = &progress->counters_sb;
-	int update = 0;
+	int show_update = 0;
 	int last_count_len = counters_sb->len;
 
 	if (progress->delay && (!progress_update || --progress->delay))
 		return;
 
 	progress->last_value = n;
-
-	if (!is_foreground_fd(fileno(stderr)) && !done) {
-		progress_update = 0;
-		return;
-	}
-
 	tp = (progress->throughput) ? progress->throughput->display.buf : "";
 	if (progress->total) {
 		unsigned percent = n * 100 / progress->total;
@@ -110,35 +104,39 @@ static void display(struct progress *progress, uint64_t n, const char *done)
 				    "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent,
 				    (uintmax_t)n, (uintmax_t)progress->total,
 				    tp);
-			update = 1;
+			show_update = 1;
 		}
 	} else if (progress_update) {
 		strbuf_reset(counters_sb);
 		strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp);
-		update = 1;
+		show_update = 1;
 	}
 
-	if (update) {
-		const char *eol = done ? done : "\r";
-		int clear_len = counters_sb->len < last_count_len ?
-				last_count_len - counters_sb->len : 0;
-		int cols = term_columns();
-
-		if (progress->split) {
-			fprintf(stderr, "  %s%-*s", counters_sb->buf, clear_len,
-				eol);
-		} else if (!done &&
-			   cols < progress->title_len + counters_sb->len + 2) {
-			clear_len = progress->title_len + 1 < cols ?
-				    cols - progress->title_len - 1 : 0;
-			fprintf(stderr, "%s:%*s\n  %s%s", progress->title,
-					clear_len, "", counters_sb->buf, eol);
-			progress->split = 1;
-		} else {
-			fprintf(stderr, "%s: %s%-*s", progress->title,
-				counters_sb->buf, clear_len, eol);
+	if (show_update) {
+		if (is_foreground_fd(fileno(stderr)) || done) {
+			const char *eol = done ? done : "\r";
+			int clear_len = counters_sb->len < last_count_len ?
+					last_count_len - counters_sb->len : 0;
+			int progress_line_len = progress->title_len +
+						counters_sb->len + 2;
+			int cols = term_columns();
+
+			if (progress->split) {
+				fprintf(stderr, "  %s%-*s", counters_sb->buf,
+					clear_len, eol);
+			} else if (!done && cols < progress_line_len) {
+				clear_len = progress->title_len + 1 < cols ?
+					    cols - progress->title_len - 1 : 0;
+				fprintf(stderr, "%s:%*s\n  %s%s",
+					progress->title, clear_len, "",
+					counters_sb->buf, eol);
+				progress->split = 1;
+			} else {
+				fprintf(stderr, "%s: %s%-*s", progress->title,
+					counters_sb->buf, clear_len, eol);
+			}
+			fflush(stderr);
 		}
-		fflush(stderr);
 		progress_update = 0;
 	}
 
@@ -242,7 +240,7 @@ 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 = strlen(title);
+	progress->title_len = utf8_strwidth(title);
 	progress->split = 0;
 	set_progress_signal();
 	return progress;
Range-diff:
1:  dea36bd2a7 = 1:  dea36bd2a7 progress: make display_progress() return void
2:  159a0b9561 < -:  ---------- progress: return early when in the background
3:  ecd6b0fd68 ! 2:  97de2a98a0 progress: assemble percentage and counters in a strbuf before printing
    @@ -4,10 +4,11 @@
     
         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 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 before printing.
    +    strbuf kept in 'struct progress' before printing.
     
         Signed-off-by: SZEDER Gábor <[email-masked]>
     
    @@ -29,24 +30,25 @@
     -	const char *eol, *tp;
     +	const char *tp;
     +	struct strbuf *counters_sb = &progress->counters_sb;
    -+	int update = 0;
    ++	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;
    --			fprintf(stderr, "%s: %3u%% (%"PRIuMAX"/%"PRIuMAX")%s%s",
    --				progress->title, percent,
    --				(uintmax_t)n, (uintmax_t)progress->total,
    --				tp, eol);
    --			fflush(stderr);
    +-			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;
     -			return;
     +
    @@ -55,22 +57,24 @@
     +				    "%3u%% (%"PRIuMAX"/%"PRIuMAX")%s", percent,
     +				    (uintmax_t)n, (uintmax_t)progress->total,
     +				    tp);
    -+			update = 1;
    ++			show_update = 1;
      		}
      	} else if (progress_update) {
    --		fprintf(stderr, "%s: %"PRIuMAX"%s%s",
    --			progress->title, (uintmax_t)n, tp, eol);
     +		strbuf_reset(counters_sb);
     +		strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp);
    -+		update = 1;
    ++		show_update = 1;
     +	}
     +
    -+	if (update) {
    -+		const char *eol = done ? done : "   \r";
    ++	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);
    ++			fprintf(stderr, "%s: %s%s", progress->title,
    ++				counters_sb->buf, eol);
    + 			fflush(stderr);
    + 		}
      		progress_update = 0;
     -		return;
      	}
4:  e360365f18 ! 3:  edfe0157a7 progress: clear previous progress update dynamically
    @@ -10,7 +10,7 @@
         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:
    +    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
    @@ -21,11 +21,10 @@
         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 four spaces instead of
    -    three 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.
    +    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 <[email-masked]>
     
    @@ -35,24 +34,24 @@
     @@
      	const char *tp;
      	struct strbuf *counters_sb = &progress->counters_sb;
    - 	int update = 0;
    + 	int show_update = 0;
     +	int last_count_len = counters_sb->len;
      
      	if (progress->delay && (!progress_update || --progress->delay))
      		return;
     @@
    - 	}
      
    - 	if (update) {
    --		const char *eol = done ? done : "   \r";
    + 	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";
    -+		int clear_len = counters_sb->len < last_count_len ?
    -+				last_count_len - counters_sb->len : 0;
    -+		fprintf(stderr, "%s: %s%-*s", progress->title,
    -+			counters_sb->buf, clear_len, eol);
    - 		fflush(stderr);
    +-			fprintf(stderr, "%s: %s%s", progress->title,
    +-				counters_sb->buf, eol);
    ++			const char *eol = done ? done : "\r";
    ++			int clear_len = counters_sb->len < last_count_len ?
    ++					last_count_len - counters_sb->len : 0;
    ++			fprintf(stderr, "%s: %s%-*s", progress->title,
    ++				counters_sb->buf, clear_len, eol);
    + 			fflush(stderr);
    + 		}
      		progress_update = 0;
    - 	}
5:  cbd3be1c6d ! 4:  d53de231ee progress: break too long progress bar lines
    @@ -69,35 +69,37 @@
      
      static volatile sig_atomic_t progress_update;
     @@
    - 		const char *eol = done ? done : "\r";
    - 		int clear_len = counters_sb->len < last_count_len ?
    - 				last_count_len - counters_sb->len : 0;
    --		fprintf(stderr, "%s: %s%-*s", progress->title,
    --			counters_sb->buf, clear_len, eol);
    -+		int cols = term_columns();
    + 			const char *eol = done ? done : "\r";
    + 			int clear_len = counters_sb->len < last_count_len ?
    + 					last_count_len - counters_sb->len : 0;
    +-			fprintf(stderr, "%s: %s%-*s", progress->title,
    +-				counters_sb->buf, clear_len, eol);
    ++			int progress_line_len = progress->title_len +
    ++						counters_sb->len + 2;
    ++			int cols = term_columns();
     +
    -+		if (progress->split) {
    -+			fprintf(stderr, "  %s%-*s", counters_sb->buf, clear_len,
    -+				eol);
    -+		} else if (!done &&
    -+			   cols < progress->title_len + counters_sb->len + 2) {
    -+			clear_len = progress->title_len + 1 < cols ?
    -+				    cols - progress->title_len - 1 : 0;
    -+			fprintf(stderr, "%s:%*s\n  %s%s", progress->title,
    -+					clear_len, "", counters_sb->buf, eol);
    -+			progress->split = 1;
    -+		} else {
    -+			fprintf(stderr, "%s: %s%-*s", progress->title,
    -+				counters_sb->buf, clear_len, eol);
    -+		}
    - 		fflush(stderr);
    ++			if (progress->split) {
    ++				fprintf(stderr, "  %s%-*s", counters_sb->buf,
    ++					clear_len, eol);
    ++			} else if (!done && cols < progress_line_len) {
    ++				clear_len = progress->title_len + 1 < cols ?
    ++					    cols - progress->title_len - 1 : 0;
    ++				fprintf(stderr, "%s:%*s\n  %s%s",
    ++					progress->title, clear_len, "",
    ++					counters_sb->buf, eol);
    ++				progress->split = 1;
    ++			} else {
    ++				fprintf(stderr, "%s: %s%-*s", progress->title,
    ++					counters_sb->buf, clear_len, eol);
    ++			}
    + 			fflush(stderr);
    + 		}
      		progress_update = 0;
    - 	}
     @@
      	progress->throughput = NULL;
      	progress->start_ns = getnanotime();
      	strbuf_init(&progress->counters_sb, 0);
    -+	progress->title_len = strlen(title);
    ++	progress->title_len = utf8_strwidth(title);
     +	progress->split = 0;
      	set_progress_signal();
      	return progress;
-- 
2.21.0.539.g07239c3a71.dirty