aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/pretty-formats.txt44
-rw-r--r--cache.h3
-rw-r--r--commit.c195
-rw-r--r--commit.h1
-rw-r--r--date.c20
-rw-r--r--log-tree.c2
-rw-r--r--utf8.c17
-rw-r--r--utf8.h2
8 files changed, 268 insertions, 16 deletions
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
index fb0b0b958..2fe6c3196 100644
--- a/Documentation/pretty-formats.txt
+++ b/Documentation/pretty-formats.txt
@@ -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.
+
diff --git a/cache.h b/cache.h
index b84e3decf..8018b2cd3 100644
--- a/cache.h
+++ b/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);
diff --git a/commit.c b/commit.c
index da515a497..555252734 100644
--- a/commit.c
+++ b/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);
diff --git a/commit.h b/commit.h
index c73744463..83507a07e 100644
--- a/commit.h
+++ b/commit.h
@@ -47,6 +47,7 @@ enum cmit_fmt {
CMIT_FMT_FULLER,
CMIT_FMT_ONELINE,
CMIT_FMT_EMAIL,
+ CMIT_FMT_USERFORMAT,
CMIT_FMT_UNSPECIFIED,
};
diff --git a/date.c b/date.c
index 542c004c2..0ceccbe03 100644
--- a/date.c
+++ b/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,12 +105,16 @@ const char *show_date(unsigned long time, int tz, int relative)
tm = time_to_tm(time, tz);
if (!tm)
return NULL;
- sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d %+05d",
- 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);
+ 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],
+ tm->tm_mday,
+ tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_year + 1900, tz);
return timebuf;
}
diff --git a/log-tree.c b/log-tree.c
index ac8619404..6ce239d8f 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -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)
diff --git a/utf8.c b/utf8.c
index 7c80eeccb..ea23a6e5d 100644
--- a/utf8.c
+++ b/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)
diff --git a/utf8.h b/utf8.h
index a07c5a88a..15db6f1f2 100644
--- a/utf8.h
+++ b/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);