Skip to content

Commit 8ab3e18

Browse files
author
Junio C Hamano
committed
Merge branch 'js/commit-format'
* js/commit-format: show_date(): rename the "relative" parameter to "mode" Actually make print_wrapped_text() useful pretty-formats: add 'format:<string>'
2 parents 8b969a5 + f8493ec commit 8ab3e18

File tree

8 files changed

+268
-16
lines changed

8 files changed

+268
-16
lines changed

Documentation/pretty-formats.txt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,53 @@ displayed in full, regardless of whether --abbrev or
7777
true parent commits, without taking grafts nor history
7878
simplification into account.
7979

80+
* 'format:'
81+
+
82+
The 'format:' format allows you to specify which information
83+
you want to show. It works a little bit like printf format,
84+
with the notable exception that you get a newline with '%n'
85+
instead of '\n'.
86+
87+
E.g, 'format:"The author of %h was %an, %ar%nThe title was >>%s<<"'
88+
would show something like this:
89+
90+
The author of fe6e0ee was Junio C Hamano, 23 hours ago
91+
The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
92+
93+
The placeholders are:
94+
95+
- '%H': commit hash
96+
- '%h': abbreviated commit hash
97+
- '%T': tree hash
98+
- '%t': abbreviated tree hash
99+
- '%P': parent hashes
100+
- '%p': abbreviated parent hashes
101+
- '%an': author name
102+
- '%ae': author email
103+
- '%ad': author date
104+
- '%aD': author date, RFC2822 style
105+
- '%ar': author date, relative
106+
- '%at': author date, UNIX timestamp
107+
- '%cn': committer name
108+
- '%ce': committer email
109+
- '%cd': committer date
110+
- '%cD': committer date, RFC2822 style
111+
- '%cr': committer date, relative
112+
- '%ct': committer date, UNIX timestamp
113+
- '%e': encoding
114+
- '%s': subject
115+
- '%b': body
116+
- '%Cred': switch color to red
117+
- '%Cgreen': switch color to green
118+
- '%Cblue': switch color to blue
119+
- '%Creset': reset color
120+
- '%n': newline
121+
122+
80123
--encoding[=<encoding>]::
81124
The commit objects record the encoding used for the log message
82125
in their encoding header; this option can be used to tell the
83126
command to re-code the commit log message in the encoding
84127
preferred by the user. For non plumbing commands this
85128
defaults to UTF-8.
129+

cache.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ extern void *read_object_with_reference(const unsigned char *sha1,
327327
unsigned long *size,
328328
unsigned char *sha1_ret);
329329

330-
const char *show_date(unsigned long time, int timezone, int relative);
330+
enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT };
331+
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
331332
const char *show_rfc2822_date(unsigned long time, int timezone);
332333
int parse_date(const char *date, char *buf, int bufsize);
333334
void datestamp(char *buf, int bufsize);

commit.c

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "commit.h"
44
#include "pkt-line.h"
55
#include "utf8.h"
6+
#include "interpolate.h"
67

78
int save_commit_buffer = 1;
89

@@ -36,8 +37,11 @@ struct cmt_fmt_map {
3637
{ "full", 5, CMIT_FMT_FULL },
3738
{ "fuller", 5, CMIT_FMT_FULLER },
3839
{ "oneline", 1, CMIT_FMT_ONELINE },
40+
{ "format:", 7, CMIT_FMT_USERFORMAT},
3941
};
4042

43+
static char *user_format;
44+
4145
enum cmit_fmt get_commit_format(const char *arg)
4246
{
4347
int i;
@@ -46,6 +50,12 @@ enum cmit_fmt get_commit_format(const char *arg)
4650
return CMIT_FMT_DEFAULT;
4751
if (*arg == '=')
4852
arg++;
53+
if (!prefixcmp(arg, "format:")) {
54+
if (user_format)
55+
free(user_format);
56+
user_format = xstrdup(arg + 7);
57+
return CMIT_FMT_USERFORMAT;
58+
}
4959
for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
5060
if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
5161
!strncmp(arg, cmt_fmts[i].n, strlen(arg)))
@@ -710,6 +720,188 @@ static char *logmsg_reencode(const struct commit *commit,
710720
return out;
711721
}
712722

