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>'
This commit is contained in:
commit
8ab3e18586
@ -77,9 +77,53 @@ displayed in full, regardless of whether --abbrev or
|
||||
true parent commits, without taking grafts nor history
|
||||
simplification into account.
|
||||
|
||||
* 'format:'
|
||||
+
|
||||
The 'format:' format allows you to specify which information
|
||||
you want to show. It works a little bit like printf format,
|
||||
with the notable exception that you get a newline with '%n'
|
||||
instead of '\n'.
|
||||
|
||||
E.g, 'format:"The author of %h was %an, %ar%nThe title was >>%s<<"'
|
||||
would show something like this:
|
||||
|
||||
The author of fe6e0ee was Junio C Hamano, 23 hours ago
|
||||
The title was >>t4119: test autocomputing -p<n> for traditional diff input.<<
|
||||
|
||||
The placeholders are:
|
||||
|
||||
- '%H': commit hash
|
||||
- '%h': abbreviated commit hash
|
||||
- '%T': tree hash
|
||||
- '%t': abbreviated tree hash
|
||||
- '%P': parent hashes
|
||||
- '%p': abbreviated parent hashes
|
||||
- '%an': author name
|
||||
- '%ae': author email
|
||||
- '%ad': author date
|
||||
- '%aD': author date, RFC2822 style
|
||||
- '%ar': author date, relative
|
||||
- '%at': author date, UNIX timestamp
|
||||
- '%cn': committer name
|
||||
- '%ce': committer email
|
||||
- '%cd': committer date
|
||||
- '%cD': committer date, RFC2822 style
|
||||
- '%cr': committer date, relative
|
||||
- '%ct': committer date, UNIX timestamp
|
||||
- '%e': encoding
|
||||
- '%s': subject
|
||||
- '%b': body
|
||||
- '%Cred': switch color to red
|
||||
- '%Cgreen': switch color to green
|
||||
- '%Cblue': switch color to blue
|
||||
- '%Creset': reset color
|
||||
- '%n': newline
|
||||
|
||||
|
||||
--encoding[=<encoding>]::
|
||||
The commit objects record the encoding used for the log message
|
||||
in their encoding header; this option can be used to tell the
|
||||
command to re-code the commit log message in the encoding
|
||||
preferred by the user. For non plumbing commands this
|
||||
defaults to UTF-8.
|
||||
|
||||
|
3
cache.h
3
cache.h
@ -327,7 +327,8 @@ extern void *read_object_with_reference(const unsigned char *sha1,
|
||||
unsigned long *size,
|
||||
unsigned char *sha1_ret);
|
||||
|
||||
const char *show_date(unsigned long time, int timezone, int relative);
|
||||
enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT };
|
||||
const char *show_date(unsigned long time, int timezone, enum date_mode mode);
|
||||
const char *show_rfc2822_date(unsigned long time, int timezone);
|
||||
int parse_date(const char *date, char *buf, int bufsize);
|
||||
void datestamp(char *buf, int bufsize);
|
||||
|
195
commit.c
195
commit.c
@ -3,6 +3,7 @@
|
||||
#include "commit.h"
|
||||
#include "pkt-line.h"
|
||||
#include "utf8.h"
|
||||
#include "interpolate.h"
|
||||
|
||||
int save_commit_buffer = 1;
|
||||
|
||||
@ -36,8 +37,11 @@ struct cmt_fmt_map {
|
||||
{ "full", 5, CMIT_FMT_FULL },
|
||||
{ "fuller", 5, CMIT_FMT_FULLER },
|
||||
{ "oneline", 1, CMIT_FMT_ONELINE },
|
||||
{ "format:", 7, CMIT_FMT_USERFORMAT},
|
||||
};
|
||||
|
||||
static char *user_format;
|
||||
|
||||
enum cmit_fmt get_commit_format(const char *arg)
|
||||
{
|
||||
int i;
|
||||
@ -46,6 +50,12 @@ enum cmit_fmt get_commit_format(const char *arg)
|
||||
return CMIT_FMT_DEFAULT;
|
||||
if (*arg == '=')
|
||||
arg++;
|
||||
if (!prefixcmp(arg, "format:")) {
|
||||
if (user_format)
|
||||
free(user_format);
|
||||
user_format = xstrdup(arg + 7);
|
||||
return CMIT_FMT_USERFORMAT;
|
||||
}
|
||||
for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
|
||||
if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
|
||||
!strncmp(arg, cmt_fmts[i].n, strlen(arg)))
|
||||
@ -710,6 +720,188 @@ static char *logmsg_reencode(const struct commit *commit,
|
||||
return out;
|
||||
}
|
||||
|
||||
static char *xstrndup(const char *text, int len)
|
||||
{
|
||||
char *result = xmalloc(len + 1);
|
||||
memcpy(result, text, len);
|
||||
result[len] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
static void fill_person(struct interp *table, const char *msg, int len)
|
||||
{
|
||||
int start, end, tz = 0;
|
||||
unsigned long date;
|
||||
char *ep;
|
||||
|
||||
/* parse name */
|
||||
for (end = 0; end < len && msg[end] != '<'; end++)
|
||||
; /* do nothing */
|
||||
start = end + 1;
|
||||
while (end > 0 && isspace(msg[end - 1]))
|
||||
end--;
|
||||
table[0].value = xstrndup(msg, end);
|
||||
|
||||
if (start >= len)
|
||||
return;
|
||||
|
||||
/* parse email */
|
||||
for (end = start + 1; end < len && msg[end] != '>'; end++)
|
||||
; /* do nothing */
|
||||
|
||||
if (end >= len)
|
||||
return;
|
||||
|
||||
table[1].value = xstrndup(msg + start, end - start);
|
||||
|
||||
/* parse date */
|
||||
for (start = end + 1; start < len && isspace(msg[start]); start++)
|
||||
; /* do nothing */
|
||||
if (start >= len)
|
||||
return;
|
||||
date = strtoul(msg + start, &ep, 10);
|
||||
if (msg + start == ep)
|
||||
return;
|
||||
|
||||
table[5].value = xstrndup(msg + start, ep - msg + start);
|
||||
|
||||
/* parse tz */
|
||||
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
|
||||
; /* do nothing */
|
||||
if (start + 1 < len) {
|
||||
tz = strtoul(msg + start + 1, NULL, 10);
|
||||
if (msg[start] == '-')
|
||||
tz = -tz;
|
||||
}
|
||||
|
||||
interp_set_entry(table, 2, show_date(date, tz, 0));
|
||||
interp_set_entry(table, 3, show_rfc2822_date(date, tz));
|
||||
interp_set_entry(table, 4, show_date(date, tz, 1));
|
||||
}
|
||||
|
||||
static long format_commit_message(const struct commit *commit,
|
||||
const char *msg, char *buf, unsigned long space)
|
||||
{
|
||||
struct interp table[] = {
|
||||
{ "%H" }, /* commit hash */
|
||||
{ "%h" }, /* abbreviated commit hash */
|
||||
{ "%T" }, /* tree hash */
|
||||
{ "%t" }, /* abbreviated tree hash */
|
||||
{ "%P" }, /* parent hashes */
|
||||
{ "%p" }, /* abbreviated parent hashes */
|
||||
{ "%an" }, /* author name */
|
||||
{ "%ae" }, /* author email */
|
||||
{ "%ad" }, /* author date */
|
||||
{ "%aD" }, /* author date, RFC2822 style */
|
||||
{ "%ar" }, /* author date, relative */
|
||||
{ "%at" }, /* author date, UNIX timestamp */
|
||||
{ "%cn" }, /* committer name */
|
||||
{ "%ce" }, /* committer email */
|
||||
{ "%cd" }, /* committer date */
|
||||
{ "%cD" }, /* committer date, RFC2822 style */
|
||||
{ "%cr" }, /* committer date, relative */
|
||||
{ "%ct" }, /* committer date, UNIX timestamp */
|
||||
{ "%e" }, /* encoding */
|
||||
{ "%s" }, /* subject */
|
||||
{ "%b" }, /* body */
|
||||
{ "%Cred" }, /* red */
|
||||
{ "%Cgreen" }, /* green */
|
||||
{ "%Cblue" }, /* blue */
|
||||
{ "%Creset" }, /* reset color */
|
||||
{ "%n" } /* newline */
|
||||
};
|
||||
enum interp_index {
|
||||
IHASH = 0, IHASH_ABBREV,
|
||||
ITREE, ITREE_ABBREV,
|
||||
IPARENTS, IPARENTS_ABBREV,
|
||||
IAUTHOR_NAME, IAUTHOR_EMAIL,
|
||||
IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
|
||||
IAUTHOR_TIMESTAMP,
|
||||
ICOMMITTER_NAME, ICOMMITTER_EMAIL,
|
||||
ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
|
||||
ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
|
||||
IENCODING,
|
||||
ISUBJECT,
|
||||
IBODY,
|
||||
IRED, IGREEN, IBLUE, IRESET_COLOR,
|
||||
INEWLINE
|
||||
};
|
||||
struct commit_list *p;
|
||||
char parents[1024];
|
||||
int i;
|
||||
enum { HEADER, SUBJECT, BODY } state;
|
||||
|
||||
if (INEWLINE + 1 != ARRAY_SIZE(table))
|
||||
die("invalid interp table!");
|
||||
|
||||
/* these are independent of the commit */
|
||||
interp_set_entry(table, IRED, "\033[31m");
|
||||
interp_set_entry(table, IGREEN, "\033[32m");
|
||||
interp_set_entry(table, IBLUE, "\033[34m");
|
||||
interp_set_entry(table, IRESET_COLOR, "\033[m");
|
||||
interp_set_entry(table, INEWLINE, "\n");
|
||||
|
||||
/* these depend on the commit */
|
||||
if (!commit->object.parsed)
|
||||
parse_object(commit->object.sha1);
|
||||
interp_set_entry(table, IHASH, sha1_to_hex(commit->object.sha1));
|
||||
interp_set_entry(table, IHASH_ABBREV,
|
||||
find_unique_abbrev(commit->object.sha1,
|
||||
DEFAULT_ABBREV));
|
||||
interp_set_entry(table, ITREE, sha1_to_hex(commit->tree->object.sha1));
|
||||
interp_set_entry(table, ITREE_ABBREV,
|
||||
find_unique_abbrev(commit->tree->object.sha1,
|
||||
DEFAULT_ABBREV));
|
||||
for (i = 0, p = commit->parents;
|
||||
p && i < sizeof(parents) - 1;
|
||||
p = p->next)
|
||||
i += snprintf(parents + i, sizeof(parents) - i - 1, "%s ",
|
||||
sha1_to_hex(p->item->object.sha1));
|
||||
interp_set_entry(table, IPARENTS, parents);
|
||||
for (i = 0, p = commit->parents;
|
||||
p && i < sizeof(parents) - 1;
|
||||
p = p->next)
|
||||
i += snprintf(parents + i, sizeof(parents) - i - 1, "%s ",
|
||||
find_unique_abbrev(p->item->object.sha1,
|
||||
DEFAULT_ABBREV));
|
||||
interp_set_entry(table, IPARENTS_ABBREV, parents);
|
||||
|
||||
for (i = 0, state = HEADER; msg[i] && state < BODY; i++) {
|
||||
int eol;
|
||||
for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
|
||||
; /* do nothing */
|
||||
|
||||
if (state == SUBJECT) {
|
||||
table[ISUBJECT].value = xstrndup(msg + i, eol - i);
|
||||
i = eol;
|
||||
}
|
||||
if (i == eol) {
|
||||
state++;
|
||||
/* strip empty lines */
|
||||
while (msg[eol + 1] == '\n')
|
||||
eol++;
|
||||
} else if (!prefixcmp(msg + i, "author "))
|
||||
fill_person(table + IAUTHOR_NAME,
|
||||
msg + i + 7, eol - i - 7);
|
||||
else if (!prefixcmp(msg + i, "committer "))
|
||||
fill_person(table + ICOMMITTER_NAME,
|
||||
msg + i + 10, eol - i - 10);
|
||||
else if (!prefixcmp(msg + i, "encoding "))
|
||||
table[IENCODING].value = xstrndup(msg + i, eol - i);
|
||||
i = eol;
|
||||
}
|
||||
if (msg[i])
|
||||
table[IBODY].value = xstrdup(msg + i);
|
||||
for (i = 0; i < ARRAY_SIZE(table); i++)
|
||||
if (!table[i].value)
|
||||
interp_set_entry(table, i, "<unknown>");
|
||||
|
||||
interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
|
||||
interp_clear_table(table, ARRAY_SIZE(table));
|
||||
|
||||
return strlen(buf);
|
||||
}
|
||||
|
||||
unsigned long pretty_print_commit(enum cmit_fmt fmt,
|
||||
const struct commit *commit,
|
||||
unsigned long len,
|
||||
@ -727,6 +919,9 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
|
||||
char *reencoded;
|
||||
char *encoding;
|
||||
|
||||
if (fmt == CMIT_FMT_USERFORMAT)
|
||||
return format_commit_message(commit, msg, buf, space);
|
||||
|
||||
encoding = (git_log_output_encoding
|
||||
? git_log_output_encoding
|
||||
: git_commit_encoding);
|
||||
|
1
commit.h
1
commit.h
@ -47,6 +47,7 @@ enum cmit_fmt {
|
||||
CMIT_FMT_FULLER,
|
||||
CMIT_FMT_ONELINE,
|
||||
CMIT_FMT_EMAIL,
|
||||
CMIT_FMT_USERFORMAT,
|
||||
|
||||
CMIT_FMT_UNSPECIFIED,
|
||||
};
|
||||
|
8
date.c
8
date.c
@ -55,12 +55,12 @@ static struct tm *time_to_tm(unsigned long time, int tz)
|
||||
return gmtime(&t);
|
||||
}
|
||||
|
||||
const char *show_date(unsigned long time, int tz, int relative)
|
||||
const char *show_date(unsigned long time, int tz, enum date_mode mode)
|
||||
{
|
||||
struct tm *tm;
|
||||
static char timebuf[200];
|
||||
|
||||
if (relative) {
|
||||
if (mode == DATE_RELATIVE) {
|
||||
unsigned long diff;
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
@ -105,6 +105,10 @@ const char *show_date(unsigned long time, int tz, int relative)
|
||||
tm = time_to_tm(time, tz);
|
||||
if (!tm)
|
||||
return NULL;
|
||||
if (mode == DATE_SHORT)
|
||||
sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
|
||||
tm->tm_mon + 1, tm->tm_mday);
|
||||
else
|
||||
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
|
||||
weekday_names[tm->tm_wday],
|
||||
month_names[tm->tm_mon],
|
||||
|
@ -211,7 +211,7 @@ void show_log(struct rev_info *opt, const char *sep)
|
||||
sha1, sha1);
|
||||
opt->diffopt.stat_sep = buffer;
|
||||
}
|
||||
} else {
|
||||
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
|
||||
fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
|
||||
stdout);
|
||||
if (opt->commit_format != CMIT_FMT_ONELINE)
|
||||
|
17
utf8.c
17
utf8.c
@ -235,12 +235,19 @@ static void print_spaces(int count)
|
||||
/*
|
||||
* Wrap the text, if necessary. The variable indent is the indent for the
|
||||
* first line, indent2 is the indent for all other lines.
|
||||
* If indent is negative, assume that already -indent columns have been
|
||||
* consumed (and no extra indent is necessary for the first line).
|
||||
*/
|
||||
void print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||
int print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||
{
|
||||
int w = indent, assume_utf8 = is_utf8(text);
|
||||
const char *bol = text, *space = NULL;
|
||||
|
||||
if (indent < 0) {
|
||||
w = -indent;
|
||||
space = text;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
char c = *text;
|
||||
if (!c || isspace(c)) {
|
||||
@ -251,10 +258,9 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||
else
|
||||
print_spaces(indent);
|
||||
fwrite(start, text - start, 1, stdout);
|
||||
if (!c) {
|
||||
putchar('\n');
|
||||
return;
|
||||
} else if (c == '\t')
|
||||
if (!c)
|
||||
return w;
|
||||
else if (c == '\t')
|
||||
w |= 0x07;
|
||||
space = text;
|
||||
w++;
|
||||
@ -275,6 +281,7 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||
text++;
|
||||
}
|
||||
}
|
||||
return w;
|
||||
}
|
||||
|
||||
int is_encoding_utf8(const char *name)
|
||||
|
2
utf8.h
2
utf8.h
@ -5,7 +5,7 @@ int utf8_width(const char **start);
|
||||
int is_utf8(const char *text);
|
||||
int is_encoding_utf8(const char *name);
|
||||
|
||||
void print_wrapped_text(const char *text, int indent, int indent2, int len);
|
||||
int print_wrapped_text(const char *text, int indent, int indent2, int len);
|
||||
|
||||
#ifndef NO_ICONV
|
||||
char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
|
||||
|
Loading…
Reference in New Issue
Block a user