зеркало из https://github.com/microsoft/git.git
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:
Коммит
8ab3e18586
|
@ -77,9 +77,53 @@ displayed in full, regardless of whether --abbrev or
|
||||||
true parent commits, without taking grafts nor history
|
true parent commits, without taking grafts nor history
|
||||||
simplification into account.
|
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>]::
|
--encoding[=<encoding>]::
|
||||||
The commit objects record the encoding used for the log message
|
The commit objects record the encoding used for the log message
|
||||||
in their encoding header; this option can be used to tell the
|
in their encoding header; this option can be used to tell the
|
||||||
command to re-code the commit log message in the encoding
|
command to re-code the commit log message in the encoding
|
||||||
preferred by the user. For non plumbing commands this
|
preferred by the user. For non plumbing commands this
|
||||||
defaults to UTF-8.
|
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 long *size,
|
||||||
unsigned char *sha1_ret);
|
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);
|
const char *show_rfc2822_date(unsigned long time, int timezone);
|
||||||
int parse_date(const char *date, char *buf, int bufsize);
|
int parse_date(const char *date, char *buf, int bufsize);
|
||||||
void datestamp(char *buf, int bufsize);
|
void datestamp(char *buf, int bufsize);
|
||||||
|
|
195
commit.c
195
commit.c
|
@ -3,6 +3,7 @@
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
#include "pkt-line.h"
|
#include "pkt-line.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
#include "interpolate.h"
|
||||||
|
|
||||||
int save_commit_buffer = 1;
|
int save_commit_buffer = 1;
|
||||||
|
|
||||||
|
@ -36,8 +37,11 @@ struct cmt_fmt_map {
|
||||||
{ "full", 5, CMIT_FMT_FULL },
|
{ "full", 5, CMIT_FMT_FULL },
|
||||||
{ "fuller", 5, CMIT_FMT_FULLER },
|
{ "fuller", 5, CMIT_FMT_FULLER },
|
||||||
{ "oneline", 1, CMIT_FMT_ONELINE },
|
{ "oneline", 1, CMIT_FMT_ONELINE },
|
||||||
|
{ "format:", 7, CMIT_FMT_USERFORMAT},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static char *user_format;
|
||||||
|
|
||||||
enum cmit_fmt get_commit_format(const char *arg)
|
enum cmit_fmt get_commit_format(const char *arg)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -46,6 +50,12 @@ enum cmit_fmt get_commit_format(const char *arg)
|
||||||
return CMIT_FMT_DEFAULT;
|
return CMIT_FMT_DEFAULT;
|
||||||
if (*arg == '=')
|
if (*arg == '=')
|
||||||
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++) {
|
for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
|
||||||
if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
|
if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
|
||||||
!strncmp(arg, cmt_fmts[i].n, strlen(arg)))
|
!strncmp(arg, cmt_fmts[i].n, strlen(arg)))
|
||||||
|
@ -710,6 +720,188 @@ static char *logmsg_reencode(const struct commit *commit,
|
||||||
return out;
|
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,
|
unsigned long pretty_print_commit(enum cmit_fmt fmt,
|
||||||
const struct commit *commit,
|
const struct commit *commit,
|
||||||
unsigned long len,
|
unsigned long len,
|
||||||
|
@ -727,6 +919,9 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
|
||||||
char *reencoded;
|
char *reencoded;
|
||||||
char *encoding;
|
char *encoding;
|
||||||
|
|
||||||
|
if (fmt == CMIT_FMT_USERFORMAT)
|
||||||
|
return format_commit_message(commit, msg, buf, space);
|
||||||
|
|
||||||
encoding = (git_log_output_encoding
|
encoding = (git_log_output_encoding
|
||||||
? git_log_output_encoding
|
? git_log_output_encoding
|
||||||
: git_commit_encoding);
|
: git_commit_encoding);
|
||||||
|
|
1
commit.h
1
commit.h
|
@ -47,6 +47,7 @@ enum cmit_fmt {
|
||||||
CMIT_FMT_FULLER,
|
CMIT_FMT_FULLER,
|
||||||
CMIT_FMT_ONELINE,
|
CMIT_FMT_ONELINE,
|
||||||
CMIT_FMT_EMAIL,
|
CMIT_FMT_EMAIL,
|
||||||
|
CMIT_FMT_USERFORMAT,
|
||||||
|
|
||||||
CMIT_FMT_UNSPECIFIED,
|
CMIT_FMT_UNSPECIFIED,
|
||||||
};
|
};
|
||||||
|
|
20
date.c
20
date.c
|
@ -55,12 +55,12 @@ static struct tm *time_to_tm(unsigned long time, int tz)
|
||||||
return gmtime(&t);
|
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;
|
struct tm *tm;
|
||||||
static char timebuf[200];
|
static char timebuf[200];
|
||||||
|
|
||||||
if (relative) {
|
if (mode == DATE_RELATIVE) {
|
||||||
unsigned long diff;
|
unsigned long diff;
|
||||||
struct timeval now;
|
struct timeval now;
|
||||||
gettimeofday(&now, NULL);
|
gettimeofday(&now, NULL);
|
||||||
|
@ -105,12 +105,16 @@ const char *show_date(unsigned long time, int tz, int relative)
|
||||||
tm = time_to_tm(time, tz);
|
tm = time_to_tm(time, tz);
|
||||||
if (!tm)
|
if (!tm)
|
||||||
return NULL;
|
return NULL;
|
||||||
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
|
if (mode == DATE_SHORT)
|
||||||
weekday_names[tm->tm_wday],
|
sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
|
||||||
month_names[tm->tm_mon],
|
tm->tm_mon + 1, tm->tm_mday);
|
||||||
tm->tm_mday,
|
else
|
||||||
tm->tm_hour, tm->tm_min, tm->tm_sec,
|
sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
|
||||||
tm->tm_year + 1900, tz);
|
weekday_names[tm->tm_wday],
|
||||||
|
month_names[tm->tm_mon],
|
||||||
|
tm->tm_mday,
|
||||||
|
tm->tm_hour, tm->tm_min, tm->tm_sec,
|
||||||
|
tm->tm_year + 1900, tz);
|
||||||
return timebuf;
|
return timebuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ void show_log(struct rev_info *opt, const char *sep)
|
||||||
sha1, sha1);
|
sha1, sha1);
|
||||||
opt->diffopt.stat_sep = buffer;
|
opt->diffopt.stat_sep = buffer;
|
||||||
}
|
}
|
||||||
} else {
|
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
|
||||||
fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
|
fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
|
||||||
stdout);
|
stdout);
|
||||||
if (opt->commit_format != CMIT_FMT_ONELINE)
|
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
|
* Wrap the text, if necessary. The variable indent is the indent for the
|
||||||
* first line, indent2 is the indent for all other lines.
|
* 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);
|
int w = indent, assume_utf8 = is_utf8(text);
|
||||||
const char *bol = text, *space = NULL;
|
const char *bol = text, *space = NULL;
|
||||||
|
|
||||||
|
if (indent < 0) {
|
||||||
|
w = -indent;
|
||||||
|
space = text;
|
||||||
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
char c = *text;
|
char c = *text;
|
||||||
if (!c || isspace(c)) {
|
if (!c || isspace(c)) {
|
||||||
|
@ -251,10 +258,9 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||||
else
|
else
|
||||||
print_spaces(indent);
|
print_spaces(indent);
|
||||||
fwrite(start, text - start, 1, stdout);
|
fwrite(start, text - start, 1, stdout);
|
||||||
if (!c) {
|
if (!c)
|
||||||
putchar('\n');
|
return w;
|
||||||
return;
|
else if (c == '\t')
|
||||||
} else if (c == '\t')
|
|
||||||
w |= 0x07;
|
w |= 0x07;
|
||||||
space = text;
|
space = text;
|
||||||
w++;
|
w++;
|
||||||
|
@ -275,6 +281,7 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width)
|
||||||
text++;
|
text++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_encoding_utf8(const char *name)
|
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_utf8(const char *text);
|
||||||
int is_encoding_utf8(const char *name);
|
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
|
#ifndef NO_ICONV
|
||||||
char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
|
char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
|
||||||
|
|
Загрузка…
Ссылка в новой задаче