723+
static char *xstrndup(const char *text, int len)
724+
{
725+
char *result = xmalloc(len + 1);
726+
memcpy(result, text, len);
727+
result[len] = '\0';
728+
return result;
729+
}
730+
731+
static void fill_person(struct interp *table, const char *msg, int len)
732+
{
733+
int start, end, tz = 0;
734+
unsigned long date;
735+
char *ep;
736+
737+
/* parse name */
738+
for (end = 0; end < len && msg[end] != '<'; end++)
739+
; /* do nothing */
740+
start = end + 1;
741+
while (end > 0 && isspace(msg[end - 1]))
742+
end--;
743+
table[0].value = xstrndup(msg, end);
744+
745+
if (start >= len)
746+
return;
747+
748+
/* parse email */
749+
for (end = start + 1; end < len && msg[end] != '>'; end++)
750+
; /* do nothing */
751+
752+
if (end >= len)
753+
return;
754+
755+
table[1].value = xstrndup(msg + start, end - start);
756+
757+
/* parse date */
758+
for (start = end + 1; start < len && isspace(msg[start]); start++)
759+
; /* do nothing */
760+
if (start >= len)
761+
return;
762+
date = strtoul(msg + start, &ep, 10);
763+
if (msg + start == ep)
764+
return;
765+
766+
table[5].value = xstrndup(msg + start, ep - msg + start);
767+
768+
/* parse tz */
769+
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
770+
; /* do nothing */
771+
if (start + 1 < len) {
772+
tz = strtoul(msg + start + 1, NULL, 10);
773+
if (msg[start] == '-')
774+
tz = -tz;
775+
}
776+
777+
interp_set_entry(table, 2, show_date(date, tz, 0));
778+
interp_set_entry(table, 3, show_rfc2822_date(date, tz));
779+
interp_set_entry(table, 4, show_date(date, tz, 1));
780+
}
781+
782+
static long format_commit_message(const struct commit *commit,
783+
const char *msg, char *buf, unsigned long space)
784+
{
785+
struct interp table[] = {
786+
{ "%H" }, /* commit hash */
787+
{ "%h" }, /* abbreviated commit hash */
788+
{ "%T" }, /* tree hash */
789+
{ "%t" }, /* abbreviated tree hash */
790+
{ "%P" }, /* parent hashes */
791+
{ "%p" }, /* abbreviated parent hashes */
792+
{ "%an" }, /* author name */
793+
{ "%ae" }, /* author email */
794+
{ "%ad" }, /* author date */
795+
{ "%aD" }, /* author date, RFC2822 style */
796+
{ "%ar" }, /* author date, relative */
797+
{ "%at" }, /* author date, UNIX timestamp */
798+
{ "%cn" }, /* committer name */
799+
{ "%ce" }, /* committer email */
800+
{ "%cd" }, /* committer date */
801+
{ "%cD" }, /* committer date, RFC2822 style */
802+
{ "%cr" }, /* committer date, relative */
803+
{ "%ct" }, /* committer date, UNIX timestamp */
804+
{ "%e" }, /* encoding */
805+
{ "%s" }, /* subject */
806+
{ "%b" }, /* body */
807+
{ "%Cred" }, /* red */
808+
{ "%Cgreen" }, /* green */
809+
{ "%Cblue" }, /* blue */
810+
{ "%Creset" }, /* reset color */
811+
{ "%n" } /* newline */
812+
};
813+
enum interp_index {
814+
IHASH = 0, IHASH_ABBREV,
815+
ITREE, ITREE_ABBREV,
816+
IPARENTS, IPARENTS_ABBREV,
817+
IAUTHOR_NAME, IAUTHOR_EMAIL,
818+
IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
819+
IAUTHOR_TIMESTAMP,
820+
ICOMMITTER_NAME, ICOMMITTER_EMAIL,
821+
ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
822+
ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
823+
IENCODING,
824+
ISUBJECT,
825+
IBODY,
826+
IRED, IGREEN, IBLUE, IRESET_COLOR,
827+
INEWLINE
828+
};
829+
struct commit_list *p;
830+
char parents[1024];
831+
int i;
832+
enum { HEADER, SUBJECT, BODY } state;
833+
834+
if (INEWLINE + 1 != ARRAY_SIZE(table))
835+
die("invalid interp table!");
836+
837+
/* these are independent of the commit */
838+
interp_set_entry(table, IRED, "\033[31m");
839+
interp_set_entry(table, IGREEN, "\033[32m");
840+
interp_set_entry(table, IBLUE, "\033[34m");
841+
interp_set_entry(table, IRESET_COLOR, "\033[m");
842+
interp_set_entry(table, INEWLINE, "\n");
843+
844+
/* these depend on the commit */
845+
if (!commit->object.parsed)
846+
parse_object(commit->object.sha1);
847+
interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
848+
interp_set_entry(table, IHASH_ABBREV,
849+
find_unique_abbrev(commit->object.sha1,
850+
DEFAULT_ABBREV));
851+
interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
852+
interp_set_entry(table, ITREE_ABBREV,
853+
find_unique_abbrev(commit->tree->object.sha1,
854+
DEFAULT_ABBREV));
855+
for (i = 0, p = commit->parents;
856+
p && i < sizeof(parents) - 1;
857+
p = p->next)
858+
i += snprintf(parents + i, sizeof(parents) - i - 1, "%s ",
859+
sha1_to_hex(p->item->object.sha1));
860+
interp_set_entry(table, IPARENTS, parents);
861+
for (i = 0, p = commit->parents;
862+
p && i < sizeof(parents) - 1;
863+
p = p->next)
864+
i += snprintf(parents + i, sizeof(parents) - i - 1, "%s ",
865+
find_unique_abbrev(p->item->object.sha1,
866+
DEFAULT_ABBREV));
867+
interp_set_entry(table, IPARENTS_ABBREV, parents);
868+
869+
for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
870+
int eol;
871+
for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
872+
; /* do nothing */
873+
874+
if (state == SUBJECT) {
875+
table[ISUBJECT].value = xstrndup(msg + i, eol - i);
876+
i = eol;
877+
}
878+
if (i == eol) {
879+
state++;
880+
/* strip empty lines */
881+
while (msg[eol + 1] == '\n')
882+
eol++;
883+
} else if (!prefixcmp(msg + i, "author "))
884+
fill_person(table + IAUTHOR_NAME,
885+
msg + i + 7, eol - i - 7);
886+
else if (!prefixcmp(msg + i, "committer "))
887+
fill_person(table + ICOMMITTER_NAME,
888+
msg + i + 10, eol - i - 10);
889+
else if (!prefixcmp(msg + i, "encoding "))
890+
table[IENCODING].value = xstrndup(msg + i, eol - i);
891+
i = eol;
892+
}
893+
if (msg[i])
894+
table[IBODY].value = xstrdup(msg + i);
895+
for (i = 0; i < ARRAY_SIZE(table); i++)
896+
if (!table[i].value)
897+
interp_set_entry(table, i, "<unknown>");
898+
899+
interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
900+
interp_clear_table(table, ARRAY_SIZE(table));
901+
902+
return strlen(buf);
903+
}
904+
713905
unsigned long pretty_print_commit(enum cmit_fmt fmt,
714906
const struct commit *commit,
715907
unsigned long len,
@@ -727,6 +919,9 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
727919
char *reencoded;
728920
char *encoding;
729921

922+
if (fmt == CMIT_FMT_USERFORMAT)
923+
return format_commit_message(commit, msg, buf, space);
924+
730925
encoding = (git_log_output_encoding
731926
? git_log_output_encoding
732927
: git_commit_encoding);

commit.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ enum cmit_fmt {
4747
CMIT_FMT_FULLER,
4848
CMIT_FMT_ONELINE,
4949
CMIT_FMT_EMAIL,
50+
CMIT_FMT_USERFORMAT,
5051

5152
CMIT_FMT_UNSPECIFIED,
5253
};

date.c

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ static struct tm *time_to_tm(unsigned long time, int tz)
5555
return gmtime(&t);
5656
}
5757

