2 #include "add-interactive.h"
4 #include "run-command.h"
5 #include "argv-array.h"
10 enum prompt_mode_type {
11 PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK
14 static const char *prompt_mode[] = {
15 N_("Stage mode change [y,n,a,d%s,?]? "),
16 N_("Stage deletion [y,n,a,d%s,?]? "),
17 N_("Stage this hunk [y,n,a,d%s,?]? ")
21 unsigned long old_offset, old_count, new_offset, new_count;
23 * Start/end offsets to the extra text after the second `@@` in the
24 * hunk header, e.g. the function signature. This is expected to
25 * include the newline.
27 size_t extra_start, extra_end, colored_extra_start, colored_extra_end;
31 size_t start, end, colored_start, colored_end, splittable_into;
32 enum { UNDECIDED_HUNK = 0, SKIP_HUNK, USE_HUNK } use;
33 struct hunk_header header;
38 struct strbuf answer, buf;
41 struct strbuf plain, colored;
45 size_t hunk_nr, hunk_alloc;
46 unsigned deleted:1, mode_change:1;
51 static void err(struct add_p_state *s, const char *fmt, ...)
56 fputs(s->s.error_color, stderr);
57 vfprintf(stderr, fmt, args);
58 fputs(s->s.reset_color, stderr);
63 static void setup_child_process(struct add_p_state *s,
64 struct child_process *cp, ...)
70 while ((arg = va_arg(ap, const char *)))
71 argv_array_push(&cp->args, arg);
75 argv_array_pushf(&cp->env_array,
76 INDEX_ENVIRONMENT "=%s", s->s.r->index_file);
79 static int parse_range(const char **p,
80 unsigned long *offset, unsigned long *count)
84 *offset = strtoul(*p, &pend, 10);
92 *count = strtoul(pend + 1, (char **)p, 10);
93 return *p == pend + 1 ? -1 : 0;
96 static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk)
98 struct hunk_header *header = &hunk->header;
99 const char *line = s->plain.buf + hunk->start, *p = line;
100 char *eol = memchr(p, '\n', s->plain.len - hunk->start);
103 eol = s->plain.buf + s->plain.len;
105 if (!skip_prefix(p, "@@ -", &p) ||
106 parse_range(&p, &header->old_offset, &header->old_count) < 0 ||
107 !skip_prefix(p, " +", &p) ||
108 parse_range(&p, &header->new_offset, &header->new_count) < 0 ||
109 !skip_prefix(p, " @@", &p))
110 return error(_("could not parse hunk header '%.*s'"),
111 (int)(eol - line), line);
113 hunk->start = eol - s->plain.buf + (*eol == '\n');
114 header->extra_start = p - s->plain.buf;
115 header->extra_end = hunk->start;
117 if (!s->colored.len) {
118 header->colored_extra_start = header->colored_extra_end = 0;
122 /* Now find the extra text in the colored diff */
123 line = s->colored.buf + hunk->colored_start;
124 eol = memchr(line, '\n', s->colored.len - hunk->colored_start);
126 eol = s->colored.buf + s->colored.len;
127 p = memmem(line, eol - line, "@@ -", 4);
129 return error(_("could not parse colored hunk header '%.*s'"),
130 (int)(eol - line), line);
131 p = memmem(p + 4, eol - p - 4, " @@", 3);
133 return error(_("could not parse colored hunk header '%.*s'"),
134 (int)(eol - line), line);
135 hunk->colored_start = eol - s->colored.buf + (*eol == '\n');
136 header->colored_extra_start = p + 3 - s->colored.buf;
137 header->colored_extra_end = hunk->colored_start;
142 static int is_octal(const char *p, size_t len)
148 if (*p < '0' || *(p++) > '7')
153 static int parse_diff(struct add_p_state *s, const struct pathspec *ps)
155 struct argv_array args = ARGV_ARRAY_INIT;
156 struct strbuf *plain = &s->plain, *colored = NULL;
157 struct child_process cp = CHILD_PROCESS_INIT;
158 char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0';
159 size_t file_diff_alloc = 0, i, color_arg_index;
160 struct file_diff *file_diff = NULL;
161 struct hunk *hunk = NULL;
164 /* Use `--no-color` explicitly, just in case `diff.color = always`. */
165 argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL);
166 color_arg_index = args.argc - 2;
167 for (i = 0; i < ps->nr; i++)
168 argv_array_push(&args, ps->items[i].original);
170 setup_child_process(s, &cp, NULL);
172 res = capture_command(&cp, plain, 0);
174 argv_array_clear(&args);
175 return error(_("could not parse diff"));
178 argv_array_clear(&args);
181 strbuf_complete_line(plain);
183 if (want_color_fd(1, -1)) {
184 struct child_process colored_cp = CHILD_PROCESS_INIT;
186 setup_child_process(s, &colored_cp, NULL);
187 xsnprintf((char *)args.argv[color_arg_index], 8, "--color");
188 colored_cp.argv = args.argv;
189 colored = &s->colored;
190 res = capture_command(&colored_cp, colored, 0);
191 argv_array_clear(&args);
193 return error(_("could not parse colored diff"));
194 strbuf_complete_line(colored);
195 colored_p = colored->buf;
196 colored_pend = colored_p + colored->len;
198 argv_array_clear(&args);
200 /* parse files and hunks */
202 pend = p + plain->len;
204 char *eol = memchr(p, '\n', pend - p);
205 const char *deleted = NULL, *mode_change = NULL;
210 if (starts_with(p, "diff ")) {
212 ALLOC_GROW(s->file_diff, s->file_diff_nr,
214 file_diff = s->file_diff + s->file_diff_nr - 1;
215 memset(file_diff, 0, sizeof(*file_diff));
216 hunk = &file_diff->head;
217 hunk->start = p - plain->buf;
219 hunk->colored_start = colored_p - colored->buf;
221 } else if (p == plain->buf)
222 BUG("diff starts with unexpected line:\n"
223 "%.*s\n", (int)(eol - p), p);
224 else if (file_diff->deleted)
225 ; /* keep the rest of the file in a single "hunk" */
226 else if (starts_with(p, "@@ ") ||
227 (hunk == &file_diff->head &&
228 skip_prefix(p, "deleted file", &deleted))) {
229 if (marker == '-' || marker == '+')
231 * Should not happen; previous hunk did not end
232 * in a context line? Handle it anyway.
234 hunk->splittable_into++;
236 file_diff->hunk_nr++;
237 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
238 file_diff->hunk_alloc);
239 hunk = file_diff->hunk + file_diff->hunk_nr - 1;
240 memset(hunk, 0, sizeof(*hunk));
242 hunk->start = p - plain->buf;
244 hunk->colored_start = colored_p - colored->buf;
247 file_diff->deleted = 1;
248 else if (parse_hunk_header(s, hunk) < 0)
252 * Start counting into how many hunks this one can be
256 } else if (hunk == &file_diff->head &&
257 skip_prefix(p, "old mode ", &mode_change) &&
258 is_octal(mode_change, eol - mode_change)) {
259 if (file_diff->mode_change)
260 BUG("double mode change?\n\n%.*s",
261 (int)(eol - plain->buf), plain->buf);
262 if (file_diff->hunk_nr++)
263 BUG("mode change in the middle?\n\n%.*s",
264 (int)(eol - plain->buf), plain->buf);
267 * Do *not* change `hunk`: the mode change pseudo-hunk
268 * is _part of_ the header "hunk".
270 file_diff->mode_change = 1;
271 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr,
272 file_diff->hunk_alloc);
273 memset(file_diff->hunk, 0, sizeof(struct hunk));
274 file_diff->hunk->start = p - plain->buf;
276 file_diff->hunk->colored_start =
277 colored_p - colored->buf;
278 } else if (hunk == &file_diff->head &&
279 skip_prefix(p, "new mode ", &mode_change) &&
280 is_octal(mode_change, eol - mode_change)) {
283 * Extend the "mode change" pseudo-hunk to include also
284 * the "new mode" line.
286 if (!file_diff->mode_change)
287 BUG("'new mode' without 'old mode'?\n\n%.*s",
288 (int)(eol - plain->buf), plain->buf);
289 if (file_diff->hunk_nr != 1)
290 BUG("mode change in the middle?\n\n%.*s",
291 (int)(eol - plain->buf), plain->buf);
292 if (p - plain->buf != file_diff->hunk->end)
293 BUG("'new mode' does not immediately follow "
294 "'old mode'?\n\n%.*s",
295 (int)(eol - plain->buf), plain->buf);
298 if (file_diff->deleted && file_diff->mode_change)
299 BUG("diff contains delete *and* a mode change?!?\n%.*s",
300 (int)(eol - (plain->buf + file_diff->head.start)),
301 plain->buf + file_diff->head.start);
303 if ((marker == '-' || marker == '+') && *p == ' ')
304 hunk->splittable_into++;
305 if (marker && *p != '\\')
308 p = eol == pend ? pend : eol + 1;
309 hunk->end = p - plain->buf;
312 char *colored_eol = memchr(colored_p, '\n',
313 colored_pend - colored_p);
315 colored_p = colored_eol + 1;
317 colored_p = colored_pend;
319 hunk->colored_end = colored_p - colored->buf;
323 if (file_diff->hunk_nr != 1)
324 BUG("mode change in hunk #%d???",
325 (int)file_diff->hunk_nr);
326 /* Adjust the end of the "mode change" pseudo-hunk */
327 file_diff->hunk->end = hunk->end;
329 file_diff->hunk->colored_end = hunk->colored_end;
333 if (marker == '-' || marker == '+')
335 * Last hunk ended in non-context line (i.e. it appended lines
336 * to the file, so there are no trailing context lines).
338 hunk->splittable_into++;
343 static size_t find_next_line(struct strbuf *sb, size_t offset)
347 if (offset >= sb->len)
348 BUG("looking for next line beyond buffer (%d >= %d)\n%s",
349 (int)offset, (int)sb->len, sb->buf);
351 eol = memchr(sb->buf + offset, '\n', sb->len - offset);
354 return eol - sb->buf + 1;
357 static void render_hunk(struct add_p_state *s, struct hunk *hunk,
358 ssize_t delta, int colored, struct strbuf *out)
360 struct hunk_header *header = &hunk->header;
362 if (hunk->header.old_offset != 0 || hunk->header.new_offset != 0) {
364 * Generate the hunk header dynamically, except for special
365 * hunks (such as the diff header).
369 unsigned long old_offset = header->old_offset;
370 unsigned long new_offset = header->new_offset;
373 p = s->plain.buf + header->extra_start;
374 len = header->extra_end - header->extra_start;
376 strbuf_addstr(out, s->s.fraginfo_color);
377 p = s->colored.buf + header->colored_extra_start;
378 len = header->colored_extra_end
379 - header->colored_extra_start;
384 strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@",
385 old_offset, header->old_count,
386 new_offset, header->new_count);
388 strbuf_add(out, p, len);
390 strbuf_addf(out, "%s\n", GIT_COLOR_RESET);
392 strbuf_addch(out, '\n');
396 strbuf_add(out, s->colored.buf + hunk->colored_start,
397 hunk->colored_end - hunk->colored_start);
399 strbuf_add(out, s->plain.buf + hunk->start,
400 hunk->end - hunk->start);
403 static void render_diff_header(struct add_p_state *s,
404 struct file_diff *file_diff, int colored,
408 * If there was a mode change, the first hunk is a pseudo hunk that
409 * corresponds to the mode line in the header. If the user did not want
410 * to stage that "hunk", we actually have to cut it out from the header.
412 int skip_mode_change =
413 file_diff->mode_change && file_diff->hunk->use != USE_HUNK;
414 struct hunk *head = &file_diff->head, *first = file_diff->hunk;
416 if (!skip_mode_change) {
417 render_hunk(s, head, 0, colored, out);
422 const char *p = s->colored.buf;
424 strbuf_add(out, p + head->colored_start,
425 first->colored_start - head->colored_start);
426 strbuf_add(out, p + first->colored_end,
427 head->colored_end - first->colored_end);
429 const char *p = s->plain.buf;
431 strbuf_add(out, p + head->start, first->start - head->start);
432 strbuf_add(out, p + first->end, head->end - first->end);
436 /* Coalesce hunks again that were split */
437 static int merge_hunks(struct add_p_state *s, struct file_diff *file_diff,
438 size_t *hunk_index, struct hunk *merged)
440 size_t i = *hunk_index;
441 struct hunk *hunk = file_diff->hunk + i;
442 /* `header` corresponds to the merged hunk */
443 struct hunk_header *header = &merged->header, *next;
445 if (hunk->use != USE_HUNK)
449 /* We simply skip the colored part (if any) when merging hunks */
450 merged->colored_start = merged->colored_end = 0;
452 for (; i + 1 < file_diff->hunk_nr; i++) {
454 next = &hunk->header;
457 * Stop merging hunks when:
459 * - the hunk is not selected for use, or
460 * - the hunk does not overlap with the already-merged hunk(s)
462 if (hunk->use != USE_HUNK ||
463 header->new_offset >= next->new_offset ||
464 header->new_offset + header->new_count < next->new_offset ||
465 merged->start >= hunk->start ||
466 merged->end < hunk->start)
469 merged->end = hunk->end;
470 merged->colored_end = hunk->colored_end;
472 header->old_count = next->old_offset + next->old_count
473 - header->old_offset;
474 header->new_count = next->new_offset + next->new_count
475 - header->new_offset;
478 if (i == *hunk_index)
485 static void reassemble_patch(struct add_p_state *s,
486 struct file_diff *file_diff, struct strbuf *out)
492 render_diff_header(s, file_diff, 0, out);
494 for (i = file_diff->mode_change; i < file_diff->hunk_nr; i++) {
495 struct hunk merged = { 0 };
497 hunk = file_diff->hunk + i;
498 if (hunk->use != USE_HUNK)
499 delta += hunk->header.old_count
500 - hunk->header.new_count;
502 /* merge overlapping hunks into a temporary hunk */
503 if (merge_hunks(s, file_diff, &i, &merged))
506 render_hunk(s, hunk, delta, 0, out);
511 static int split_hunk(struct add_p_state *s, struct file_diff *file_diff,
514 int colored = !!s->colored.len, first = 1;
515 struct hunk *hunk = file_diff->hunk + hunk_index;
516 size_t splittable_into;
517 size_t end, colored_end, current, colored_current = 0, context_line_count;
518 struct hunk_header remaining, *header;
521 if (hunk_index >= file_diff->hunk_nr)
522 BUG("invalid hunk index: %d (must be >= 0 and < %d)",
523 (int)hunk_index, (int)file_diff->hunk_nr);
525 if (hunk->splittable_into < 2)
527 splittable_into = hunk->splittable_into;
530 colored_end = hunk->colored_end;
532 remaining = hunk->header;
534 file_diff->hunk_nr += splittable_into - 1;
535 ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, file_diff->hunk_alloc);
536 if (hunk_index + splittable_into < file_diff->hunk_nr)
537 memmove(file_diff->hunk + hunk_index + splittable_into,
538 file_diff->hunk + hunk_index + 1,
539 (file_diff->hunk_nr - hunk_index - splittable_into)
541 hunk = file_diff->hunk + hunk_index;
542 hunk->splittable_into = 1;
543 memset(hunk + 1, 0, (splittable_into - 1) * sizeof(*hunk));
545 header = &hunk->header;
546 header->old_count = header->new_count = 0;
548 current = hunk->start;
550 colored_current = hunk->colored_start;
552 context_line_count = 0;
554 while (splittable_into > 1) {
555 ch = s->plain.buf[current];
558 BUG("buffer overrun while splitting hunks");
561 * Is this the first context line after a chain of +/- lines?
562 * Then record the start of the next split hunk.
564 if ((marker == '-' || marker == '+') && ch == ' ') {
566 hunk[1].start = current;
568 hunk[1].colored_start = colored_current;
569 context_line_count = 0;
573 * Was the previous line a +/- one? Alternatively, is this the
574 * first line (and not a +/- one)?
576 * Then just increment the appropriate counter and continue
577 * with the next line.
579 if (marker != ' ' || (ch != '-' && ch != '+')) {
581 /* Comment lines are attached to the previous line */
583 ch = marker ? marker : ' ';
585 /* current hunk not done yet */
587 context_line_count++;
593 BUG("unhandled diff marker: '%c'", ch);
595 current = find_next_line(&s->plain, current);
598 find_next_line(&s->colored,
604 * We got us the start of a new hunk!
606 * This is a context line, so it is shared with the previous
611 if (header->old_count || header->new_count)
612 BUG("counts are off: %d/%d",
613 (int)header->old_count,
614 (int)header->new_count);
616 header->old_count = context_line_count;
617 header->new_count = context_line_count;
618 context_line_count = 0;
623 remaining.old_offset += header->old_count;
624 remaining.old_count -= header->old_count;
625 remaining.new_offset += header->new_count;
626 remaining.new_count -= header->new_count;
628 /* initialize next hunk header's offsets */
629 hunk[1].header.old_offset =
630 header->old_offset + header->old_count;
631 hunk[1].header.new_offset =
632 header->new_offset + header->new_count;
634 /* add one split hunk */
635 header->old_count += context_line_count;
636 header->new_count += context_line_count;
640 hunk->colored_end = colored_current;
643 hunk->splittable_into = 1;
644 hunk->use = hunk[-1].use;
645 header = &hunk->header;
647 header->old_count = header->new_count = context_line_count;
648 context_line_count = 0;
654 /* last hunk simply gets the rest */
655 if (header->old_offset != remaining.old_offset)
656 BUG("miscounted old_offset: %lu != %lu",
657 header->old_offset, remaining.old_offset);
658 if (header->new_offset != remaining.new_offset)
659 BUG("miscounted new_offset: %lu != %lu",
660 header->new_offset, remaining.new_offset);
661 header->old_count = remaining.old_count;
662 header->new_count = remaining.new_count;
665 hunk->colored_end = colored_end;
670 static const char help_patch_text[] =
671 N_("y - stage this hunk\n"
672 "n - do not stage this hunk\n"
673 "a - stage this and all the remaining hunks\n"
674 "d - do not stage this hunk nor any of the remaining hunks\n"
675 "j - leave this hunk undecided, see next undecided hunk\n"
676 "J - leave this hunk undecided, see next hunk\n"
677 "k - leave this hunk undecided, see previous undecided hunk\n"
678 "K - leave this hunk undecided, see previous hunk\n"
679 "s - split the current hunk into smaller hunks\n"
682 static int patch_update_file(struct add_p_state *s,
683 struct file_diff *file_diff)
685 size_t hunk_index = 0;
686 ssize_t i, undecided_previous, undecided_next;
689 struct child_process cp = CHILD_PROCESS_INIT;
690 int colored = !!s->colored.len;
691 enum prompt_mode_type prompt_mode_type;
693 if (!file_diff->hunk_nr)
696 strbuf_reset(&s->buf);
697 render_diff_header(s, file_diff, colored, &s->buf);
698 fputs(s->buf.buf, stdout);
700 if (hunk_index >= file_diff->hunk_nr)
702 hunk = file_diff->hunk + hunk_index;
704 undecided_previous = -1;
705 for (i = hunk_index - 1; i >= 0; i--)
706 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
707 undecided_previous = i;
712 for (i = hunk_index + 1; i < file_diff->hunk_nr; i++)
713 if (file_diff->hunk[i].use == UNDECIDED_HUNK) {
718 /* Everything decided? */
719 if (undecided_previous < 0 && undecided_next < 0 &&
720 hunk->use != UNDECIDED_HUNK)
723 strbuf_reset(&s->buf);
724 render_hunk(s, hunk, 0, colored, &s->buf);
725 fputs(s->buf.buf, stdout);
727 strbuf_reset(&s->buf);
728 if (undecided_previous >= 0)
729 strbuf_addstr(&s->buf, ",k");
731 strbuf_addstr(&s->buf, ",K");
732 if (undecided_next >= 0)
733 strbuf_addstr(&s->buf, ",j");
734 if (hunk_index + 1 < file_diff->hunk_nr)
735 strbuf_addstr(&s->buf, ",J");
736 if (hunk->splittable_into > 1)
737 strbuf_addstr(&s->buf, ",s");
739 if (file_diff->deleted)
740 prompt_mode_type = PROMPT_DELETION;
741 else if (file_diff->mode_change && !hunk_index)
742 prompt_mode_type = PROMPT_MODE_CHANGE;
744 prompt_mode_type = PROMPT_HUNK;
746 color_fprintf(stdout, s->s.prompt_color,
747 "(%"PRIuMAX"/%"PRIuMAX") ",
748 (uintmax_t)hunk_index + 1,
749 (uintmax_t)file_diff->hunk_nr);
750 color_fprintf(stdout, s->s.prompt_color,
751 _(prompt_mode[prompt_mode_type]), s->buf.buf);
753 if (strbuf_getline(&s->answer, stdin) == EOF)
755 strbuf_trim_trailing_newline(&s->answer);
759 ch = tolower(s->answer.buf[0]);
761 hunk->use = USE_HUNK;
763 hunk_index = undecided_next < 0 ?
764 file_diff->hunk_nr : undecided_next;
765 } else if (ch == 'n') {
766 hunk->use = SKIP_HUNK;
768 } else if (ch == 'a') {
769 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
770 hunk = file_diff->hunk + hunk_index;
771 if (hunk->use == UNDECIDED_HUNK)
772 hunk->use = USE_HUNK;
774 } else if (ch == 'd') {
775 for (; hunk_index < file_diff->hunk_nr; hunk_index++) {
776 hunk = file_diff->hunk + hunk_index;
777 if (hunk->use == UNDECIDED_HUNK)
778 hunk->use = SKIP_HUNK;
780 } else if (s->answer.buf[0] == 'K') {
784 err(s, _("No previous hunk"));
785 } else if (s->answer.buf[0] == 'J') {
786 if (hunk_index + 1 < file_diff->hunk_nr)
789 err(s, _("No next hunk"));
790 } else if (s->answer.buf[0] == 'k') {
791 if (undecided_previous >= 0)
792 hunk_index = undecided_previous;
794 err(s, _("No previous hunk"));
795 } else if (s->answer.buf[0] == 'j') {
796 if (undecided_next >= 0)
797 hunk_index = undecided_next;
799 err(s, _("No next hunk"));
800 } else if (s->answer.buf[0] == 's') {
801 size_t splittable_into = hunk->splittable_into;
802 if (splittable_into < 2)
803 err(s, _("Sorry, cannot split this hunk"));
804 else if (!split_hunk(s, file_diff,
805 hunk - file_diff->hunk))
806 color_fprintf_ln(stdout, s->s.header_color,
807 _("Split into %d hunks."),
808 (int)splittable_into);
810 color_fprintf(stdout, s->s.help_color,
814 /* Any hunk to be used? */
815 for (i = 0; i < file_diff->hunk_nr; i++)
816 if (file_diff->hunk[i].use == USE_HUNK)
819 if (i < file_diff->hunk_nr) {
820 /* At least one hunk selected: apply */
821 strbuf_reset(&s->buf);
822 reassemble_patch(s, file_diff, &s->buf);
824 discard_index(s->s.r->index);
825 setup_child_process(s, &cp, "apply", "--cached", NULL);
826 if (pipe_command(&cp, s->buf.buf, s->buf.len,
828 error(_("'git apply --cached' failed"));
829 if (!repo_read_index(s->s.r))
830 repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0,
831 1, NULL, NULL, NULL);
838 int run_add_p(struct repository *r, const struct pathspec *ps)
840 struct add_p_state s = {
841 { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
845 init_add_i_state(&s.s, r);
847 if (discard_index(r->index) < 0 || repo_read_index(r) < 0 ||
848 repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1,
849 NULL, NULL, NULL) < 0 ||
850 parse_diff(&s, ps) < 0) {
851 strbuf_release(&s.plain);
852 strbuf_release(&s.colored);
856 for (i = 0; i < s.file_diff_nr; i++)
857 if (patch_update_file(&s, s.file_diff + i))
860 strbuf_release(&s.answer);
861 strbuf_release(&s.buf);
862 strbuf_release(&s.plain);
863 strbuf_release(&s.colored);