]>
Commit | Line | Data |
---|---|---|
ad5611d8 TR |
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 | } |