58-
const char *show_date(unsigned long time, int tz, int relative)
58+
const char *show_date(unsigned long time, int tz, enum date_mode mode)
5959
{
6060
struct tm *tm;
6161
static char timebuf[200];
6262

63-
if (relative) {
63+
if (mode == DATE_RELATIVE) {
6464
unsigned long diff;
6565
struct timeval now;
6666
gettimeofday(&now, NULL);
@@ -105,12 +105,16 @@ const char *show_date(unsigned long time, int tz, int relative)
105105
tm = time_to_tm(time, tz);
106106
if (!tm)
107107
return NULL;
108-
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
109-
weekday_names[tm->tm_wday],
110-
month_names[tm->tm_mon],
111-
tm->tm_mday,
112-
tm->tm_hour, tm->tm_min, tm->tm_sec,
113-
tm->tm_year + 1900, tz);
108+
if (mode == DATE_SHORT)
109+
sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
110+
tm->tm_mon + 1, tm->tm_mday);
111+
else
112+
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
113+
weekday_names[tm->tm_wday],
114+
month_names[tm->tm_mon],
115+
tm->tm_mday,
116+
tm->tm_hour, tm->tm_min, tm->tm_sec,
117+
tm->tm_year + 1900, tz);
114118
return timebuf;
115119
}
116120

log-tree.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ void show_log(struct rev_info *opt, const char *sep)
211211
sha1, sha1);
212212
opt->diffopt.stat_sep = buffer;
213213
}
214-
} else {
214+
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
215215
fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
216216
stdout);
217217
if (opt->commit_format != CMIT_FMT_ONELINE)

utf8.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,19 @@ static void print_spaces(int count)
235235
/*
236236
* Wrap the text, if necessary. The variable indent is the indent for the
237237
* first line, indent2 is the indent for all other lines.
238+
* If indent is negative, assume that already -indent columns have been
239+
* consumed (and no extra indent is necessary for the first line).
238240
*/
239-
void print_wrapped_text(const char *text, int indent, int indent2, int width)
241+
int print_wrapped_text(const char *text, int indent, int indent2, int width)
240242
{
241243
int w = indent, assume_utf8 = is_utf8(text);
242244
const char *bol = text, *space = NULL;
243245

246+
if (indent < 0) {
247+
w = -indent;
248+
space = text;
249+
}
250+
244251
for (;;) {
245252
char c = *text;
246253
if (!c || isspace(c)) {
@@ -251,10 +258,9 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
251258
else
252259
print_spaces(indent);
253260
fwrite(start, text - start, 1, stdout);
254-
if (!c) {
255-
putchar('\n');
256-
return;
257-
} else if (c == '\t')
261+
if (!c)
262+
return w;
263+
else if (c == '\t')
258264
w |= 0x07;
259265
space = text;
260266
w++;
@@ -275,6 +281,7 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
275281
text++;
276282
}
277283
}
284+
return w;
278285
}
279286

280287
int is_encoding_utf8(const char *name)

0 commit comments

Comments
 (0)