pretty: expand tabs in indented logs to make things line up properly

A commit log message sometimes tries to line things up using tabs, assuming fixed-width font with the standard 8-place tab settings. Viewing such a commit however does not work well in "git log", as we indent the lines by prefixing 4 spaces in front of them. This should all line up: Column 1 Column 2 -------- -------- A B ABCD EFGH SPACES Instead of Tabs Even with multi-byte UTF8 characters: Column 1 Column 2 -------- -------- Ä B åäö 100 A Møøse once bit my sister.. Tab-expand the lines in "git log --expand-tabs" output before prefixing 4 spaces. This is based on the patch by Linus Torvalds, but at this step, we require an explicit command line option to enable the behaviour. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Linus Torvalds committed Mar 16, 2016 at 09:15 UTC 7cc13c717b52d3539e76f087d747f96d0d24a914
6 files changed +79 -2
Documentation/pretty-options.txt
+5
@@ -42,6 +42,11 @@ people using 80-column terminals.
42 verbatim; this means that invalid sequences in the original
43 commit may be copied to the output.
44
45 +--expand-tabs::
46 + Perform a tab expansion (replace each tab with enough spaces
47 + to fill to the next display column that is multiple of 8)
48 + in the log message before showing it in the output.
49 +
50 ifndef::git-rev-list[]
51 --notes[=<ref>]::
52 Show the notes (see linkgit:git-notes[1]) that annotate the
commit.h
+1
@@ -147,6 +147,7 @@ struct pretty_print_context {
147 int preserve_subject;
148 struct date_mode date_mode;
149 unsigned date_mode_explicit:1;
150 + unsigned expand_tabs_in_log:1;
151 int need_8bit_cte;
152 char *notes_message;
153 struct reflog_walk_info *reflog_info;
log-tree.c
+1
@@ -683,6 +683,7 @@ void show_log(struct rev_info *opt)
683 ctx.fmt = opt->commit_format;
684 ctx.mailmap = opt->mailmap;
685 ctx.color = opt->diffopt.use_color;
686 + ctx.expand_tabs_in_log = opt->expand_tabs_in_log;
687 ctx.output_encoding = get_log_output_encoding();
688 if (opt->from_ident.mail_begin && opt->from_ident.name_begin)
689 ctx.from_ident = &opt->from_ident;
pretty.c
+69 -2
@@ -1629,6 +1629,72 @@ void pp_title_line(struct pretty_print_context *pp,
1629 strbuf_release(&title);
1630 }
1631
1632 +static int pp_utf8_width(const char *start, const char *end)
1633 +{
1634 + int width = 0;
1635 + size_t remain = end - start;
1636 +
1637 + while (remain) {
1638 + int n = utf8_width(&start, &remain);
1639 + if (n < 0 || !start)
1640 + return -1;
1641 + width += n;
1642 + }
1643 + return width;
1644 +}
1645 +
1646 +static void strbuf_add_tabexpand(struct strbuf *sb,
1647 + const char *line, int linelen)
1648 +{
1649 + const char *tab;
1650 +
1651 + while ((tab = memchr(line, '\t', linelen)) != NULL) {
1652 + int width = pp_utf8_width(line, tab);
1653 +
1654 + /*
1655 + * If it wasn't well-formed utf8, or it
1656 + * had characters with badly defined
1657 + * width (control characters etc), just
1658 + * give up on trying to align things.
1659 + */
1660 + if (width < 0)
1661 + break;
1662 +
1663 + /* Output the data .. */
1664 + strbuf_add(sb, line, tab - line);
1665 +
1666 + /* .. and the de-tabified tab */
1667 + strbuf_addchars(sb, ' ', 8 - (width % 8));
1668 +
1669 + /* Skip over the printed part .. */
1670 + linelen -= tab + 1 - line;
1671 + line = tab + 1;
1672 + }
1673 +
1674 + /*
1675 + * Print out everything after the last tab without
1676 + * worrying about width - there's nothing more to
1677 + * align.
1678 + */
1679 + strbuf_add(sb, line, linelen);
1680 +}
1681 +
1682 +/*
1683 + * pp_handle_indent() prints out the intendation, and
1684 + * the whole line (without the final newline), after
1685 + * de-tabifying.
1686 + */
1687 +static void pp_handle_indent(struct pretty_print_context *pp,
1688 + struct strbuf *sb, int indent,
1689 + const char *line, int linelen)
1690 +{
1691 + strbuf_addchars(sb, ' ', indent);
1692 + if (pp->expand_tabs_in_log)
1693 + strbuf_add_tabexpand(sb, line, linelen);
1694 + else
1695 + strbuf_add(sb, line, linelen);
1696 +}
1697 +
1698 void pp_remainder(struct pretty_print_context *pp,
1699 const char **msg_p,
1700 struct strbuf *sb,
@@ -1653,8 +1719,9 @@ void pp_remainder(struct pretty_print_context *pp,
1719
1720 strbuf_grow(sb, linelen + indent + 20);
1721 if (indent)
1656 - strbuf_addchars(sb, ' ', indent);
1657 - strbuf_add(sb, line, linelen);
1722 + pp_handle_indent(pp, sb, indent, line, linelen);
1723 + else
1724 + strbuf_add(sb, line, linelen);
1725 strbuf_addch(sb, '\n');
1726 }
1727 }
revision.c
+2
@@ -1915,6 +1915,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
1915 revs->verbose_header = 1;
1916 revs->pretty_given = 1;
1917 get_commit_format(arg+9, revs);
1918 + } else if (!strcmp(arg, "--expand-tabs")) {
1919 + revs->expand_tabs_in_log = 1;
1920 } else if (!strcmp(arg, "--show-notes") || !strcmp(arg, "--notes")) {
1921 revs->show_notes = 1;
1922 revs->show_notes_given = 1;
revision.h
+1
@@ -133,6 +133,7 @@ struct rev_info {
133 show_notes_given:1,
134 show_signature:1,
135 pretty_given:1,
136 + expand_tabs_in_log:1,
137 abbrev_commit:1,
138 abbrev_commit_given:1,
139 zero_commit:1,