]> git.proxmox.com Git - libgit2.git/blob - src/cli/progress.c
New upstream version 1.5.0+ds
[libgit2.git] / src / cli / progress.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
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.
6 */
7
8 #include <stdio.h>
9 #include <stdarg.h>
10 #include <stdint.h>
11
12 #include "progress.h"
13 #include "error.h"
14
15 /*
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.
19 */
20 #define PROGRESS_UPDATE_TIME 0.10
21 #define THROUGHPUT_UPDATE_TIME 1.00
22
23 #define is_nl(c) ((c) == '\r' || (c) == '\n')
24
25 #define return_os_error(msg) do { \
26 git_error_set(GIT_ERROR_OS, "%s", msg); return -1; } while(0)
27
28 GIT_INLINE(size_t) no_nl_len(const char *str, size_t len)
29 {
30 size_t i = 0;
31
32 while (i < len && !is_nl(str[i]))
33 i++;
34
35 return i;
36 }
37
38 GIT_INLINE(size_t) nl_len(bool *has_nl, const char *str, size_t len)
39 {
40 size_t i = no_nl_len(str, len);
41
42 *has_nl = false;
43
44 while (i < len && is_nl(str[i])) {
45 *has_nl = true;
46 i++;
47 }
48
49 return i;
50 }
51
52 static int progress_write(cli_progress *progress, bool force, git_str *line)
53 {
54 bool has_nl;
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();
58 size_t i;
59
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;
66 }
67 }
68
69 /*
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.
73 */
74 if (printf("%.*s", (int)no_nl, line->ptr) < 0)
75 return_os_error("could not print status");
76
77 if (progress->onscreen.size) {
78 for (i = no_nl; i < progress->onscreen.size; i++) {
79 if (printf(" ") < 0)
80 return_os_error("could not print status");
81 }
82 }
83
84 if (printf("%.*s", (int)nl, line->ptr + no_nl) < 0 ||
85 fflush(stdout) != 0)
86 return_os_error("could not print status");
87
88 git_str_clear(&progress->onscreen);
89
90 if (line->ptr[line->size - 1] == '\n') {
91 progress->last_update = 0;
92 } else {
93 git_str_put(&progress->onscreen, line->ptr, line->size);
94 progress->last_update = now;
95 }
96
97 git_str_clear(&progress->deferred);
98 return git_str_oom(&progress->onscreen) ? -1 : 0;
99 }
100
101 static int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
102 GIT_FORMAT_PRINTF(3, 4);
103
104 int progress_printf(cli_progress *progress, bool force, const char *fmt, ...)
105 {
106 git_str buf = GIT_BUF_INIT;
107 va_list ap;
108 int error;
109
110 va_start(ap, fmt);
111 error = git_str_vprintf(&buf, fmt, ap);
112 va_end(ap);
113
114 if (error < 0)
115 return error;
116
117 error = progress_write(progress, force, &buf);
118
119 git_str_dispose(&buf);
120 return error;
121 }
122
123 static int progress_complete(cli_progress *progress)
124 {
125 if (progress->deferred.size)
126 progress_write(progress, true, &progress->deferred);
127
128 if (progress->onscreen.size)
129 if (printf("\n") < 0)
130 return_os_error("could not print status");
131
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;
137
138 return 0;
139 }
140
141 GIT_INLINE(int) percent(size_t completed, size_t total)
142 {
143 if (total == 0)
144 return (completed == 0) ? 100 : 0;
145
146 return (int)(((double)completed / (double)total) * 100);
147 }
148
149 int cli_progress_fetch_sideband(const char *str, int len, void *payload)
150 {
151 cli_progress *progress = (cli_progress *)payload;
152 size_t remain;
153
154 if (len <= 0)
155 return 0;
156
157 /* Accumulate the sideband data, then print it line-at-a-time. */
158 if (git_str_put(&progress->sideband, str, len) < 0)
159 return -1;
160
161 str = progress->sideband.ptr;
162 remain = progress->sideband.size;
163
164 while (remain) {
165 bool has_nl;
166 size_t line_len = nl_len(&has_nl, str, remain);
167
168 if (!has_nl)
169 break;
170
171 if (line_len < INT_MAX) {
172 int error = progress_printf(progress, true,
173 "remote: %.*s", (int)line_len, str);
174
175 if (error < 0)
176 return error;
177 }
178
179 str += line_len;
180 remain -= line_len;
181 }
182
183 git_str_consume_bytes(&progress->sideband, (progress->sideband.size - remain));
184
185 return 0;
186 }
187
188 static int fetch_receiving(
189 cli_progress *progress,
190 const git_indexer_progress *stats)
191 {
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 };
194
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);
198
199 if (!progress->action_start)
200 progress->action_start = git__timer();
201
202 if (done && progress->action_finish)
203 now = progress->action_finish;
204 else if (done)
205 progress->action_finish = now = git__timer();
206 else
207 now = git__timer();
208
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;
214 } else {
215 elapsed = now - progress->action_start;
216 recv_len = (double)stats->received_bytes;
217
218 progress->throughput_update = now;
219 progress->throughput_bytes = recv_len;
220 }
221
222 rate = elapsed ? recv_len / elapsed : 0;
223
224 while (recv_len > 1024 && recv_units[recv_unit_idx+1]) {
225 recv_len /= 1024;
226 recv_unit_idx++;
227 }
228
229 while (rate > 1024 && rate_units[rate_unit_idx+1]) {
230 rate /= 1024;
231 rate_unit_idx++;
232 }
233
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." : "");
242 }
243
244 static int fetch_resolving(
245 cli_progress *progress,
246 const git_indexer_progress *stats)
247 {
248 bool done = (stats->indexed_deltas == stats->total_deltas);
249
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." : "");
255 }
256
257 int cli_progress_fetch_transfer(const git_indexer_progress *stats, void *payload)
258 {
259 cli_progress *progress = (cli_progress *)payload;
260 int error = 0;
261
262 switch (progress->action) {
263 case CLI_PROGRESS_NONE:
264 progress->action = CLI_PROGRESS_RECEIVING;
265 /* fall through */
266
267 case CLI_PROGRESS_RECEIVING:
268 if ((error = fetch_receiving(progress, stats)) < 0)
269 break;
270
271 /*
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
275 * any sideband data.
276 */
277 if (!stats->indexed_deltas)
278 break;
279
280 progress_complete(progress);
281 progress->action = CLI_PROGRESS_RESOLVING;
282 /* fall through */
283
284 case CLI_PROGRESS_RESOLVING:
285 error = fetch_resolving(progress, stats);
286 break;
287
288 default:
289 /* should not be reached */
290 GIT_ASSERT(!"unexpected progress state");
291 }
292
293 return error;
294 }
295
296 void cli_progress_checkout(
297 const char *path,
298 size_t completed_steps,
299 size_t total_steps,
300 void *payload)
301 {
302 cli_progress *progress = (cli_progress *)payload;
303 bool done = (completed_steps == total_steps);
304
305 GIT_UNUSED(path);
306
307 if (progress->action != CLI_PROGRESS_CHECKING_OUT) {
308 progress_complete(progress);
309 progress->action = CLI_PROGRESS_CHECKING_OUT;
310 }
311
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." : "");
317 }
318
319 int cli_progress_abort(cli_progress *progress)
320 {
321 if (progress->onscreen.size > 0 && printf("\n") < 0)
322 return_os_error("could not print status");
323
324 return 0;
325 }
326
327 int cli_progress_finish(cli_progress *progress)
328 {
329 int error = progress->action ? progress_complete(progress) : 0;
330
331 progress->action = 0;
332 return error;
333 }
334
335 void cli_progress_dispose(cli_progress *progress)
336 {
337 if (progress == NULL)
338 return;
339
340 git_str_dispose(&progress->sideband);
341 git_str_dispose(&progress->onscreen);
342 git_str_dispose(&progress->deferred);
343
344 memset(progress, 0, sizeof(cli_progress));
345 }