2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
16 * Show updates to the percentage and number of objects received
17 * separately from the throughput to give an accurate progress while
18 * avoiding too much noise on the screen.
20 #define PROGRESS_UPDATE_TIME 0.10
21 #define THROUGHPUT_UPDATE_TIME 1.00
23 #define is_nl(c) ((c) == '\r' || (c) == '\n')
25 #define return_os_error(msg) do { \
26 git_error_set(GIT_ERROR_OS, "%s", msg); return -1; } while(0)
28 GIT_INLINE(size_t) no_nl_len(const char *str
, size_t len
)
32 while (i
< len
&& !is_nl(str
[i
]))
38 GIT_INLINE(size_t) nl_len(bool *has_nl
, const char *str
, size_t len
)
40 size_t i
= no_nl_len(str
, len
);
44 while (i
< len
&& is_nl(str
[i
])) {
52 static int progress_write(cli_progress
*progress
, bool force
, git_str
*line
)
55 size_t no_nl
= no_nl_len(line
->ptr
, line
->size
);
56 size_t nl
= nl_len(&has_nl
, line
->ptr
+ no_nl
, line
->size
- no_nl
);
57 double now
= git__timer();
60 /* Avoid spamming the console with progress updates */
61 if (!force
&& line
->ptr
[line
->size
- 1] != '\n' && progress
->last_update
) {
62 if (now
- progress
->last_update
< PROGRESS_UPDATE_TIME
) {
63 git_str_clear(&progress
->deferred
);
64 git_str_put(&progress
->deferred
, line
->ptr
, line
->size
);
65 return git_str_oom(&progress
->deferred
) ? -1 : 0;
70 * If there's something on this line already (eg, a progress line
71 * with only a trailing `\r` that we'll print over) then we need
72 * to really print over it in case we're writing a shorter line.
74 if (printf("%.*s", (int)no_nl
, line
->ptr
) < 0)
75 return_os_error("could not print status");
77 if (progress
->onscreen
.size
) {
78 for (i
= no_nl
; i
< progress
->onscreen
.size
; i
++) {
80 return_os_error("could not print status");
84 if (printf("%.*s", (int)nl
, line
->ptr
+ no_nl
) < 0 ||
86 return_os_error("could not print status");
88 git_str_clear(&progress
->onscreen
);
90 if (line
->ptr
[line
->size
- 1] == '\n') {
91 progress
->last_update
= 0;
93 git_str_put(&progress
->onscreen
, line
->ptr
, line
->size
);
94 progress
->last_update
= now
;
97 git_str_clear(&progress
->deferred
);
98 return git_str_oom(&progress
->onscreen
) ? -1 : 0;
101 static int progress_printf(cli_progress
*progress
, bool force
, const char *fmt
, ...)
102 GIT_FORMAT_PRINTF(3, 4);
104 int progress_printf(cli_progress
*progress
, bool force
, const char *fmt
, ...)
106 git_str buf
= GIT_BUF_INIT
;
111 error
= git_str_vprintf(&buf
, fmt
, ap
);
117 error
= progress_write(progress
, force
, &buf
);
119 git_str_dispose(&buf
);
123 static int progress_complete(cli_progress
*progress
)
125 if (progress
->deferred
.size
)
126 progress_write(progress
, true, &progress
->deferred
);
128 if (progress
->onscreen
.size
)
129 if (printf("\n") < 0)
130 return_os_error("could not print status");
132 git_str_clear(&progress
->deferred
);
133 git_str_clear(&progress
->onscreen
);
134 progress
->last_update
= 0;
135 progress
->action_start
= 0;
136 progress
->action_finish
= 0;
141 GIT_INLINE(int) percent(size_t completed
, size_t total
)
144 return (completed
== 0) ? 100 : 0;
146 return (int)(((double)completed
/ (double)total
) * 100);
149 int cli_progress_fetch_sideband(const char *str
, int len
, void *payload
)
151 cli_progress
*progress
= (cli_progress
*)payload
;
157 /* Accumulate the sideband data, then print it line-at-a-time. */
158 if (git_str_put(&progress
->sideband
, str
, len
) < 0)
161 str
= progress
->sideband
.ptr
;
162 remain
= progress
->sideband
.size
;
166 size_t line_len
= nl_len(&has_nl
, str
, remain
);
171 if (line_len
< INT_MAX
) {
172 int error
= progress_printf(progress
, true,
173 "remote: %.*s", (int)line_len
, str
);
183 git_str_consume_bytes(&progress
->sideband
, (progress
->sideband
.size
- remain
));
188 static int fetch_receiving(
189 cli_progress
*progress
,
190 const git_indexer_progress
*stats
)
192 char *recv_units
[] = { "B", "KiB", "MiB", "GiB", "TiB", NULL
};
193 char *rate_units
[] = { "B/s", "KiB/s", "MiB/s", "GiB/s", "TiB/s", NULL
};
195 double now
, recv_len
, rate
, elapsed
;
196 size_t recv_unit_idx
= 0, rate_unit_idx
= 0;
197 bool done
= (stats
->received_objects
== stats
->total_objects
);
199 if (!progress
->action_start
)
200 progress
->action_start
= git__timer();
202 if (done
&& progress
->action_finish
)
203 now
= progress
->action_finish
;
205 progress
->action_finish
= now
= git__timer();
209 if (progress
->throughput_update
&&
210 now
- progress
->throughput_update
< THROUGHPUT_UPDATE_TIME
) {
211 elapsed
= progress
->throughput_update
-
212 progress
->action_start
;
213 recv_len
= progress
->throughput_bytes
;
215 elapsed
= now
- progress
->action_start
;
216 recv_len
= (double)stats
->received_bytes
;
218 progress
->throughput_update
= now
;
219 progress
->throughput_bytes
= recv_len
;
222 rate
= elapsed
? recv_len
/ elapsed
: 0;
224 while (recv_len
> 1024 && recv_units
[recv_unit_idx
+1]) {
229 while (rate
> 1024 && rate_units
[rate_unit_idx
+1]) {
234 return progress_printf(progress
, false,
235 "Receiving objects: %3d%% (%d/%d), %.2f %s | %.2f %s%s\r",
236 percent(stats
->received_objects
, stats
->total_objects
),
237 stats
->received_objects
,
238 stats
->total_objects
,
239 recv_len
, recv_units
[recv_unit_idx
],
240 rate
, rate_units
[rate_unit_idx
],
241 done
? ", done." : "");
244 static int fetch_resolving(
245 cli_progress
*progress
,
246 const git_indexer_progress
*stats
)
248 bool done
= (stats
->indexed_deltas
== stats
->total_deltas
);
250 return progress_printf(progress
, false,
251 "Resolving deltas: %3d%% (%d/%d)%s\r",
252 percent(stats
->indexed_deltas
, stats
->total_deltas
),
253 stats
->indexed_deltas
, stats
->total_deltas
,
254 done
? ", done." : "");
257 int cli_progress_fetch_transfer(const git_indexer_progress
*stats
, void *payload
)
259 cli_progress
*progress
= (cli_progress
*)payload
;
262 switch (progress
->action
) {
263 case CLI_PROGRESS_NONE
:
264 progress
->action
= CLI_PROGRESS_RECEIVING
;
267 case CLI_PROGRESS_RECEIVING
:
268 if ((error
= fetch_receiving(progress
, stats
)) < 0)
272 * Upgrade from receiving to resolving; do this after the
273 * final call to cli_progress_fetch_receiving (above) to
274 * ensure that we've printed a final "done" string after
277 if (!stats
->indexed_deltas
)
280 progress_complete(progress
);
281 progress
->action
= CLI_PROGRESS_RESOLVING
;
284 case CLI_PROGRESS_RESOLVING
:
285 error
= fetch_resolving(progress
, stats
);
289 /* should not be reached */
290 GIT_ASSERT(!"unexpected progress state");
296 void cli_progress_checkout(
298 size_t completed_steps
,
302 cli_progress
*progress
= (cli_progress
*)payload
;
303 bool done
= (completed_steps
== total_steps
);
307 if (progress
->action
!= CLI_PROGRESS_CHECKING_OUT
) {
308 progress_complete(progress
);
309 progress
->action
= CLI_PROGRESS_CHECKING_OUT
;
312 progress_printf(progress
, false,
313 "Checking out files: %3d%% (%" PRIuZ
"/%" PRIuZ
")%s\r",
314 percent(completed_steps
, total_steps
),
315 completed_steps
, total_steps
,
316 done
? ", done." : "");
319 int cli_progress_abort(cli_progress
*progress
)
321 if (progress
->onscreen
.size
> 0 && printf("\n") < 0)
322 return_os_error("could not print status");
327 int cli_progress_finish(cli_progress
*progress
)
329 int error
= progress
->action
? progress_complete(progress
) : 0;
331 progress
->action
= 0;
335 void cli_progress_dispose(cli_progress
*progress
)
337 if (progress
== NULL
)
340 git_str_dispose(&progress
->sideband
);
341 git_str_dispose(&progress
->onscreen
);
342 git_str_dispose(&progress
->deferred
);
344 memset(progress
, 0, sizeof(cli_progress
));