2023-04-11 07:42:00 +00:00
|
|
|
#include "git-compat-util.h"
|
2017-06-14 18:07:36 +00:00
|
|
|
#include "config.h"
|
2007-11-04 19:15:06 +00:00
|
|
|
#include "commit.h"
|
2023-03-21 06:26:03 +00:00
|
|
|
#include "environment.h"
|
2023-03-21 06:25:54 +00:00
|
|
|
#include "gettext.h"
|
2023-05-16 06:34:00 +00:00
|
|
|
#include "hash.h"
|
2023-02-24 00:09:27 +00:00
|
|
|
#include "hex.h"
|
2007-11-04 19:15:06 +00:00
|
|
|
#include "utf8.h"
|
|
|
|
#include "diff.h"
|
2023-04-11 07:41:59 +00:00
|
|
|
#include "pager.h"
|
2007-11-04 19:15:06 +00:00
|
|
|
#include "revision.h"
|
2008-07-21 18:03:49 +00:00
|
|
|
#include "string-list.h"
|
2008-07-11 23:28:18 +00:00
|
|
|
#include "mailmap.h"
|
2008-09-04 21:40:03 +00:00
|
|
|
#include "log-tree.h"
|
2009-10-09 10:21:57 +00:00
|
|
|
#include "notes.h"
|
expand --pretty=format color options
Currently, the only colors available to --pretty=format
users are red, green, and blue. Rather than expand it with a
few new colors, this patch makes the usual config color
syntax available, including more colors, backgrounds, and
attributes.
Because colors are no longer bounded to a single word (e.g.,
%Cred), this uses a more advanced syntax that features a
beginning and end delimiter (but the old syntax still
works). So you can now do:
git log --pretty=tformat:'%C(yellow)%h%C(reset) %s'
to emulate --pretty=oneline, or even
git log --pretty=tformat:'%C(cyan magenta bold)%s%C(reset)'
if you want to relive the awesomeness of 4-color CGA.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-17 15:38:46 +00:00
|
|
|
#include "color.h"
|
2009-10-19 15:48:10 +00:00
|
|
|
#include "reflog-walk.h"
|
2011-10-22 04:06:02 +00:00
|
|
|
#include "gpg-interface.h"
|
2016-11-19 00:58:14 +00:00
|
|
|
#include "trailer.h"
|
2021-02-14 10:04:34 +00:00
|
|
|
#include "run-command.h"
|
2023-06-06 19:48:41 +00:00
|
|
|
#include "object-name.h"
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2022-12-01 14:47:23 +00:00
|
|
|
/*
|
|
|
|
* The limit for formatting directives, which enable the caller to append
|
|
|
|
* arbitrarily many bytes to the formatted buffer. This includes padding
|
|
|
|
* and wrapping formatters.
|
|
|
|
*/
|
|
|
|
#define FORMATTING_LIMIT (16 * 1024)
|
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
static char *user_format;
|
2010-05-02 11:00:42 +00:00
|
|
|
static struct cmt_fmt_map {
|
|
|
|
const char *name;
|
|
|
|
enum cmit_fmt format;
|
|
|
|
int is_tformat;
|
2016-03-29 22:49:24 +00:00
|
|
|
int expand_tabs_in_log;
|
2010-05-02 11:00:43 +00:00
|
|
|
int is_alias;
|
2019-11-20 00:51:23 +00:00
|
|
|
enum date_mode_type default_date_mode_type;
|
2010-05-02 11:00:43 +00:00
|
|
|
const char *user_format;
|
2010-05-02 11:00:42 +00:00
|
|
|
} *commit_formats;
|
2010-05-02 11:00:44 +00:00
|
|
|
static size_t builtin_formats_len;
|
2010-05-02 11:00:42 +00:00
|
|
|
static size_t commit_formats_len;
|
2010-05-02 11:00:44 +00:00
|
|
|
static size_t commit_formats_alloc;
|
2010-05-02 11:00:42 +00:00
|
|
|
static struct cmt_fmt_map *find_commit_format(const char *sought);
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2014-07-29 17:56:48 +00:00
|
|
|
int commit_format_is_empty(enum cmit_fmt fmt)
|
|
|
|
{
|
|
|
|
return fmt == CMIT_FMT_USERFORMAT && !*user_format;
|
|
|
|
}
|
|
|
|
|
2009-02-24 09:59:15 +00:00
|
|
|
static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
|
|
|
|
{
|
|
|
|
free(user_format);
|
|
|
|
user_format = xstrdup(cp);
|
|
|
|
if (is_tformat)
|
|
|
|
rev->use_terminator = 1;
|
|
|
|
rev->commit_format = CMIT_FMT_USERFORMAT;
|
|
|
|
}
|
|
|
|
|
2022-08-19 10:08:44 +00:00
|
|
|
static int git_pretty_formats_config(const char *var, const char *value,
|
config: add ctx arg to config_fn_t
Add a new "const struct config_context *ctx" arg to config_fn_t to hold
additional information about the config iteration operation.
config_context has a "struct key_value_info kvi" member that holds
metadata about the config source being read (e.g. what kind of config
source it is, the filename, etc). In this series, we're only interested
in .kvi, so we could have just used "struct key_value_info" as an arg,
but config_context makes it possible to add/adjust members in the future
without changing the config_fn_t signature. We could also consider other
ways of organizing the args (e.g. moving the config name and value into
config_context or key_value_info), but in my experiments, the
incremental benefit doesn't justify the added complexity (e.g. a
config_fn_t will sometimes invoke another config_fn_t but with a
different config value).
In subsequent commits, the .kvi member will replace the global "struct
config_reader" in config.c, making config iteration a global-free
operation. It requires much more work for the machinery to provide
meaningful values of .kvi, so for now, merely change the signature and
call sites, pass NULL as a placeholder value, and don't rely on the arg
in any meaningful way.
Most of the changes are performed by
contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every
config_fn_t:
- Modifies the signature to accept "const struct config_context *ctx"
- Passes "ctx" to any inner config_fn_t, if needed
- Adds UNUSED attributes to "ctx", if needed
Most config_fn_t instances are easily identified by seeing if they are
called by the various config functions. Most of the remaining ones are
manually named in the .cocci patch. Manual cleanups are still needed,
but the majority of it is trivial; it's either adjusting config_fn_t
that the .cocci patch didn't catch, or adding forward declarations of
"struct config_context ctx" to make the signatures make sense.
The non-trivial changes are in cases where we are invoking a config_fn_t
outside of config machinery, and we now need to decide what value of
"ctx" to pass. These cases are:
- trace2/tr2_cfg.c:tr2_cfg_set_fl()
This is indirectly called by git_config_set() so that the trace2
machinery can notice the new config values and update its settings
using the tr2 config parsing function, i.e. tr2_cfg_cb().
- builtin/checkout.c:checkout_main()
This calls git_xmerge_config() as a shorthand for parsing a CLI arg.
This might be worth refactoring away in the future, since
git_xmerge_config() can call git_default_config(), which can do much
more than just parsing.
Handle them by creating a KVI_INIT macro that initializes "struct
key_value_info" to a reasonable default, and use that to construct the
"ctx" arg.
Signed-off-by: Glen Choo <chooglen@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-06-28 19:26:22 +00:00
|
|
|
const struct config_context *ctx UNUSED,
|
2022-08-25 17:09:48 +00:00
|
|
|
void *cb UNUSED)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2010-05-02 11:00:44 +00:00
|
|
|
struct cmt_fmt_map *commit_format = NULL;
|
|
|
|
const char *name;
|
|
|
|
const char *fmt;
|
2007-11-04 19:15:06 +00:00
|
|
|
int i;
|
2010-05-02 11:00:44 +00:00
|
|
|
|
2014-06-18 19:48:29 +00:00
|
|
|
if (!skip_prefix(var, "pretty.", &name))
|
2010-05-02 11:00:44 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
for (i = 0; i < builtin_formats_len; i++) {
|
|
|
|
if (!strcmp(commit_formats[i].name, name))
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = builtin_formats_len; i < commit_formats_len; i++) {
|
|
|
|
if (!strcmp(commit_formats[i].name, name)) {
|
|
|
|
commit_format = &commit_formats[i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!commit_format) {
|
|
|
|
ALLOC_GROW(commit_formats, commit_formats_len+1,
|
|
|
|
commit_formats_alloc);
|
|
|
|
commit_format = &commit_formats[commit_formats_len];
|
2010-05-08 21:07:39 +00:00
|
|
|
memset(commit_format, 0, sizeof(*commit_format));
|
2010-05-02 11:00:44 +00:00
|
|
|
commit_formats_len++;
|
|
|
|
}
|
|
|
|
|
|
|
|
commit_format->name = xstrdup(name);
|
|
|
|
commit_format->format = CMIT_FMT_USERFORMAT;
|
2014-08-04 14:41:15 +00:00
|
|
|
if (git_config_string(&fmt, var, value))
|
|
|
|
return -1;
|
|
|
|
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(fmt, "format:", &fmt))
|
|
|
|
commit_format->is_tformat = 0;
|
|
|
|
else if (skip_prefix(fmt, "tformat:", &fmt) || strchr(fmt, '%'))
|
2010-05-02 11:00:44 +00:00
|
|
|
commit_format->is_tformat = 1;
|
|
|
|
else
|
|
|
|
commit_format->is_alias = 1;
|
|
|
|
commit_format->user_format = fmt;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-05-02 11:00:42 +00:00
|
|
|
static void setup_commit_formats(void)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2010-05-02 11:00:42 +00:00
|
|
|
struct cmt_fmt_map builtin_formats[] = {
|
2016-03-29 22:49:24 +00:00
|
|
|
{ "raw", CMIT_FMT_RAW, 0, 0 },
|
2016-03-29 23:05:39 +00:00
|
|
|
{ "medium", CMIT_FMT_MEDIUM, 0, 8 },
|
2016-03-29 22:49:24 +00:00
|
|
|
{ "short", CMIT_FMT_SHORT, 0, 0 },
|
|
|
|
{ "email", CMIT_FMT_EMAIL, 0, 0 },
|
2016-06-05 04:46:39 +00:00
|
|
|
{ "mboxrd", CMIT_FMT_MBOXRD, 0, 0 },
|
2016-03-29 23:05:39 +00:00
|
|
|
{ "fuller", CMIT_FMT_FULLER, 0, 8 },
|
|
|
|
{ "full", CMIT_FMT_FULL, 0, 8 },
|
2019-11-20 00:51:25 +00:00
|
|
|
{ "oneline", CMIT_FMT_ONELINE, 1, 0 },
|
|
|
|
{ "reference", CMIT_FMT_USERFORMAT, 1, 0,
|
|
|
|
0, DATE_SHORT, "%C(auto)%h (%s, %ad)" },
|
2019-02-16 11:24:41 +00:00
|
|
|
/*
|
|
|
|
* Please update $__git_log_pretty_formats in
|
|
|
|
* git-completion.bash when you add new formats.
|
|
|
|
*/
|
2008-04-08 00:11:34 +00:00
|
|
|
};
|
2010-05-02 11:00:42 +00:00
|
|
|
commit_formats_len = ARRAY_SIZE(builtin_formats);
|
2010-05-02 11:00:44 +00:00
|
|
|
builtin_formats_len = commit_formats_len;
|
|
|
|
ALLOC_GROW(commit_formats, commit_formats_len, commit_formats_alloc);
|
2019-06-15 18:36:35 +00:00
|
|
|
COPY_ARRAY(commit_formats, builtin_formats,
|
|
|
|
ARRAY_SIZE(builtin_formats));
|
2010-05-02 11:00:44 +00:00
|
|
|
|
|
|
|
git_config(git_pretty_formats_config, NULL);
|
2010-05-02 11:00:42 +00:00
|
|
|
}
|
|
|
|
|
2010-05-02 11:00:43 +00:00
|
|
|
static struct cmt_fmt_map *find_commit_format_recursive(const char *sought,
|
|
|
|
const char *original,
|
|
|
|
int num_redirections)
|
2010-05-02 11:00:42 +00:00
|
|
|
{
|
|
|
|
struct cmt_fmt_map *found = NULL;
|
|
|
|
size_t found_match_len = 0;
|
|
|
|
int i;
|
|
|
|
|
2010-05-02 11:00:43 +00:00
|
|
|
if (num_redirections >= commit_formats_len)
|
|
|
|
die("invalid --pretty format: "
|
|
|
|
"'%s' references an alias which points to itself",
|
|
|
|
original);
|
2010-05-02 11:00:42 +00:00
|
|
|
|
|
|
|
for (i = 0; i < commit_formats_len; i++) {
|
|
|
|
size_t match_len;
|
|
|
|
|
2024-03-25 07:25:13 +00:00
|
|
|
if (!istarts_with(commit_formats[i].name, sought))
|
2010-05-02 11:00:42 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
match_len = strlen(commit_formats[i].name);
|
|
|
|
if (found == NULL || found_match_len > match_len) {
|
|
|
|
found = &commit_formats[i];
|
|
|
|
found_match_len = match_len;
|
|
|
|
}
|
|
|
|
}
|
2010-05-02 11:00:43 +00:00
|
|
|
|
|
|
|
if (found && found->is_alias) {
|
|
|
|
found = find_commit_format_recursive(found->user_format,
|
|
|
|
original,
|
|
|
|
num_redirections+1);
|
|
|
|
}
|
|
|
|
|
2010-05-02 11:00:42 +00:00
|
|
|
return found;
|
|
|
|
}
|
|
|
|
|
2010-05-02 11:00:43 +00:00
|
|
|
static struct cmt_fmt_map *find_commit_format(const char *sought)
|
|
|
|
{
|
|
|
|
if (!commit_formats)
|
|
|
|
setup_commit_formats();
|
|
|
|
|
|
|
|
return find_commit_format_recursive(sought, sought, 0);
|
|
|
|
}
|
|
|
|
|
2010-05-02 11:00:42 +00:00
|
|
|
void get_commit_format(const char *arg, struct rev_info *rev)
|
|
|
|
{
|
|
|
|
struct cmt_fmt_map *commit_format;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2008-04-08 00:11:34 +00:00
|
|
|
rev->use_terminator = 0;
|
2014-07-29 17:54:46 +00:00
|
|
|
if (!arg) {
|
2008-04-08 00:11:34 +00:00
|
|
|
rev->commit_format = CMIT_FMT_DEFAULT;
|
|
|
|
return;
|
|
|
|
}
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(arg, "format:", &arg)) {
|
|
|
|
save_user_format(rev, arg, 0);
|
2008-04-08 00:11:34 +00:00
|
|
|
return;
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
2010-05-02 11:00:42 +00:00
|
|
|
|
2014-10-04 18:54:50 +00:00
|
|
|
if (!*arg || skip_prefix(arg, "tformat:", &arg) || strchr(arg, '%')) {
|
2009-02-24 09:59:15 +00:00
|
|
|
save_user_format(rev, arg, 1);
|
|
|
|
return;
|
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2010-05-02 11:00:42 +00:00
|
|
|
commit_format = find_commit_format(arg);
|
|
|
|
if (!commit_format)
|
|
|
|
die("invalid --pretty format: %s", arg);
|
|
|
|
|
|
|
|
rev->commit_format = commit_format->format;
|
|
|
|
rev->use_terminator = commit_format->is_tformat;
|
2016-03-29 22:49:24 +00:00
|
|
|
rev->expand_tabs_in_log_default = commit_format->expand_tabs_in_log;
|
2019-11-20 00:51:23 +00:00
|
|
|
if (!rev->date_mode_explicit && commit_format->default_date_mode_type)
|
|
|
|
rev->date_mode.type = commit_format->default_date_mode_type;
|
2010-05-02 11:00:44 +00:00
|
|
|
if (commit_format->format == CMIT_FMT_USERFORMAT) {
|
|
|
|
save_user_format(rev, commit_format->user_format,
|
|
|
|
commit_format->is_tformat);
|
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Generic support for pretty-printing the header
|
|
|
|
*/
|
|
|
|
static int get_one_line(const char *msg)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
char c = *msg++;
|
|
|
|
if (!c)
|
|
|
|
break;
|
|
|
|
ret++;
|
|
|
|
if (c == '\n')
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* High bit set, or ISO-2022-INT */
|
2010-01-12 06:23:35 +00:00
|
|
|
static int non_ascii(int ch)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2009-03-07 13:06:49 +00:00
|
|
|
return !isascii(ch) || ch == '\033';
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
2009-08-10 16:22:18 +00:00
|
|
|
int has_non_ascii(const char *s)
|
|
|
|
{
|
|
|
|
int ch;
|
|
|
|
if (!s)
|
|
|
|
return 0;
|
|
|
|
while ((ch = *s++) != '\0') {
|
|
|
|
if (non_ascii(ch))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-04-08 22:40:36 +00:00
|
|
|
static int is_rfc822_special(char ch)
|
|
|
|
{
|
|
|
|
switch (ch) {
|
|
|
|
case '(':
|
|
|
|
case ')':
|
|
|
|
case '<':
|
|
|
|
case '>':
|
|
|
|
case '[':
|
|
|
|
case ']':
|
|
|
|
case ':':
|
|
|
|
case ';':
|
|
|
|
case '@':
|
|
|
|
case ',':
|
|
|
|
case '.':
|
|
|
|
case '"':
|
|
|
|
case '\\':
|
|
|
|
return 1;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-18 14:43:33 +00:00
|
|
|
static int needs_rfc822_quoting(const char *s, int len)
|
2011-04-08 22:40:36 +00:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
|
|
if (is_rfc822_special(s[i]))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-18 14:43:31 +00:00
|
|
|
static int last_line_length(struct strbuf *sb)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* How many bytes are already used on the last line? */
|
|
|
|
for (i = sb->len - 1; i >= 0; i--)
|
|
|
|
if (sb->buf[i] == '\n')
|
|
|
|
break;
|
|
|
|
return sb->len - (i + 1);
|
|
|
|
}
|
|
|
|
|
2011-04-08 22:40:36 +00:00
|
|
|
static void add_rfc822_quoted(struct strbuf *out, const char *s, int len)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* just a guess, we may have to also backslash-quote */
|
|
|
|
strbuf_grow(out, len + 2);
|
|
|
|
|
|
|
|
strbuf_addch(out, '"');
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
switch (s[i]) {
|
|
|
|
case '"':
|
|
|
|
case '\\':
|
|
|
|
strbuf_addch(out, '\\');
|
|
|
|
/* fall through */
|
|
|
|
default:
|
|
|
|
strbuf_addch(out, s[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strbuf_addch(out, '"');
|
|
|
|
}
|
|
|
|
|
2012-10-18 14:43:32 +00:00
|
|
|
enum rfc2047_type {
|
|
|
|
RFC2047_SUBJECT,
|
2014-07-02 18:24:05 +00:00
|
|
|
RFC2047_ADDRESS
|
2012-10-18 14:43:32 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static int is_rfc2047_special(char ch, enum rfc2047_type type)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2012-10-18 14:43:32 +00:00
|
|
|
/*
|
|
|
|
* rfc2047, section 4.2:
|
|
|
|
*
|
|
|
|
* 8-bit values which correspond to printable ASCII characters other
|
|
|
|
* than "=", "?", and "_" (underscore), MAY be represented as those
|
|
|
|
* characters. (But see section 5 for restrictions.) In
|
|
|
|
* particular, SPACE and TAB MUST NOT be represented as themselves
|
|
|
|
* within encoded words.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
* rule out non-ASCII characters and non-printable characters (the
|
|
|
|
* non-ASCII check should be redundant as isprint() is not localized
|
|
|
|
* and only knows about ASCII, but be defensive about that)
|
|
|
|
*/
|
|
|
|
if (non_ascii(ch) || !isprint(ch))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* rule out special printable characters (' ' should be the only
|
|
|
|
* whitespace character considered printable, but be defensive and use
|
|
|
|
* isspace())
|
|
|
|
*/
|
|
|
|
if (isspace(ch) || ch == '=' || ch == '?' || ch == '_')
|
2012-10-18 14:43:30 +00:00
|
|
|
return 1;
|
|
|
|
|
2012-10-18 14:43:32 +00:00
|
|
|
/*
|
|
|
|
* rfc2047, section 5.3:
|
|
|
|
*
|
|
|
|
* As a replacement for a 'word' entity within a 'phrase', for example,
|
|
|
|
* one that precedes an address in a From, To, or Cc header. The ABNF
|
|
|
|
* definition for 'phrase' from RFC 822 thus becomes:
|
|
|
|
*
|
|
|
|
* phrase = 1*( encoded-word / word )
|
|
|
|
*
|
|
|
|
* In this case the set of characters that may be used in a "Q"-encoded
|
|
|
|
* 'encoded-word' is restricted to: <upper and lower case ASCII
|
|
|
|
* letters, decimal digits, "!", "*", "+", "-", "/", "=", and "_"
|
|
|
|
* (underscore, ASCII 95.)>. An 'encoded-word' that appears within a
|
|
|
|
* 'phrase' MUST be separated from any adjacent 'word', 'text' or
|
|
|
|
* 'special' by 'linear-white-space'.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (type != RFC2047_ADDRESS)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* '=' and '_' are special cases and have been checked above */
|
|
|
|
return !(isalnum(ch) || ch == '!' || ch == '*' || ch == '+' || ch == '-' || ch == '/');
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
2019-03-20 08:16:36 +00:00
|
|
|
static int needs_rfc2047_encoding(const char *line, int len)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2011-02-23 09:58:41 +00:00
|
|
|
int i;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
int ch = line[i];
|
2011-02-23 09:59:18 +00:00
|
|
|
if (non_ascii(ch) || ch == '\n')
|
2012-10-18 14:43:33 +00:00
|
|
|
return 1;
|
2007-11-04 19:15:06 +00:00
|
|
|
if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
|
2012-10-18 14:43:33 +00:00
|
|
|
return 1;
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
2012-10-18 14:43:33 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-03-07 10:55:07 +00:00
|
|
|
static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
|
2012-10-18 14:43:33 +00:00
|
|
|
const char *encoding, enum rfc2047_type type)
|
|
|
|
{
|
|
|
|
static const int max_encoded_length = 76; /* per rfc2047 */
|
|
|
|
int i;
|
|
|
|
int line_len = last_line_length(sb);
|
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
|
|
|
|
strbuf_addf(sb, "=?%s?q?", encoding);
|
2011-02-23 09:58:41 +00:00
|
|
|
line_len += strlen(encoding) + 5; /* 5 for =??q? */
|
2013-03-07 10:55:07 +00:00
|
|
|
|
|
|
|
while (len) {
|
|
|
|
/*
|
|
|
|
* RFC 2047, section 5 (3):
|
|
|
|
*
|
|
|
|
* Each 'encoded-word' MUST represent an integral number of
|
|
|
|
* characters. A multi-octet character may not be split across
|
|
|
|
* adjacent 'encoded- word's.
|
|
|
|
*/
|
|
|
|
const unsigned char *p = (const unsigned char *)line;
|
|
|
|
int chrlen = mbs_chrlen(&line, &len, encoding);
|
|
|
|
int is_special = (chrlen > 1) || is_rfc2047_special(*p, type);
|
|
|
|
|
|
|
|
/* "=%02X" * chrlen, or the byte itself */
|
|
|
|
const char *encoded_fmt = is_special ? "=%02X" : "%c";
|
|
|
|
int encoded_len = is_special ? 3 * chrlen : 1;
|
2012-10-18 14:43:30 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* According to RFC 2047, we could encode the special character
|
|
|
|
* ' ' (space) with '_' (underscore) for readability. But many
|
|
|
|
* programs do not understand this and just leave the
|
|
|
|
* underscore in place. Thus, we do nothing special here, which
|
|
|
|
* causes ' ' to be encoded as '=20', avoiding this problem.
|
|
|
|
*/
|
2011-02-23 09:58:41 +00:00
|
|
|
|
2013-03-07 10:55:07 +00:00
|
|
|
if (line_len + encoded_len + 2 > max_encoded_length) {
|
|
|
|
/* It won't fit with trailing "?=" --- break the line */
|
2011-02-23 09:58:41 +00:00
|
|
|
strbuf_addf(sb, "?=\n =?%s?q?", encoding);
|
|
|
|
line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
|
|
|
|
}
|
|
|
|
|
2013-03-07 10:55:07 +00:00
|
|
|
for (i = 0; i < chrlen; i++)
|
|
|
|
strbuf_addf(sb, encoded_fmt, p[i]);
|
|
|
|
line_len += encoded_len;
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
strbuf_addstr(sb, "?=");
|
|
|
|
}
|
|
|
|
|
2014-05-02 01:07:22 +00:00
|
|
|
const char *show_ident_date(const struct ident_split *ident,
|
2024-04-05 17:44:59 +00:00
|
|
|
struct date_mode mode)
|
2013-04-17 18:33:35 +00:00
|
|
|
{
|
2017-04-26 19:29:31 +00:00
|
|
|
timestamp_t date = 0;
|
2014-03-07 17:15:01 +00:00
|
|
|
long tz = 0;
|
2013-04-17 18:33:35 +00:00
|
|
|
|
|
|
|
if (ident->date_begin && ident->date_end)
|
2017-04-21 10:45:44 +00:00
|
|
|
date = parse_timestamp(ident->date_begin, NULL, 10);
|
log: handle integer overflow in timestamps
If an ident line has a ridiculous date value like (2^64)+1,
we currently just pass ULONG_MAX along to the date code,
which can produce nonsensical dates.
On systems with a signed long time_t (e.g., 64-bit glibc
systems), this actually doesn't end up too bad. The
ULONG_MAX is converted to -1, we apply the timezone field to
that, and the result ends up somewhere between Dec 31, 1969
and Jan 1, 1970.
However, there is still a few good reasons to detect the
overflow explicitly:
1. On systems where "unsigned long" is smaller than
time_t, we get a nonsensical date in the future.
2. Even where it would produce "Dec 31, 1969", it's easier
to recognize "midnight Jan 1" as a consistent sentinel
value for "we could not parse this".
3. Values which do not overflow strtoul but do overflow a
signed time_t produce nonsensical values in the past.
For example, on a 64-bit system with a signed long
time_t, a timestamp of 18446744073000000000 produces a
date in 1947.
We also recognize overflow in the timezone field, which
could produce nonsensical results. In this case we show the
parsed date, but in UTC.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-02-24 07:46:37 +00:00
|
|
|
if (date_overflows(date))
|
|
|
|
date = 0;
|
|
|
|
else {
|
|
|
|
if (ident->tz_begin && ident->tz_end)
|
|
|
|
tz = strtol(ident->tz_begin, NULL, 10);
|
2014-03-07 17:15:01 +00:00
|
|
|
if (tz >= INT_MAX || tz <= INT_MIN)
|
log: handle integer overflow in timestamps
If an ident line has a ridiculous date value like (2^64)+1,
we currently just pass ULONG_MAX along to the date code,
which can produce nonsensical dates.
On systems with a signed long time_t (e.g., 64-bit glibc
systems), this actually doesn't end up too bad. The
ULONG_MAX is converted to -1, we apply the timezone field to
that, and the result ends up somewhere between Dec 31, 1969
and Jan 1, 1970.
However, there is still a few good reasons to detect the
overflow explicitly:
1. On systems where "unsigned long" is smaller than
time_t, we get a nonsensical date in the future.
2. Even where it would produce "Dec 31, 1969", it's easier
to recognize "midnight Jan 1" as a consistent sentinel
value for "we could not parse this".
3. Values which do not overflow strtoul but do overflow a
signed time_t produce nonsensical values in the past.
For example, on a 64-bit system with a signed long
time_t, a timestamp of 18446744073000000000 produces a
date in 1947.
We also recognize overflow in the timezone field, which
could produce nonsensical results. In this case we show the
parsed date, but in UTC.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-02-24 07:46:37 +00:00
|
|
|
tz = 0;
|
|
|
|
}
|
2013-04-17 18:33:35 +00:00
|
|
|
return show_date(date, tz, mode);
|
|
|
|
}
|
|
|
|
|
2021-10-07 20:31:47 +00:00
|
|
|
static inline void strbuf_add_with_color(struct strbuf *sb, const char *color,
|
|
|
|
const char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
strbuf_addstr(sb, color);
|
|
|
|
strbuf_add(sb, buf, buflen);
|
|
|
|
if (*color)
|
|
|
|
strbuf_addstr(sb, GIT_COLOR_RESET);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void append_line_with_color(struct strbuf *sb, struct grep_opt *opt,
|
|
|
|
const char *line, size_t linelen,
|
|
|
|
int color, enum grep_context ctx,
|
|
|
|
enum grep_header_field field)
|
|
|
|
{
|
|
|
|
const char *buf, *eol, *line_color, *match_color;
|
|
|
|
regmatch_t match;
|
|
|
|
int eflags = 0;
|
|
|
|
|
|
|
|
buf = line;
|
|
|
|
eol = buf + linelen;
|
|
|
|
|
|
|
|
if (!opt || !want_color(color) || opt->invert)
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
line_color = opt->colors[GREP_COLOR_SELECTED];
|
|
|
|
match_color = opt->colors[GREP_COLOR_MATCH_SELECTED];
|
|
|
|
|
|
|
|
while (grep_next_match(opt, buf, eol, ctx, &match, field, eflags)) {
|
|
|
|
if (match.rm_so == match.rm_eo)
|
|
|
|
break;
|
|
|
|
|
|
|
|
strbuf_add_with_color(sb, line_color, buf, match.rm_so);
|
|
|
|
strbuf_add_with_color(sb, match_color, buf + match.rm_so,
|
|
|
|
match.rm_eo - match.rm_so);
|
|
|
|
buf += match.rm_eo;
|
|
|
|
eflags = REG_NOTBOL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (eflags)
|
|
|
|
strbuf_add_with_color(sb, line_color, buf, eol - buf);
|
|
|
|
else {
|
|
|
|
end:
|
|
|
|
strbuf_add(sb, buf, eol - buf);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-29 21:38:35 +00:00
|
|
|
static int use_in_body_from(const struct pretty_print_context *pp,
|
|
|
|
const struct ident_split *ident)
|
|
|
|
{
|
2022-08-29 21:38:36 +00:00
|
|
|
if (pp->rev && pp->rev->force_in_body_from)
|
|
|
|
return 1;
|
2022-08-29 21:38:35 +00:00
|
|
|
if (ident_cmp(pp->from_ident, ident))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-03 07:07:48 +00:00
|
|
|
void pp_user_info(struct pretty_print_context *pp,
|
2011-05-26 22:27:49 +00:00
|
|
|
const char *what, struct strbuf *sb,
|
|
|
|
const char *line, const char *encoding)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2013-01-05 21:26:38 +00:00
|
|
|
struct ident_split ident;
|
2013-04-17 18:33:35 +00:00
|
|
|
char *line_end;
|
2013-01-05 21:26:42 +00:00
|
|
|
const char *mailbuf, *namebuf;
|
|
|
|
size_t namelen, maillen;
|
2012-10-18 14:43:33 +00:00
|
|
|
int max_length = 78; /* per rfc2822 */
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE)
|
2007-11-04 19:15:06 +00:00
|
|
|
return;
|
2013-01-05 21:26:38 +00:00
|
|
|
|
2013-04-25 19:40:25 +00:00
|
|
|
line_end = strchrnul(line, '\n');
|
|
|
|
if (split_ident_line(&ident, line, line_end - line))
|
2007-11-04 19:15:06 +00:00
|
|
|
return;
|
2013-01-05 21:26:38 +00:00
|
|
|
|
2013-01-05 21:26:42 +00:00
|
|
|
mailbuf = ident.mail_begin;
|
|
|
|
maillen = ident.mail_end - ident.mail_begin;
|
|
|
|
namebuf = ident.name_begin;
|
|
|
|
namelen = ident.name_end - ident.name_begin;
|
|
|
|
|
|
|
|
if (pp->mailmap)
|
|
|
|
map_user(pp->mailmap, &mailbuf, &maillen, &namebuf, &namelen);
|
|
|
|
|
2016-06-05 04:46:39 +00:00
|
|
|
if (cmit_fmt_is_mail(pp->fmt)) {
|
2022-08-29 21:38:35 +00:00
|
|
|
if (pp->from_ident && use_in_body_from(pp, &ident)) {
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 07:08:22 +00:00
|
|
|
struct strbuf buf = STRBUF_INIT;
|
|
|
|
|
|
|
|
strbuf_addstr(&buf, "From: ");
|
|
|
|
strbuf_add(&buf, namebuf, namelen);
|
|
|
|
strbuf_addstr(&buf, " <");
|
|
|
|
strbuf_add(&buf, mailbuf, maillen);
|
|
|
|
strbuf_addstr(&buf, ">\n");
|
|
|
|
string_list_append(&pp->in_body_headers,
|
|
|
|
strbuf_detach(&buf, NULL));
|
|
|
|
|
|
|
|
mailbuf = pp->from_ident->mail_begin;
|
|
|
|
maillen = pp->from_ident->mail_end - mailbuf;
|
|
|
|
namebuf = pp->from_ident->name_begin;
|
|
|
|
namelen = pp->from_ident->name_end - namebuf;
|
|
|
|
}
|
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addstr(sb, "From: ");
|
2020-04-08 04:31:38 +00:00
|
|
|
if (pp->encode_email_headers &&
|
|
|
|
needs_rfc2047_encoding(namebuf, namelen)) {
|
2013-04-25 19:43:56 +00:00
|
|
|
add_rfc2047(sb, namebuf, namelen,
|
2013-01-05 21:26:42 +00:00
|
|
|
encoding, RFC2047_ADDRESS);
|
2012-10-18 14:43:33 +00:00
|
|
|
max_length = 76; /* per rfc2047 */
|
2013-04-25 19:43:56 +00:00
|
|
|
} else if (needs_rfc822_quoting(namebuf, namelen)) {
|
2011-04-08 22:40:36 +00:00
|
|
|
struct strbuf quoted = STRBUF_INIT;
|
2013-04-25 19:43:56 +00:00
|
|
|
add_rfc822_quoted("ed, namebuf, namelen);
|
2012-10-18 14:43:33 +00:00
|
|
|
strbuf_add_wrapped_bytes(sb, quoted.buf, quoted.len,
|
|
|
|
-6, 1, max_length);
|
2011-04-08 22:40:36 +00:00
|
|
|
strbuf_release("ed);
|
2012-10-18 14:43:33 +00:00
|
|
|
} else {
|
2013-04-25 19:43:56 +00:00
|
|
|
strbuf_add_wrapped_bytes(sb, namebuf, namelen,
|
2013-01-05 21:26:42 +00:00
|
|
|
-6, 1, max_length);
|
2011-04-08 22:40:36 +00:00
|
|
|
}
|
2013-01-05 21:26:42 +00:00
|
|
|
|
2013-04-25 19:41:57 +00:00
|
|
|
if (max_length <
|
|
|
|
last_line_length(sb) + strlen(" <") + maillen + strlen(">"))
|
|
|
|
strbuf_addch(sb, '\n');
|
2013-04-25 19:43:56 +00:00
|
|
|
strbuf_addf(sb, " <%.*s>\n", (int)maillen, mailbuf);
|
2007-11-04 19:15:06 +00:00
|
|
|
} else {
|
2021-10-07 20:31:47 +00:00
|
|
|
struct strbuf id = STRBUF_INIT;
|
|
|
|
enum grep_header_field field = GREP_HEADER_FIELD_MAX;
|
|
|
|
struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
|
|
|
|
|
|
|
|
if (!strcmp(what, "Author"))
|
|
|
|
field = GREP_HEADER_AUTHOR;
|
|
|
|
else if (!strcmp(what, "Commit"))
|
|
|
|
field = GREP_HEADER_COMMITTER;
|
|
|
|
|
|
|
|
strbuf_addf(sb, "%s: ", what);
|
|
|
|
if (pp->fmt == CMIT_FMT_FULLER)
|
|
|
|
strbuf_addchars(sb, ' ', 4);
|
|
|
|
|
|
|
|
strbuf_addf(&id, "%.*s <%.*s>", (int)namelen, namebuf,
|
|
|
|
(int)maillen, mailbuf);
|
|
|
|
|
|
|
|
append_line_with_color(sb, opt, id.buf, id.len, pp->color,
|
|
|
|
GREP_CONTEXT_HEAD, field);
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
strbuf_release(&id);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
2013-01-05 21:26:42 +00:00
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
switch (pp->fmt) {
|
2007-11-04 19:15:06 +00:00
|
|
|
case CMIT_FMT_MEDIUM:
|
2013-04-17 18:33:35 +00:00
|
|
|
strbuf_addf(sb, "Date: %s\n",
|
2024-04-05 17:44:59 +00:00
|
|
|
show_ident_date(&ident, pp->date_mode));
|
2007-11-04 19:15:06 +00:00
|
|
|
break;
|
|
|
|
case CMIT_FMT_EMAIL:
|
2016-06-05 04:46:39 +00:00
|
|
|
case CMIT_FMT_MBOXRD:
|
2013-04-17 18:33:35 +00:00
|
|
|
strbuf_addf(sb, "Date: %s\n",
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
show_ident_date(&ident, DATE_MODE(RFC2822)));
|
2007-11-04 19:15:06 +00:00
|
|
|
break;
|
|
|
|
case CMIT_FMT_FULLER:
|
2013-04-17 18:33:35 +00:00
|
|
|
strbuf_addf(sb, "%sDate: %s\n", what,
|
2024-04-05 17:44:59 +00:00
|
|
|
show_ident_date(&ident, pp->date_mode));
|
2007-11-04 19:15:06 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
/* notin' */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-22 20:20:16 +00:00
|
|
|
static int is_blank_line(const char *line, int *len_p)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
|
|
|
int len = *len_p;
|
2013-10-31 09:25:42 +00:00
|
|
|
while (len && isspace(line[len - 1]))
|
2007-11-04 19:15:06 +00:00
|
|
|
len--;
|
|
|
|
*len_p = len;
|
|
|
|
return !len;
|
|
|
|
}
|
|
|
|
|
2016-06-22 20:20:16 +00:00
|
|
|
const char *skip_blank_lines(const char *msg)
|
2008-12-27 00:32:49 +00:00
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
int linelen = get_one_line(msg);
|
|
|
|
int ll = linelen;
|
|
|
|
if (!linelen)
|
|
|
|
break;
|
2016-06-22 20:20:16 +00:00
|
|
|
if (!is_blank_line(msg, &ll))
|
2008-12-27 00:32:49 +00:00
|
|
|
break;
|
|
|
|
msg += linelen;
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
static void add_merge_info(const struct pretty_print_context *pp,
|
|
|
|
struct strbuf *sb, const struct commit *commit)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
|
|
|
struct commit_list *parent = commit->parents;
|
|
|
|
|
2016-06-05 04:46:39 +00:00
|
|
|
if ((pp->fmt == CMIT_FMT_ONELINE) || (cmit_fmt_is_mail(pp->fmt)) ||
|
2007-11-04 19:15:06 +00:00
|
|
|
!parent || !parent->next)
|
|
|
|
return;
|
|
|
|
|
|
|
|
strbuf_addstr(sb, "Merge:");
|
|
|
|
|
|
|
|
while (parent) {
|
2016-10-08 15:38:47 +00:00
|
|
|
struct object_id *oidp = &parent->item->object.oid;
|
|
|
|
strbuf_addch(sb, ' ');
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->abbrev)
|
strbuf: convert strbuf_add_unique_abbrev to use struct object_id
Convert the declaration and definition of strbuf_add_unique_abbrev to
make it take a pointer to struct object_id. Predeclare the struct in
strbuf.h, as cache.h includes strbuf.h before it declares the struct,
and otherwise the struct declaration would have the wrong scope.
Apply the following semantic patch, along with the standard object_id
transforms, to adjust the callers:
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2.hash, E3);
+ strbuf_add_unique_abbrev(E1, &E2, E3);
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2->hash, E3);
+ strbuf_add_unique_abbrev(E1, E2, E3);
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-03-12 02:27:28 +00:00
|
|
|
strbuf_add_unique_abbrev(sb, oidp, pp->abbrev);
|
2016-10-08 15:38:47 +00:00
|
|
|
else
|
|
|
|
strbuf_addstr(sb, oid_to_hex(oidp));
|
2007-11-04 19:15:06 +00:00
|
|
|
parent = parent->next;
|
|
|
|
}
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
commit: provide a function to find a header in a buffer
Usually when we parse a commit, we read it line by line and
handle each individual line (e.g., parse_commit and
parse_commit_header). Sometimes, however, we only care
about extracting a single header. Code in this situation is
stuck doing an ad-hoc parse of the commit buffer.
Let's provide a reusable function to locate a header within
the commit. The code is modeled after pretty.c's
get_header, which is used to extract the encoding.
Since some callers may not have the "struct commit" to go
along with the buffer, we drop that parameter. The only
thing lost is a warning for truncated commits, but that's
OK. This shouldn't happen in practice, and even if it does,
there's no particular reason that this function needs to
complain about it. It either finds the header it was asked
for, or it doesn't (and in the latter case, the caller will
typically complain).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-27 07:56:01 +00:00
|
|
|
static char *get_header(const char *msg, const char *key)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
commit: provide a function to find a header in a buffer
Usually when we parse a commit, we read it line by line and
handle each individual line (e.g., parse_commit and
parse_commit_header). Sometimes, however, we only care
about extracting a single header. Code in this situation is
stuck doing an ad-hoc parse of the commit buffer.
Let's provide a reusable function to locate a header within
the commit. The code is modeled after pretty.c's
get_header, which is used to extract the encoding.
Since some callers may not have the "struct commit" to go
along with the buffer, we drop that parameter. The only
thing lost is a warning for truncated commits, but that's
OK. This shouldn't happen in practice, and even if it does,
there's no particular reason that this function needs to
complain about it. It either finds the header it was asked
for, or it doesn't (and in the latter case, the caller will
typically complain).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-27 07:56:01 +00:00
|
|
|
size_t len;
|
|
|
|
const char *v = find_commit_header(msg, key, &len);
|
|
|
|
return v ? xmemdupz(v, len) : NULL;
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *replace_encoding_header(char *buf, const char *encoding)
|
|
|
|
{
|
2008-10-09 19:12:12 +00:00
|
|
|
struct strbuf tmp = STRBUF_INIT;
|
2007-11-04 19:15:06 +00:00
|
|
|
size_t start, len;
|
|
|
|
char *cp = buf;
|
|
|
|
|
|
|
|
/* guess if there is an encoding header before a \n\n */
|
2015-02-21 19:53:09 +00:00
|
|
|
while (!starts_with(cp, "encoding ")) {
|
2007-11-04 19:15:06 +00:00
|
|
|
cp = strchr(cp, '\n');
|
|
|
|
if (!cp || *++cp == '\n')
|
|
|
|
return buf;
|
|
|
|
}
|
|
|
|
start = cp - buf;
|
|
|
|
cp = strchr(cp, '\n');
|
|
|
|
if (!cp)
|
|
|
|
return buf; /* should not happen but be defensive */
|
|
|
|
len = cp + 1 - (buf + start);
|
|
|
|
|
|
|
|
strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
|
|
|
|
if (is_encoding_utf8(encoding)) {
|
|
|
|
/* we have re-coded to UTF-8; drop the header */
|
|
|
|
strbuf_remove(&tmp, start, len);
|
|
|
|
} else {
|
|
|
|
/* just replaces XXXX in 'encoding XXXX\n' */
|
|
|
|
strbuf_splice(&tmp, start + strlen("encoding "),
|
|
|
|
len - strlen("encoding \n"),
|
|
|
|
encoding, strlen(encoding));
|
|
|
|
}
|
|
|
|
return strbuf_detach(&tmp, NULL);
|
|
|
|
}
|
|
|
|
|
2018-11-14 00:12:59 +00:00
|
|
|
const char *repo_logmsg_reencode(struct repository *r,
|
|
|
|
const struct commit *commit,
|
|
|
|
char **commit_encoding,
|
|
|
|
const char *output_encoding)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2009-05-18 23:44:39 +00:00
|
|
|
static const char *utf8 = "UTF-8";
|
2007-11-04 19:15:06 +00:00
|
|
|
const char *use_encoding;
|
|
|
|
char *encoding;
|
2018-11-14 00:12:59 +00:00
|
|
|
const char *msg = repo_get_commit_buffer(r, commit, NULL);
|
2007-11-04 19:15:06 +00:00
|
|
|
char *out;
|
|
|
|
|
2013-04-18 23:08:40 +00:00
|
|
|
if (!output_encoding || !*output_encoding) {
|
|
|
|
if (commit_encoding)
|
commit: provide a function to find a header in a buffer
Usually when we parse a commit, we read it line by line and
handle each individual line (e.g., parse_commit and
parse_commit_header). Sometimes, however, we only care
about extracting a single header. Code in this situation is
stuck doing an ad-hoc parse of the commit buffer.
Let's provide a reusable function to locate a header within
the commit. The code is modeled after pretty.c's
get_header, which is used to extract the encoding.
Since some callers may not have the "struct commit" to go
along with the buffer, we drop that parameter. The only
thing lost is a warning for truncated commits, but that's
OK. This shouldn't happen in practice, and even if it does,
there's no particular reason that this function needs to
complain about it. It either finds the header it was asked
for, or it doesn't (and in the latter case, the caller will
typically complain).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-27 07:56:01 +00:00
|
|
|
*commit_encoding = get_header(msg, "encoding");
|
2013-01-26 09:44:06 +00:00
|
|
|
return msg;
|
2013-04-18 23:08:40 +00:00
|
|
|
}
|
commit: provide a function to find a header in a buffer
Usually when we parse a commit, we read it line by line and
handle each individual line (e.g., parse_commit and
parse_commit_header). Sometimes, however, we only care
about extracting a single header. Code in this situation is
stuck doing an ad-hoc parse of the commit buffer.
Let's provide a reusable function to locate a header within
the commit. The code is modeled after pretty.c's
get_header, which is used to extract the encoding.
Since some callers may not have the "struct commit" to go
along with the buffer, we drop that parameter. The only
thing lost is a warning for truncated commits, but that's
OK. This shouldn't happen in practice, and even if it does,
there's no particular reason that this function needs to
complain about it. It either finds the header it was asked
for, or it doesn't (and in the latter case, the caller will
typically complain).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2014-08-27 07:56:01 +00:00
|
|
|
encoding = get_header(msg, "encoding");
|
2013-04-18 23:08:40 +00:00
|
|
|
if (commit_encoding)
|
|
|
|
*commit_encoding = encoding;
|
2007-11-04 19:15:06 +00:00
|
|
|
use_encoding = encoding ? encoding : utf8;
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 09:44:28 +00:00
|
|
|
if (same_encoding(use_encoding, output_encoding)) {
|
|
|
|
/*
|
|
|
|
* No encoding work to be done. If we have no encoding header
|
|
|
|
* at all, then there's nothing to do, and we can return the
|
|
|
|
* message verbatim (whether newly allocated or not).
|
|
|
|
*/
|
|
|
|
if (!encoding)
|
|
|
|
return msg;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise, we still want to munge the encoding header in the
|
|
|
|
* result, which will be done by modifying the buffer. If we
|
|
|
|
* are using a fresh copy, we can reuse it. But if we are using
|
2023-03-28 13:58:57 +00:00
|
|
|
* the cached copy from repo_get_commit_buffer, we need to duplicate it
|
2014-06-10 21:41:39 +00:00
|
|
|
* to avoid munging the cached copy.
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 09:44:28 +00:00
|
|
|
*/
|
2018-11-14 00:12:59 +00:00
|
|
|
if (msg == get_cached_commit_buffer(r, commit, NULL))
|
2014-06-10 21:41:39 +00:00
|
|
|
out = xstrdup(msg);
|
|
|
|
else
|
|
|
|
out = (char *)msg;
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 09:44:28 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
/*
|
|
|
|
* There's actual encoding work to do. Do the reencoding, which
|
|
|
|
* still leaves the header to be replaced in the next step. At
|
|
|
|
* this point, we are done with msg. If we allocated a fresh
|
|
|
|
* copy, we can free it.
|
|
|
|
*/
|
|
|
|
out = reencode_string(msg, output_encoding, use_encoding);
|
2014-06-10 21:41:39 +00:00
|
|
|
if (out)
|
2018-11-14 00:12:59 +00:00
|
|
|
repo_unuse_commit_buffer(r, commit, msg);
|
logmsg_reencode: lazily load missing commit buffers
Usually a commit that makes it to logmsg_reencode will have
been parsed, and the commit->buffer struct member will be
valid. However, some code paths will free commit buffers
after having used them (for example, the log traversal
machinery will do so to keep memory usage down).
Most of the time this is fine; log should only show a commit
once, and then exits. However, there are some code paths
where this does not work. At least two are known:
1. A commit may be shown as part of a regular ref, and
then it may be shown again as part of a submodule diff
(e.g., if a repo contains refs to both the superproject
and subproject).
2. A notes-cache commit may be shown during "log --all",
and then later used to access a textconv cache during a
diff.
Lazily loading in logmsg_reencode does not necessarily catch
all such cases, but it should catch most of them. Users of
the commit buffer tend to be either parsing for structure
(in which they will call parse_commit, and either we will
already have parsed, or we will load commit->buffer lazily
there), or outputting (either to the user, or fetching a
part of the commit message via format_commit_message). In
the latter case, we should always be using logmsg_reencode
anyway (and typically we do so via the pretty-print
machinery).
If there are any cases that this misses, we can fix them up
to use logmsg_reencode (or handle them on a case-by-case
basis if that is inappropriate).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-01-26 09:44:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This replacement actually consumes the buffer we hand it, so we do
|
|
|
|
* not have to worry about freeing the old "out" here.
|
|
|
|
*/
|
2007-11-04 19:15:06 +00:00
|
|
|
if (out)
|
|
|
|
out = replace_encoding_header(out, output_encoding);
|
|
|
|
|
2013-04-18 23:08:40 +00:00
|
|
|
if (!commit_encoding)
|
|
|
|
free(encoding);
|
2013-01-26 09:44:06 +00:00
|
|
|
/*
|
|
|
|
* If the re-encoding failed, out might be NULL here; in that
|
|
|
|
* case we just return the commit message verbatim.
|
|
|
|
*/
|
2021-10-29 20:48:58 +00:00
|
|
|
return out ? out : msg;
|
2013-01-26 09:44:06 +00:00
|
|
|
}
|
|
|
|
|
2013-01-05 21:26:40 +00:00
|
|
|
static int mailmap_name(const char **email, size_t *email_len,
|
|
|
|
const char **name, size_t *name_len)
|
2008-07-11 23:28:18 +00:00
|
|
|
{
|
2008-07-21 18:03:49 +00:00
|
|
|
static struct string_list *mail_map;
|
2008-07-11 23:28:18 +00:00
|
|
|
if (!mail_map) {
|
2021-03-13 16:17:22 +00:00
|
|
|
CALLOC_ARRAY(mail_map, 1);
|
2021-01-12 20:18:06 +00:00
|
|
|
read_mailmap(mail_map);
|
2008-07-11 23:28:18 +00:00
|
|
|
}
|
2009-02-08 14:34:30 +00:00
|
|
|
return mail_map->nr && map_user(mail_map, email, email_len, name, name_len);
|
2008-07-11 23:28:18 +00:00
|
|
|
}
|
|
|
|
|
2008-02-09 14:40:19 +00:00
|
|
|
static size_t format_person_part(struct strbuf *sb, char part,
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
const char *msg, int len,
|
2024-04-05 17:44:59 +00:00
|
|
|
struct date_mode dmode)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2008-02-09 14:40:19 +00:00
|
|
|
/* currently all placeholders have same length */
|
|
|
|
const int placeholder_len = 2;
|
2012-03-11 09:25:43 +00:00
|
|
|
struct ident_split s;
|
2013-01-05 21:26:40 +00:00
|
|
|
const char *name, *mail;
|
|
|
|
size_t maillen, namelen;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2012-03-11 09:25:43 +00:00
|
|
|
if (split_ident_line(&s, msg, len) < 0)
|
2008-02-09 14:40:19 +00:00
|
|
|
goto skip;
|
|
|
|
|
2013-01-05 21:26:40 +00:00
|
|
|
name = s.name_begin;
|
|
|
|
namelen = s.name_end - s.name_begin;
|
|
|
|
mail = s.mail_begin;
|
|
|
|
maillen = s.mail_end - s.mail_begin;
|
|
|
|
|
2019-10-29 12:09:14 +00:00
|
|
|
if (part == 'N' || part == 'E' || part == 'L') /* mailmap lookup */
|
2013-01-05 21:26:40 +00:00
|
|
|
mailmap_name(&mail, &maillen, &name, &namelen);
|
2008-07-11 23:28:18 +00:00
|
|
|
if (part == 'n' || part == 'N') { /* name */
|
2013-01-05 21:26:40 +00:00
|
|
|
strbuf_add(sb, name, namelen);
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2009-02-08 14:34:30 +00:00
|
|
|
if (part == 'e' || part == 'E') { /* email */
|
2013-01-05 21:26:40 +00:00
|
|
|
strbuf_add(sb, mail, maillen);
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2019-10-29 12:09:14 +00:00
|
|
|
if (part == 'l' || part == 'L') { /* local-part */
|
|
|
|
const char *at = memchr(mail, '@', maillen);
|
|
|
|
if (at)
|
|
|
|
maillen = at - mail;
|
|
|
|
strbuf_add(sb, mail, maillen);
|
|
|
|
return placeholder_len;
|
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2012-03-11 09:25:43 +00:00
|
|
|
if (!s.date_begin)
|
2008-02-09 14:40:19 +00:00
|
|
|
goto skip;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2007-11-09 00:49:42 +00:00
|
|
|
if (part == 't') { /* date, UNIX timestamp */
|
2012-03-11 09:25:43 +00:00
|
|
|
strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2007-11-09 00:49:42 +00:00
|
|
|
switch (part) {
|
|
|
|
case 'd': /* date */
|
2013-04-17 18:33:35 +00:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, dmode));
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'D': /* date, RFC2822 style */
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RFC2822)));
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'r': /* date, relative */
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(RELATIVE)));
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2014-08-29 16:58:42 +00:00
|
|
|
case 'i': /* date, ISO 8601-like */
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601)));
|
2008-02-09 14:40:19 +00:00
|
|
|
return placeholder_len;
|
2014-08-29 16:58:42 +00:00
|
|
|
case 'I': /* date, ISO 8601 strict */
|
convert "enum date_mode" into a struct
In preparation for adding date modes that may carry extra
information beyond the mode itself, this patch converts the
date_mode enum into a struct.
Most of the conversion is fairly straightforward; we pass
the struct as a pointer and dereference the type field where
necessary. Locations that declare a date_mode can use a "{}"
constructor. However, the tricky case is where we use the
enum labels as constants, like:
show_date(t, tz, DATE_NORMAL);
Ideally we could say:
show_date(t, tz, &{ DATE_NORMAL });
but of course C does not allow that. Likewise, we cannot
cast the constant to a struct, because we need to pass an
actual address. Our options are basically:
1. Manually add a "struct date_mode d = { DATE_NORMAL }"
definition to each caller, and pass "&d". This makes
the callers uglier, because they sometimes do not even
have their own scope (e.g., they are inside a switch
statement).
2. Provide a pre-made global "date_normal" struct that can
be passed by address. We'd also need "date_rfc2822",
"date_iso8601", and so forth. But at least the ugliness
is defined in one place.
3. Provide a wrapper that generates the correct struct on
the fly. The big downside is that we end up pointing to
a single global, which makes our wrapper non-reentrant.
But show_date is already not reentrant, so it does not
matter.
This patch implements 3, along with a minor macro to keep
the size of the callers sane.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-06-25 16:55:02 +00:00
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(ISO8601_STRICT)));
|
2014-08-29 16:58:42 +00:00
|
|
|
return placeholder_len;
|
2021-04-25 10:41:45 +00:00
|
|
|
case 'h': /* date, human */
|
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(HUMAN)));
|
|
|
|
return placeholder_len;
|
2019-11-20 00:51:21 +00:00
|
|
|
case 's':
|
|
|
|
strbuf_addstr(sb, show_ident_date(&s, DATE_MODE(SHORT)));
|
|
|
|
return placeholder_len;
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2008-02-09 14:40:19 +00:00
|
|
|
|
|
|
|
skip:
|
|
|
|
/*
|
2012-03-11 09:25:43 +00:00
|
|
|
* reading from either a bogus commit, or a reflog entry with
|
|
|
|
* %gn, %ge, etc.; 'sb' cannot be updated, but we still need
|
|
|
|
* to compute a valid return value.
|
2008-02-09 14:40:19 +00:00
|
|
|
*/
|
|
|
|
if (part == 'n' || part == 'e' || part == 't' || part == 'd'
|
|
|
|
|| part == 'D' || part == 'r' || part == 'i')
|
|
|
|
return placeholder_len;
|
|
|
|
|
|
|
|
return 0; /* unknown placeholder */
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
2007-11-10 11:14:20 +00:00
|
|
|
struct chunk {
|
|
|
|
size_t off;
|
|
|
|
size_t len;
|
|
|
|
};
|
|
|
|
|
2013-04-18 23:08:50 +00:00
|
|
|
enum flush_type {
|
|
|
|
no_flush,
|
|
|
|
flush_right,
|
|
|
|
flush_left,
|
2013-04-18 23:08:52 +00:00
|
|
|
flush_left_and_steal,
|
2013-04-18 23:08:50 +00:00
|
|
|
flush_both
|
|
|
|
};
|
|
|
|
|
2013-04-18 23:08:51 +00:00
|
|
|
enum trunc_type {
|
|
|
|
trunc_none,
|
|
|
|
trunc_left,
|
|
|
|
trunc_middle,
|
|
|
|
trunc_right
|
|
|
|
};
|
|
|
|
|
2007-11-10 11:14:20 +00:00
|
|
|
struct format_commit_context {
|
pretty: lazy-load commit data when expanding user-format
When we expand a user-format, we try to avoid work that isn't necessary
for the output. For instance, we don't bother parsing the commit header
until we know we need the author, subject, etc.
But we do always load the commit object's contents from disk, even if
the format doesn't require it (e.g., just "%H"). Traditionally this
didn't matter much, because we'd have loaded it as part of the traversal
anyway, and we'd typically have those bytes attached to the commit
struct (or these days, cached in a commit-slab).
But when we have a commit-graph, we might easily get to the point of
pretty-printing a commit without ever having looked at the actual object
contents. We should push off that load (and reencoding) until we're
certain that it's needed.
I think the results of p4205 show the advantage pretty clearly (we serve
parent and tree oids out of the commit struct itself, so they benefit as
well):
# using git.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 0.40(0.39+0.01) 0.03(0.02+0.01) -92.5%
4205.2: log with %h 0.45(0.44+0.01) 0.09(0.09+0.00) -80.0%
4205.3: log with %T 0.40(0.39+0.00) 0.04(0.04+0.00) -90.0%
4205.4: log with %t 0.46(0.46+0.00) 0.09(0.08+0.01) -80.4%
4205.5: log with %P 0.39(0.39+0.00) 0.03(0.03+0.00) -92.3%
4205.6: log with %p 0.46(0.46+0.00) 0.10(0.09+0.00) -78.3%
4205.7: log with %h-%h-%h 0.52(0.51+0.01) 0.15(0.14+0.00) -71.2%
4205.8: log with %an-%ae-%s 0.42(0.41+0.00) 0.42(0.41+0.01) +0.0%
# using linux.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 7.12(6.97+0.14) 0.76(0.65+0.11) -89.3%
4205.2: log with %h 7.35(7.19+0.16) 1.30(1.19+0.11) -82.3%
4205.3: log with %T 7.58(7.42+0.15) 1.02(0.94+0.08) -86.5%
4205.4: log with %t 8.05(7.89+0.15) 1.55(1.41+0.13) -80.7%
4205.5: log with %P 7.12(7.01+0.10) 0.76(0.69+0.07) -89.3%
4205.6: log with %p 7.38(7.27+0.10) 1.32(1.20+0.12) -82.1%
4205.7: log with %h-%h-%h 7.81(7.67+0.13) 1.79(1.67+0.12) -77.1%
4205.8: log with %an-%ae-%s 7.90(7.74+0.15) 7.81(7.66+0.15) -1.1%
I added the final test to show where we don't improve (the 1% there is
just lucky noise), but also as a regression test to make sure we're not
doing anything stupid like loading the commit multiple times when there
are several placeholders that need it.
Reported-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-28 19:57:39 +00:00
|
|
|
struct repository *repository;
|
2007-11-10 11:14:20 +00:00
|
|
|
const struct commit *commit;
|
2009-10-19 15:48:08 +00:00
|
|
|
const struct pretty_print_context *pretty_ctx;
|
2008-12-27 00:49:21 +00:00
|
|
|
unsigned commit_header_parsed:1;
|
|
|
|
unsigned commit_message_parsed:1;
|
2013-03-31 16:00:14 +00:00
|
|
|
struct signature_check signature_check;
|
2013-04-18 23:08:50 +00:00
|
|
|
enum flush_type flush_type;
|
2013-04-18 23:08:51 +00:00
|
|
|
enum trunc_type truncate;
|
2014-06-10 21:39:30 +00:00
|
|
|
const char *message;
|
2013-04-18 23:08:41 +00:00
|
|
|
char *commit_encoding;
|
2009-10-17 21:04:19 +00:00
|
|
|
size_t width, indent1, indent2;
|
2013-04-18 23:08:49 +00:00
|
|
|
int auto_color;
|
2013-04-18 23:08:50 +00:00
|
|
|
int padding;
|
2007-11-10 11:14:20 +00:00
|
|
|
|
|
|
|
/* These offsets are relative to the start of the commit message. */
|
|
|
|
struct chunk author;
|
|
|
|
struct chunk committer;
|
2008-12-27 00:49:21 +00:00
|
|
|
size_t message_off;
|
|
|
|
size_t subject_off;
|
2007-11-10 11:14:20 +00:00
|
|
|
size_t body_off;
|
2007-11-10 11:18:26 +00:00
|
|
|
|
|
|
|
/* The following ones are relative to the result struct strbuf. */
|
2009-10-17 21:04:19 +00:00
|
|
|
size_t wrap_start;
|
2007-11-10 11:14:20 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static void parse_commit_header(struct format_commit_context *context)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2010-11-02 19:59:08 +00:00
|
|
|
const char *msg = context->message;
|
2007-11-04 19:15:06 +00:00
|
|
|
int i;
|
2007-11-10 11:14:20 +00:00
|
|
|
|
2008-12-27 00:49:21 +00:00
|
|
|
for (i = 0; msg[i]; i++) {
|
2014-10-04 18:54:50 +00:00
|
|
|
const char *name;
|
2007-11-10 11:14:20 +00:00
|
|
|
int eol;
|
|
|
|
for (eol = i; msg[eol] && msg[eol] != '\n'; eol++)
|
|
|
|
; /* do nothing */
|
|
|
|
|
|
|
|
if (i == eol) {
|
2008-12-27 00:49:21 +00:00
|
|
|
break;
|
2014-10-04 18:54:50 +00:00
|
|
|
} else if (skip_prefix(msg + i, "author ", &name)) {
|
|
|
|
context->author.off = name - msg;
|
|
|
|
context->author.len = msg + eol - name;
|
|
|
|
} else if (skip_prefix(msg + i, "committer ", &name)) {
|
|
|
|
context->committer.off = name - msg;
|
|
|
|
context->committer.len = msg + eol - name;
|
2007-11-10 11:14:20 +00:00
|
|
|
}
|
|
|
|
i = eol;
|
|
|
|
}
|
2008-12-27 00:49:21 +00:00
|
|
|
context->message_off = i;
|
2007-11-10 11:14:20 +00:00
|
|
|
context->commit_header_parsed = 1;
|
|
|
|
}
|
|
|
|
|
2009-03-23 02:14:01 +00:00
|
|
|
static int istitlechar(char c)
|
|
|
|
{
|
|
|
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
|
|
|
|
(c >= '0' && c <= '9') || c == '.' || c == '_';
|
|
|
|
}
|
|
|
|
|
2020-08-21 21:41:49 +00:00
|
|
|
void format_sanitized_subject(struct strbuf *sb, const char *msg, size_t len)
|
2009-03-23 02:14:01 +00:00
|
|
|
{
|
|
|
|
size_t trimlen;
|
2009-03-31 23:24:38 +00:00
|
|
|
size_t start_len = sb->len;
|
2009-03-23 02:14:01 +00:00
|
|
|
int space = 2;
|
2020-08-21 21:41:49 +00:00
|
|
|
int i;
|
2009-03-23 02:14:01 +00:00
|
|
|
|
2020-08-21 21:41:49 +00:00
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
if (istitlechar(msg[i])) {
|
2009-03-23 02:14:01 +00:00
|
|
|
if (space == 1)
|
|
|
|
strbuf_addch(sb, '-');
|
|
|
|
space = 0;
|
2020-08-21 21:41:49 +00:00
|
|
|
strbuf_addch(sb, msg[i]);
|
|
|
|
if (msg[i] == '.')
|
|
|
|
while (msg[i+1] == '.')
|
|
|
|
i++;
|
2009-03-23 02:14:01 +00:00
|
|
|
} else
|
|
|
|
space |= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* trim any trailing '.' or '-' characters */
|
|
|
|
trimlen = 0;
|
2009-03-31 23:24:38 +00:00
|
|
|
while (sb->len - trimlen > start_len &&
|
|
|
|
(sb->buf[sb->len - 1 - trimlen] == '.'
|
|
|
|
|| sb->buf[sb->len - 1 - trimlen] == '-'))
|
2009-03-23 02:14:01 +00:00
|
|
|
trimlen++;
|
|
|
|
strbuf_remove(sb, sb->len - trimlen, trimlen);
|
|
|
|
}
|
|
|
|
|
2009-01-06 20:41:06 +00:00
|
|
|
const char *format_subject(struct strbuf *sb, const char *msg,
|
|
|
|
const char *line_separator)
|
2008-12-27 00:39:35 +00:00
|
|
|
{
|
|
|
|
int first = 1;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
const char *line = msg;
|
|
|
|
int linelen = get_one_line(line);
|
|
|
|
|
|
|
|
msg += linelen;
|
2016-06-22 20:20:16 +00:00
|
|
|
if (!linelen || is_blank_line(line, &linelen))
|
2008-12-27 00:39:35 +00:00
|
|
|
break;
|
|
|
|
|
2008-12-27 00:49:21 +00:00
|
|
|
if (!sb)
|
|
|
|
continue;
|
2008-12-27 00:39:35 +00:00
|
|
|
strbuf_grow(sb, linelen + 2);
|
|
|
|
if (!first)
|
|
|
|
strbuf_addstr(sb, line_separator);
|
|
|
|
strbuf_add(sb, line, linelen);
|
|
|
|
first = 0;
|
|
|
|
}
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
2008-12-27 00:49:21 +00:00
|
|
|
static void parse_commit_message(struct format_commit_context *c)
|
|
|
|
{
|
2010-11-02 19:59:08 +00:00
|
|
|
const char *msg = c->message + c->message_off;
|
|
|
|
const char *start = c->message;
|
2008-12-27 00:49:21 +00:00
|
|
|
|
2016-06-22 20:20:16 +00:00
|
|
|
msg = skip_blank_lines(msg);
|
2008-12-27 00:49:21 +00:00
|
|
|
c->subject_off = msg - start;
|
|
|
|
|
|
|
|
msg = format_subject(NULL, msg, NULL);
|
2016-06-22 20:20:16 +00:00
|
|
|
msg = skip_blank_lines(msg);
|
2008-12-27 00:49:21 +00:00
|
|
|
c->body_off = msg - start;
|
|
|
|
|
|
|
|
c->commit_message_parsed = 1;
|
|
|
|
}
|
|
|
|
|
2009-10-17 21:04:19 +00:00
|
|
|
static void strbuf_wrap(struct strbuf *sb, size_t pos,
|
|
|
|
size_t width, size_t indent1, size_t indent2)
|
|
|
|
{
|
|
|
|
struct strbuf tmp = STRBUF_INIT;
|
|
|
|
|
|
|
|
if (pos)
|
|
|
|
strbuf_add(&tmp, sb->buf, pos);
|
|
|
|
strbuf_add_wrapped_text(&tmp, sb->buf + pos,
|
2022-12-01 14:46:49 +00:00
|
|
|
cast_size_t_to_int(indent1),
|
|
|
|
cast_size_t_to_int(indent2),
|
|
|
|
cast_size_t_to_int(width));
|
2009-10-17 21:04:19 +00:00
|
|
|
strbuf_swap(&tmp, sb);
|
|
|
|
strbuf_release(&tmp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void rewrap_message_tail(struct strbuf *sb,
|
|
|
|
struct format_commit_context *c,
|
|
|
|
size_t new_width, size_t new_indent1,
|
|
|
|
size_t new_indent2)
|
|
|
|
{
|
|
|
|
if (c->width == new_width && c->indent1 == new_indent1 &&
|
|
|
|
c->indent2 == new_indent2)
|
|
|
|
return;
|
2009-11-08 01:04:21 +00:00
|
|
|
if (c->wrap_start < sb->len)
|
2009-10-17 21:04:19 +00:00
|
|
|
strbuf_wrap(sb, c->wrap_start, c->width, c->indent1, c->indent2);
|
|
|
|
c->wrap_start = sb->len;
|
|
|
|
c->width = new_width;
|
|
|
|
c->indent1 = new_indent1;
|
|
|
|
c->indent2 = new_indent2;
|
|
|
|
}
|
|
|
|
|
2011-12-16 11:40:24 +00:00
|
|
|
static int format_reflog_person(struct strbuf *sb,
|
|
|
|
char part,
|
|
|
|
struct reflog_walk_info *log,
|
2024-04-05 17:44:59 +00:00
|
|
|
struct date_mode dmode)
|
2011-12-16 11:40:24 +00:00
|
|
|
{
|
|
|
|
const char *ident;
|
|
|
|
|
|
|
|
if (!log)
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
ident = get_reflog_ident(log);
|
|
|
|
if (!ident)
|
|
|
|
return 2;
|
|
|
|
|
|
|
|
return format_person_part(sb, part, ident, strlen(ident), dmode);
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:48 +00:00
|
|
|
static size_t parse_color(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
2014-10-04 18:54:50 +00:00
|
|
|
const char *rest = placeholder;
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
const char *basic_color = NULL;
|
2014-10-04 18:54:50 +00:00
|
|
|
|
2013-04-18 23:08:48 +00:00
|
|
|
if (placeholder[1] == '(') {
|
|
|
|
const char *begin = placeholder + 2;
|
|
|
|
const char *end = strchr(begin, ')');
|
|
|
|
char color[COLOR_MAXLEN];
|
|
|
|
|
|
|
|
if (!end)
|
|
|
|
return 0;
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(begin, "auto,", &begin)) {
|
2013-04-18 23:08:48 +00:00
|
|
|
if (!want_color(c->pretty_ctx->color))
|
|
|
|
return end - placeholder + 1;
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
} else if (skip_prefix(begin, "always,", &begin)) {
|
|
|
|
/* nothing to do; we do not respect want_color at all */
|
|
|
|
} else {
|
|
|
|
/* the default is the same as "auto" */
|
|
|
|
if (!want_color(c->pretty_ctx->color))
|
|
|
|
return end - placeholder + 1;
|
2013-04-18 23:08:48 +00:00
|
|
|
}
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
|
2014-10-07 19:33:09 +00:00
|
|
|
if (color_parse_mem(begin, end - begin, color) < 0)
|
|
|
|
die(_("unable to parse --pretty format"));
|
2013-04-18 23:08:48 +00:00
|
|
|
strbuf_addstr(sb, color);
|
|
|
|
return end - placeholder + 1;
|
|
|
|
}
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We handle things like "%C(red)" above; for historical reasons, there
|
|
|
|
* are a few colors that can be specified without parentheses (and
|
|
|
|
* they cannot support things like "auto" or "always" at all).
|
|
|
|
*/
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(placeholder + 1, "red", &rest))
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
basic_color = GIT_COLOR_RED;
|
2014-10-04 18:54:50 +00:00
|
|
|
else if (skip_prefix(placeholder + 1, "green", &rest))
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
basic_color = GIT_COLOR_GREEN;
|
2014-10-04 18:54:50 +00:00
|
|
|
else if (skip_prefix(placeholder + 1, "blue", &rest))
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
basic_color = GIT_COLOR_BLUE;
|
2014-10-04 18:54:50 +00:00
|
|
|
else if (skip_prefix(placeholder + 1, "reset", &rest))
|
pretty: respect color settings for %C placeholders
The color placeholders have traditionally been
unconditional, showing colors even when git is not otherwise
configured to do so. This was not so bad for their original
use, which was on the command-line (and the user could
decide at that moment whether to add colors or not). But
these days we have configured formats via pretty.*, and
those should operate correctly in multiple contexts.
In 3082517 (log --format: teach %C(auto,black) to respect
color config, 2012-12-17), we gave an extended placeholder
that could be used to accomplish this. But it's rather
clunky to use, because you have to specify it individually
for each color (and their matching resets) in the format.
We shied away from just switching the default to auto,
because it is technically breaking backwards compatibility.
However, there's not really a use case for unconditional
colors. The most plausible reason you would want them is to
redirect "git log" output to a file. But there, the right
answer is --color=always, as it does the right thing both
with custom user-format colors and git-generated colors.
So let's switch to the more useful default. In the
off-chance that somebody really does find a use for
unconditional colors without wanting to enable the rest of
git's colors, we provide a new %C(always,...) to enable the
old behavior. And we can remind them of --color=always in
the documentation.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-07-13 15:08:46 +00:00
|
|
|
basic_color = GIT_COLOR_RESET;
|
|
|
|
|
|
|
|
if (basic_color && want_color(c->pretty_ctx->color))
|
|
|
|
strbuf_addstr(sb, basic_color);
|
|
|
|
|
2014-10-04 18:54:50 +00:00
|
|
|
return rest - placeholder;
|
2013-04-18 23:08:48 +00:00
|
|
|
}
|
|
|
|
|
2019-03-20 08:16:42 +00:00
|
|
|
static size_t parse_padding_placeholder(const char *placeholder,
|
2013-04-18 23:08:50 +00:00
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
|
|
|
const char *ch = placeholder;
|
|
|
|
enum flush_type flush_type;
|
|
|
|
int to_column = 0;
|
|
|
|
|
|
|
|
switch (*ch++) {
|
|
|
|
case '<':
|
|
|
|
flush_type = flush_right;
|
|
|
|
break;
|
|
|
|
case '>':
|
|
|
|
if (*ch == '<') {
|
|
|
|
flush_type = flush_both;
|
|
|
|
ch++;
|
2013-04-18 23:08:52 +00:00
|
|
|
} else if (*ch == '>') {
|
|
|
|
flush_type = flush_left_and_steal;
|
|
|
|
ch++;
|
2013-04-18 23:08:50 +00:00
|
|
|
} else
|
|
|
|
flush_type = flush_left;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the next value means "wide enough to that column" */
|
|
|
|
if (*ch == '|') {
|
|
|
|
to_column = 1;
|
|
|
|
ch++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*ch == '(') {
|
|
|
|
const char *start = ch + 1;
|
2013-04-18 23:08:51 +00:00
|
|
|
const char *end = start + strcspn(start, ",)");
|
2013-04-18 23:08:50 +00:00
|
|
|
char *next;
|
|
|
|
int width;
|
2022-12-01 14:46:34 +00:00
|
|
|
if (!*end || end == start)
|
2013-04-18 23:08:50 +00:00
|
|
|
return 0;
|
2016-06-16 13:18:38 +00:00
|
|
|
width = strtol(start, &next, 10);
|
2022-12-01 14:47:23 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We need to limit the amount of padding, or otherwise this
|
|
|
|
* would allow the user to pad the buffer by arbitrarily many
|
|
|
|
* bytes and thus cause resource exhaustion.
|
|
|
|
*/
|
|
|
|
if (width < -FORMATTING_LIMIT || width > FORMATTING_LIMIT)
|
|
|
|
return 0;
|
|
|
|
|
2013-04-18 23:08:50 +00:00
|
|
|
if (next == start || width == 0)
|
|
|
|
return 0;
|
2016-06-16 13:18:38 +00:00
|
|
|
if (width < 0) {
|
|
|
|
if (to_column)
|
|
|
|
width += term_columns();
|
|
|
|
if (width < 0)
|
|
|
|
return 0;
|
|
|
|
}
|
2013-04-18 23:08:50 +00:00
|
|
|
c->padding = to_column ? -width : width;
|
|
|
|
c->flush_type = flush_type;
|
2013-04-18 23:08:51 +00:00
|
|
|
|
|
|
|
if (*end == ',') {
|
|
|
|
start = end + 1;
|
|
|
|
end = strchr(start, ')');
|
|
|
|
if (!end || end == start)
|
|
|
|
return 0;
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(start, "trunc)"))
|
2013-04-18 23:08:51 +00:00
|
|
|
c->truncate = trunc_right;
|
2013-11-30 20:55:40 +00:00
|
|
|
else if (starts_with(start, "ltrunc)"))
|
2013-04-18 23:08:51 +00:00
|
|
|
c->truncate = trunc_left;
|
2013-11-30 20:55:40 +00:00
|
|
|
else if (starts_with(start, "mtrunc)"))
|
2013-04-18 23:08:51 +00:00
|
|
|
c->truncate = trunc_middle;
|
|
|
|
else
|
|
|
|
return 0;
|
|
|
|
} else
|
|
|
|
c->truncate = trunc_none;
|
|
|
|
|
2013-04-18 23:08:50 +00:00
|
|
|
return end - placeholder + 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-01-29 06:49:00 +00:00
|
|
|
static int match_placeholder_arg_value(const char *to_parse, const char *candidate,
|
|
|
|
const char **end, const char **valuestart,
|
|
|
|
size_t *valuelen)
|
2017-10-01 16:18:47 +00:00
|
|
|
{
|
|
|
|
const char *p;
|
|
|
|
|
|
|
|
if (!(skip_prefix(to_parse, candidate, &p)))
|
|
|
|
return 0;
|
2019-01-29 06:49:00 +00:00
|
|
|
if (valuestart) {
|
|
|
|
if (*p == '=') {
|
|
|
|
*valuestart = p + 1;
|
|
|
|
*valuelen = strcspn(*valuestart, ",)");
|
|
|
|
p = *valuestart + *valuelen;
|
|
|
|
} else {
|
|
|
|
if (*p != ',' && *p != ')')
|
|
|
|
return 0;
|
|
|
|
*valuestart = NULL;
|
|
|
|
*valuelen = 0;
|
|
|
|
}
|
|
|
|
}
|
2017-10-01 16:18:47 +00:00
|
|
|
if (*p == ',') {
|
|
|
|
*end = p + 1;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (*p == ')') {
|
|
|
|
*end = p;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-01-29 06:49:00 +00:00
|
|
|
static int match_placeholder_bool_arg(const char *to_parse, const char *candidate,
|
|
|
|
const char **end, int *val)
|
|
|
|
{
|
|
|
|
const char *argval;
|
|
|
|
char *strval;
|
|
|
|
size_t arglen;
|
|
|
|
int v;
|
|
|
|
|
|
|
|
if (!match_placeholder_arg_value(to_parse, candidate, end, &argval, &arglen))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!argval) {
|
|
|
|
*val = 1;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
strval = xstrndup(argval, arglen);
|
|
|
|
v = git_parse_maybe_bool(strval);
|
|
|
|
free(strval);
|
|
|
|
|
|
|
|
if (v == -1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*val = v;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2019-01-28 21:33:34 +00:00
|
|
|
static int format_trailer_match_cb(const struct strbuf *key, void *ud)
|
|
|
|
{
|
|
|
|
const struct string_list *list = ud;
|
|
|
|
const struct string_list_item *item;
|
|
|
|
|
|
|
|
for_each_string_list_item (item, list) {
|
|
|
|
if (key->len == (uintptr_t)item->util &&
|
|
|
|
!strncasecmp(item->string, key->buf, key->len))
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-08-20 18:50:07 +00:00
|
|
|
static struct strbuf *expand_string_arg(struct strbuf *sb,
|
|
|
|
const char *argval, size_t arglen)
|
2023-06-17 20:40:57 +00:00
|
|
|
{
|
|
|
|
char *fmt = xstrndup(argval, arglen);
|
2023-06-17 20:43:17 +00:00
|
|
|
const char *format = fmt;
|
2023-06-17 20:40:57 +00:00
|
|
|
|
|
|
|
strbuf_reset(sb);
|
2023-06-17 20:43:17 +00:00
|
|
|
while (strbuf_expand_step(sb, &format)) {
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
if (skip_prefix(format, "%", &format))
|
|
|
|
strbuf_addch(sb, '%');
|
2023-06-17 20:44:00 +00:00
|
|
|
else if ((len = strbuf_expand_literal(sb, format)))
|
2023-06-17 20:43:17 +00:00
|
|
|
format += len;
|
|
|
|
else
|
|
|
|
strbuf_addch(sb, '%');
|
|
|
|
}
|
2023-06-17 20:40:57 +00:00
|
|
|
free(fmt);
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
2021-02-13 01:52:41 +00:00
|
|
|
int format_set_trailers_options(struct process_trailer_options *opts,
|
|
|
|
struct string_list *filter_list,
|
|
|
|
struct strbuf *sepbuf,
|
|
|
|
struct strbuf *kvsepbuf,
|
2021-02-13 01:52:42 +00:00
|
|
|
const char **arg,
|
|
|
|
char **invalid_arg)
|
2021-02-13 01:52:41 +00:00
|
|
|
{
|
|
|
|
for (;;) {
|
|
|
|
const char *argval;
|
|
|
|
size_t arglen;
|
|
|
|
|
2021-02-13 01:52:42 +00:00
|
|
|
if (**arg == ')')
|
|
|
|
break;
|
|
|
|
|
2021-02-13 01:52:41 +00:00
|
|
|
if (match_placeholder_arg_value(*arg, "key", arg, &argval, &arglen)) {
|
|
|
|
uintptr_t len = arglen;
|
|
|
|
|
|
|
|
if (!argval)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (len && argval[len - 1] == ':')
|
|
|
|
len--;
|
|
|
|
string_list_append(filter_list, argval)->util = (char *)len;
|
|
|
|
|
|
|
|
opts->filter = format_trailer_match_cb;
|
|
|
|
opts->filter_data = filter_list;
|
|
|
|
opts->only_trailers = 1;
|
|
|
|
} else if (match_placeholder_arg_value(*arg, "separator", arg, &argval, &arglen)) {
|
2023-08-20 18:50:07 +00:00
|
|
|
opts->separator = expand_string_arg(sepbuf, argval, arglen);
|
2021-02-13 01:52:41 +00:00
|
|
|
} else if (match_placeholder_arg_value(*arg, "key_value_separator", arg, &argval, &arglen)) {
|
2023-08-20 18:50:07 +00:00
|
|
|
opts->key_value_separator = expand_string_arg(kvsepbuf, argval, arglen);
|
2021-02-13 01:52:41 +00:00
|
|
|
} else if (!match_placeholder_bool_arg(*arg, "only", arg, &opts->only_trailers) &&
|
|
|
|
!match_placeholder_bool_arg(*arg, "unfold", arg, &opts->unfold) &&
|
|
|
|
!match_placeholder_bool_arg(*arg, "keyonly", arg, &opts->key_only) &&
|
2021-02-13 01:52:42 +00:00
|
|
|
!match_placeholder_bool_arg(*arg, "valueonly", arg, &opts->value_only)) {
|
|
|
|
if (invalid_arg) {
|
|
|
|
size_t len = strcspn(*arg, ",)");
|
|
|
|
*invalid_arg = xstrndup(*arg, len);
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
2021-02-13 01:52:41 +00:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-14 10:10:57 +00:00
|
|
|
static size_t parse_describe_args(const char *start, struct strvec *args)
|
|
|
|
{
|
2021-10-31 17:15:08 +00:00
|
|
|
struct {
|
|
|
|
char *name;
|
|
|
|
enum {
|
2021-10-31 17:15:09 +00:00
|
|
|
DESCRIBE_ARG_BOOL,
|
2021-10-31 17:15:10 +00:00
|
|
|
DESCRIBE_ARG_INTEGER,
|
2021-10-31 17:15:08 +00:00
|
|
|
DESCRIBE_ARG_STRING,
|
|
|
|
} type;
|
|
|
|
} option[] = {
|
2021-10-31 17:15:09 +00:00
|
|
|
{ "tags", DESCRIBE_ARG_BOOL},
|
2021-10-31 17:15:10 +00:00
|
|
|
{ "abbrev", DESCRIBE_ARG_INTEGER },
|
2021-10-31 17:15:08 +00:00
|
|
|
{ "exclude", DESCRIBE_ARG_STRING },
|
|
|
|
{ "match", DESCRIBE_ARG_STRING },
|
|
|
|
};
|
2021-02-14 10:10:57 +00:00
|
|
|
const char *arg = start;
|
|
|
|
|
|
|
|
for (;;) {
|
2021-10-31 17:15:08 +00:00
|
|
|
int found = 0;
|
2021-02-14 10:10:57 +00:00
|
|
|
const char *argval;
|
|
|
|
size_t arglen = 0;
|
2021-10-31 17:15:09 +00:00
|
|
|
int optval = 0;
|
2021-02-14 10:10:57 +00:00
|
|
|
int i;
|
|
|
|
|
2021-10-31 17:15:08 +00:00
|
|
|
for (i = 0; !found && i < ARRAY_SIZE(option); i++) {
|
|
|
|
switch (option[i].type) {
|
2021-10-31 17:15:09 +00:00
|
|
|
case DESCRIBE_ARG_BOOL:
|
|
|
|
if (match_placeholder_bool_arg(arg, option[i].name, &arg, &optval)) {
|
|
|
|
if (optval)
|
|
|
|
strvec_pushf(args, "--%s", option[i].name);
|
|
|
|
else
|
|
|
|
strvec_pushf(args, "--no-%s", option[i].name);
|
|
|
|
found = 1;
|
|
|
|
}
|
|
|
|
break;
|
2021-10-31 17:15:10 +00:00
|
|
|
case DESCRIBE_ARG_INTEGER:
|
|
|
|
if (match_placeholder_arg_value(arg, option[i].name, &arg,
|
|
|
|
&argval, &arglen)) {
|
|
|
|
char *endptr;
|
|
|
|
if (!arglen)
|
|
|
|
return 0;
|
|
|
|
strtol(argval, &endptr, 10);
|
|
|
|
if (endptr - argval != arglen)
|
|
|
|
return 0;
|
|
|
|
strvec_pushf(args, "--%s=%.*s", option[i].name, (int)arglen, argval);
|
|
|
|
found = 1;
|
|
|
|
}
|
|
|
|
break;
|
2021-10-31 17:15:08 +00:00
|
|
|
case DESCRIBE_ARG_STRING:
|
|
|
|
if (match_placeholder_arg_value(arg, option[i].name, &arg,
|
|
|
|
&argval, &arglen)) {
|
|
|
|
if (!arglen)
|
|
|
|
return 0;
|
|
|
|
strvec_pushf(args, "--%s=%.*s", option[i].name, (int)arglen, argval);
|
|
|
|
found = 1;
|
|
|
|
}
|
2021-02-14 10:10:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2021-10-31 17:15:08 +00:00
|
|
|
if (!found)
|
2021-02-14 10:10:57 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
}
|
|
|
|
return arg - start;
|
|
|
|
}
|
|
|
|
|
2023-08-20 18:50:07 +00:00
|
|
|
|
|
|
|
static int parse_decoration_option(const char **arg,
|
|
|
|
const char *name,
|
|
|
|
char **opt)
|
|
|
|
{
|
|
|
|
const char *argval;
|
|
|
|
size_t arglen;
|
|
|
|
|
|
|
|
if (match_placeholder_arg_value(*arg, name, arg, &argval, &arglen)) {
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
|
|
|
|
expand_string_arg(&sb, argval, arglen);
|
|
|
|
*opt = strbuf_detach(&sb, NULL);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void parse_decoration_options(const char **arg,
|
|
|
|
struct decoration_options *opts)
|
|
|
|
{
|
|
|
|
while (parse_decoration_option(arg, "prefix", &opts->prefix) ||
|
|
|
|
parse_decoration_option(arg, "suffix", &opts->suffix) ||
|
2023-08-20 18:50:08 +00:00
|
|
|
parse_decoration_option(arg, "separator", &opts->separator) ||
|
|
|
|
parse_decoration_option(arg, "pointer", &opts->pointer) ||
|
|
|
|
parse_decoration_option(arg, "tag", &opts->tag))
|
2023-08-20 18:50:07 +00:00
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void free_decoration_options(const struct decoration_options *opts)
|
|
|
|
{
|
|
|
|
free(opts->prefix);
|
|
|
|
free(opts->suffix);
|
|
|
|
free(opts->separator);
|
2023-08-20 18:50:08 +00:00
|
|
|
free(opts->pointer);
|
|
|
|
free(opts->tag);
|
2023-08-20 18:50:07 +00:00
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:47 +00:00
|
|
|
static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
2009-10-05 06:43:32 +00:00
|
|
|
void *context)
|
2007-11-10 11:14:20 +00:00
|
|
|
{
|
|
|
|
struct format_commit_context *c = context;
|
|
|
|
const struct commit *commit = c->commit;
|
2010-11-02 19:59:08 +00:00
|
|
|
const char *msg = c->message;
|
2007-11-10 11:14:20 +00:00
|
|
|
struct commit_list *p;
|
2020-08-21 21:41:49 +00:00
|
|
|
const char *arg, *eol;
|
2019-01-28 21:33:36 +00:00
|
|
|
size_t res;
|
2019-01-11 06:30:46 +00:00
|
|
|
char **slot;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
|
|
|
/* these are independent of the commit */
|
2023-06-17 20:44:00 +00:00
|
|
|
res = strbuf_expand_literal(sb, placeholder);
|
2019-01-28 21:33:36 +00:00
|
|
|
if (res)
|
|
|
|
return res;
|
|
|
|
|
2007-11-09 00:49:42 +00:00
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 'C':
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(placeholder + 1, "(auto)")) {
|
2016-05-27 03:46:10 +00:00
|
|
|
c->auto_color = want_color(c->pretty_ctx->color);
|
2016-09-29 18:13:05 +00:00
|
|
|
if (c->auto_color && sb->len)
|
2016-09-17 18:25:24 +00:00
|
|
|
strbuf_addstr(sb, GIT_COLOR_RESET);
|
2013-04-18 23:08:49 +00:00
|
|
|
return 7; /* consumed 7 bytes, "C(auto)" */
|
|
|
|
} else {
|
|
|
|
int ret = parse_color(sb, placeholder, c);
|
|
|
|
if (ret)
|
|
|
|
c->auto_color = 0;
|
|
|
|
/*
|
|
|
|
* Otherwise, we decided to treat %C<unknown>
|
|
|
|
* as a literal string, and the previous
|
|
|
|
* %C(auto) is still valid.
|
|
|
|
*/
|
|
|
|
return ret;
|
expand --pretty=format color options
Currently, the only colors available to --pretty=format
users are red, green, and blue. Rather than expand it with a
few new colors, this patch makes the usual config color
syntax available, including more colors, backgrounds, and
attributes.
Because colors are no longer bounded to a single word (e.g.,
%Cred), this uses a more advanced syntax that features a
beginning and end delimiter (but the old syntax still
works). So you can now do:
git log --pretty=tformat:'%C(yellow)%h%C(reset) %s'
to emulate --pretty=oneline, or even
git log --pretty=tformat:'%C(cyan magenta bold)%s%C(reset)'
if you want to relive the awesomeness of 4-color CGA.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-17 15:38:46 +00:00
|
|
|
}
|
2009-10-17 21:04:19 +00:00
|
|
|
case 'w':
|
|
|
|
if (placeholder[1] == '(') {
|
|
|
|
unsigned long width = 0, indent1 = 0, indent2 = 0;
|
|
|
|
char *next;
|
|
|
|
const char *start = placeholder + 2;
|
|
|
|
const char *end = strchr(start, ')');
|
|
|
|
if (!end)
|
|
|
|
return 0;
|
|
|
|
if (end > start) {
|
|
|
|
width = strtoul(start, &next, 10);
|
|
|
|
if (*next == ',') {
|
|
|
|
indent1 = strtoul(next + 1, &next, 10);
|
|
|
|
if (*next == ',') {
|
|
|
|
indent2 = strtoul(next + 1,
|
|
|
|
&next, 10);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (*next != ')')
|
|
|
|
return 0;
|
|
|
|
}
|
2022-12-01 14:47:23 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We need to limit the format here as it allows the
|
|
|
|
* user to prepend arbitrarily many bytes to the buffer
|
|
|
|
* when rewrapping.
|
|
|
|
*/
|
|
|
|
if (width > FORMATTING_LIMIT ||
|
|
|
|
indent1 > FORMATTING_LIMIT ||
|
|
|
|
indent2 > FORMATTING_LIMIT)
|
|
|
|
return 0;
|
2009-10-17 21:04:19 +00:00
|
|
|
rewrap_message_tail(sb, c, width, indent1, indent2);
|
|
|
|
return end - placeholder + 1;
|
|
|
|
} else
|
|
|
|
return 0;
|
2013-04-18 23:08:50 +00:00
|
|
|
|
|
|
|
case '<':
|
|
|
|
case '>':
|
2019-03-20 08:16:42 +00:00
|
|
|
return parse_padding_placeholder(placeholder, c);
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2021-02-14 10:10:57 +00:00
|
|
|
if (skip_prefix(placeholder, "(describe", &arg)) {
|
2021-02-14 10:04:34 +00:00
|
|
|
struct child_process cmd = CHILD_PROCESS_INIT;
|
|
|
|
struct strbuf out = STRBUF_INIT;
|
|
|
|
struct strbuf err = STRBUF_INIT;
|
2021-02-28 11:22:47 +00:00
|
|
|
struct pretty_print_describe_status *describe_status;
|
|
|
|
|
|
|
|
describe_status = c->pretty_ctx->describe_status;
|
|
|
|
if (describe_status) {
|
|
|
|
if (!describe_status->max_invocations)
|
|
|
|
return 0;
|
|
|
|
describe_status->max_invocations--;
|
|
|
|
}
|
2021-02-14 10:04:34 +00:00
|
|
|
|
|
|
|
cmd.git_cmd = 1;
|
|
|
|
strvec_push(&cmd.args, "describe");
|
2021-02-14 10:10:57 +00:00
|
|
|
|
|
|
|
if (*arg == ':') {
|
|
|
|
arg++;
|
|
|
|
arg += parse_describe_args(arg, &cmd.args);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*arg != ')') {
|
|
|
|
child_process_clear(&cmd);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-02-14 10:04:34 +00:00
|
|
|
strvec_push(&cmd.args, oid_to_hex(&commit->object.oid));
|
|
|
|
pipe_command(&cmd, NULL, 0, &out, 0, &err, 0);
|
|
|
|
strbuf_rtrim(&out);
|
|
|
|
strbuf_addbuf(sb, &out);
|
|
|
|
strbuf_release(&out);
|
|
|
|
strbuf_release(&err);
|
2021-02-14 10:10:57 +00:00
|
|
|
return arg - placeholder + 1;
|
2021-02-14 10:04:34 +00:00
|
|
|
}
|
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
/* these depend on the commit */
|
|
|
|
if (!commit->object.parsed)
|
2018-06-29 01:21:51 +00:00
|
|
|
parse_object(the_repository, &commit->object.oid);
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2007-11-09 00:49:42 +00:00
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 'H': /* commit hash */
|
2013-04-18 23:08:49 +00:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
|
2015-11-10 02:22:28 +00:00
|
|
|
strbuf_addstr(sb, oid_to_hex(&commit->object.oid));
|
2013-04-18 23:08:49 +00:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'h': /* abbreviated commit hash */
|
2013-04-18 23:08:49 +00:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
|
strbuf: convert strbuf_add_unique_abbrev to use struct object_id
Convert the declaration and definition of strbuf_add_unique_abbrev to
make it take a pointer to struct object_id. Predeclare the struct in
strbuf.h, as cache.h includes strbuf.h before it declares the struct,
and otherwise the struct declaration would have the wrong scope.
Apply the following semantic patch, along with the standard object_id
transforms, to adjust the callers:
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2.hash, E3);
+ strbuf_add_unique_abbrev(E1, &E2, E3);
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2->hash, E3);
+ strbuf_add_unique_abbrev(E1, E2, E3);
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-03-12 02:27:28 +00:00
|
|
|
strbuf_add_unique_abbrev(sb, &commit->object.oid,
|
2016-08-06 15:41:01 +00:00
|
|
|
c->pretty_ctx->abbrev);
|
2013-04-18 23:08:49 +00:00
|
|
|
strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'T': /* tree hash */
|
2018-04-06 19:09:38 +00:00
|
|
|
strbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit)));
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 't': /* abbreviated tree hash */
|
2018-04-06 19:09:38 +00:00
|
|
|
strbuf_add_unique_abbrev(sb,
|
2018-05-23 05:38:13 +00:00
|
|
|
get_commit_tree_oid(commit),
|
2016-08-06 15:41:01 +00:00
|
|
|
c->pretty_ctx->abbrev);
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'P': /* parent hashes */
|
|
|
|
for (p = commit->parents; p; p = p->next) {
|
|
|
|
if (p != commit->parents)
|
|
|
|
strbuf_addch(sb, ' ');
|
2015-11-10 02:22:28 +00:00
|
|
|
strbuf_addstr(sb, oid_to_hex(&p->item->object.oid));
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'p': /* abbreviated parent hashes */
|
|
|
|
for (p = commit->parents; p; p = p->next) {
|
|
|
|
if (p != commit->parents)
|
|
|
|
strbuf_addch(sb, ' ');
|
strbuf: convert strbuf_add_unique_abbrev to use struct object_id
Convert the declaration and definition of strbuf_add_unique_abbrev to
make it take a pointer to struct object_id. Predeclare the struct in
strbuf.h, as cache.h includes strbuf.h before it declares the struct,
and otherwise the struct declaration would have the wrong scope.
Apply the following semantic patch, along with the standard object_id
transforms, to adjust the callers:
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2.hash, E3);
+ strbuf_add_unique_abbrev(E1, &E2, E3);
@@
expression E1, E2, E3;
@@
- strbuf_add_unique_abbrev(E1, E2->hash, E3);
+ strbuf_add_unique_abbrev(E1, E2, E3);
Signed-off-by: brian m. carlson <sandals@crustytoothpaste.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-03-12 02:27:28 +00:00
|
|
|
strbuf_add_unique_abbrev(sb, &p->item->object.oid,
|
2016-08-06 15:41:01 +00:00
|
|
|
c->pretty_ctx->abbrev);
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-09 00:49:42 +00:00
|
|
|
case 'm': /* left/right/bottom */
|
2011-03-07 12:31:39 +00:00
|
|
|
strbuf_addstr(sb, get_revision_mark(NULL, commit));
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2008-09-04 21:40:03 +00:00
|
|
|
case 'd':
|
2023-08-20 18:50:04 +00:00
|
|
|
format_decorations(sb, commit, c->auto_color, NULL);
|
2008-09-04 21:40:03 +00:00
|
|
|
return 1;
|
2014-09-18 20:53:53 +00:00
|
|
|
case 'D':
|
2023-08-20 18:50:04 +00:00
|
|
|
{
|
|
|
|
const struct decoration_options opts = {
|
|
|
|
.prefix = "",
|
|
|
|
.suffix = ""
|
|
|
|
};
|
|
|
|
|
|
|
|
format_decorations(sb, commit, c->auto_color, &opts);
|
|
|
|
return 1;
|
|
|
|
}
|
2019-01-11 06:30:46 +00:00
|
|
|
case 'S': /* tag/branch like --source */
|
|
|
|
if (!(c->pretty_ctx->rev && c->pretty_ctx->rev->sources))
|
|
|
|
return 0;
|
|
|
|
slot = revision_sources_at(c->pretty_ctx->rev->sources, commit);
|
|
|
|
if (!(slot && *slot))
|
|
|
|
return 0;
|
|
|
|
strbuf_addstr(sb, *slot);
|
|
|
|
return 1;
|
2009-10-19 15:48:10 +00:00
|
|
|
case 'g': /* reflog info */
|
|
|
|
switch(placeholder[1]) {
|
|
|
|
case 'd': /* reflog selector */
|
|
|
|
case 'D':
|
|
|
|
if (c->pretty_ctx->reflog_info)
|
|
|
|
get_reflog_selector(sb,
|
|
|
|
c->pretty_ctx->reflog_info,
|
2024-04-05 17:44:59 +00:00
|
|
|
c->pretty_ctx->date_mode,
|
2012-05-07 21:11:32 +00:00
|
|
|
c->pretty_ctx->date_mode_explicit,
|
2009-10-19 15:48:10 +00:00
|
|
|
(placeholder[1] == 'd'));
|
|
|
|
return 2;
|
|
|
|
case 's': /* reflog message */
|
|
|
|
if (c->pretty_ctx->reflog_info)
|
|
|
|
get_reflog_message(sb, c->pretty_ctx->reflog_info);
|
|
|
|
return 2;
|
2011-12-16 11:40:24 +00:00
|
|
|
case 'n':
|
|
|
|
case 'N':
|
|
|
|
case 'e':
|
|
|
|
case 'E':
|
|
|
|
return format_reflog_person(sb,
|
|
|
|
placeholder[1],
|
|
|
|
c->pretty_ctx->reflog_info,
|
2024-04-05 17:44:59 +00:00
|
|
|
c->pretty_ctx->date_mode);
|
2009-10-19 15:48:10 +00:00
|
|
|
}
|
|
|
|
return 0; /* unknown %g placeholder */
|
2009-10-09 10:22:05 +00:00
|
|
|
case 'N':
|
2012-10-18 01:51:47 +00:00
|
|
|
if (c->pretty_ctx->notes_message) {
|
|
|
|
strbuf_addstr(sb, c->pretty_ctx->notes_message);
|
2010-04-13 20:31:12 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
|
|
|
|
2011-10-22 04:06:02 +00:00
|
|
|
if (placeholder[0] == 'G') {
|
2013-03-31 16:00:14 +00:00
|
|
|
if (!c->signature_check.result)
|
|
|
|
check_commit_signature(c->commit, &(c->signature_check));
|
2011-10-22 04:06:02 +00:00
|
|
|
switch (placeholder[1]) {
|
|
|
|
case 'G':
|
2021-09-10 20:07:34 +00:00
|
|
|
if (c->signature_check.output)
|
|
|
|
strbuf_addstr(sb, c->signature_check.output);
|
2011-10-22 04:06:02 +00:00
|
|
|
break;
|
|
|
|
case '?':
|
2013-03-31 16:00:14 +00:00
|
|
|
switch (c->signature_check.result) {
|
2011-10-22 04:06:02 +00:00
|
|
|
case 'G':
|
gpg-interface: add minTrustLevel as a configuration option
Previously, signature verification for merge and pull operations checked
if the key had a trust-level of either TRUST_NEVER or TRUST_UNDEFINED in
verify_merge_signature(). If that was the case, the process die()d.
The other code paths that did signature verification relied entirely on
the return code from check_commit_signature(). And signatures made with
a good key, irregardless of its trust level, was considered valid by
check_commit_signature().
This difference in behavior might induce users to erroneously assume
that the trust level of a key in their keyring is always considered by
Git, even for operations where it is not (e.g. during a verify-commit or
verify-tag).
The way it worked was by gpg-interface.c storing the result from the
key/signature status *and* the lowest-two trust levels in the `result`
member of the signature_check structure (the last of these status lines
that were encountered got written to `result`). These are documented in
GPG under the subsection `General status codes` and `Key related`,
respectively [1].
The GPG documentation says the following on the TRUST_ status codes [1]:
"""
These are several similar status codes:
- TRUST_UNDEFINED <error_token>
- TRUST_NEVER <error_token>
- TRUST_MARGINAL [0 [<validation_model>]]
- TRUST_FULLY [0 [<validation_model>]]
- TRUST_ULTIMATE [0 [<validation_model>]]
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.
The error token values are currently only emitted by gpgsm.
"""
My interpretation is that the trust level is conceptionally different
from the validity of the key and/or signature. That seems to also have
been the assumption of the old code in check_signature() where a result
of 'G' (as in GOODSIG) and 'U' (as in TRUST_NEVER or TRUST_UNDEFINED)
were both considered a success.
The two cases where a result of 'U' had special meaning were in
verify_merge_signature() (where this caused git to die()) and in
format_commit_one() (where it affected the output of the %G? format
specifier).
I think it makes sense to refactor the processing of TRUST_ status lines
such that users can configure a minimum trust level that is enforced
globally, rather than have individual parts of git (e.g. merge) do it
themselves (except for a grace period with backward compatibility).
I also think it makes sense to not store the trust level in the same
struct member as the key/signature status. While the presence of a
TRUST_ status code does imply that the signature is good (see the first
paragraph in the included snippet above), as far as I can tell, the
order of the status lines from GPG isn't well-defined; thus it would
seem plausible that the trust level could be overwritten with the
key/signature status if they were stored in the same member of the
signature_check structure.
This patch introduces a new configuration option: gpg.minTrustLevel. It
consolidates trust-level verification to gpg-interface.c and adds a new
`trust_level` member to the signature_check structure.
Backward-compatibility is maintained by introducing a special case in
verify_merge_signature() such that if no user-configurable
gpg.minTrustLevel is set, then the old behavior of rejecting
TRUST_UNDEFINED and TRUST_NEVER is enforced. If, on the other hand,
gpg.minTrustLevel is set, then that value overrides the old behavior.
Similarly, the %G? format specifier will continue show 'U' for
signatures made with a key that has a trust level of TRUST_UNDEFINED or
TRUST_NEVER, even though the 'U' character no longer exist in the
`result` member of the signature_check structure. A new format
specifier, %GT, is also introduced for users that want to show all
possible trust levels for a signature.
Another approach would have been to simply drop the trust-level
requirement in verify_merge_signature(). This would also have made the
behavior consistent with other parts of git that perform signature
verification. However, requiring a minimum trust level for signing keys
does seem to have a real-world use-case. For example, the build system
used by the Qubes OS project currently parses the raw output from
verify-tag in order to assert a minimum trust level for keys used to
sign git tags [2].
[1] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/doc/DETAILS;h=bd00006e933ac56719b1edd2478ecd79273eae72;hb=refs/heads/master
[2] https://github.com/QubesOS/qubes-builder/blob/9674c1991deef45b1a1b1c71fddfab14ba50dccf/scripts/verify-git-tag#L43
Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-12-27 13:55:57 +00:00
|
|
|
switch (c->signature_check.trust_level) {
|
|
|
|
case TRUST_UNDEFINED:
|
|
|
|
case TRUST_NEVER:
|
|
|
|
strbuf_addch(sb, 'U');
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
strbuf_addch(sb, 'G');
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
break;
|
2011-10-22 04:06:02 +00:00
|
|
|
case 'B':
|
gpg-interface: use more status letters
According to gpg2's doc/DETAILS:
For each signature only one of the codes GOODSIG, BADSIG,
EXPSIG, EXPKEYSIG, REVKEYSIG or ERRSIG will be emitted.
gpg1 ("classic") behaves the same (although doc/DETAILS differs).
Currently, we parse gpg's status output for GOODSIG, BADSIG and
trust information and translate that into status codes G, B, U, N
for the %G? format specifier.
git-verify-* returns success in the GOODSIG case only. This is
somewhat in disagreement with gpg, which considers the first 5 of
the 6 above as VALIDSIG, but we err on the very safe side.
Introduce additional status codes E, X, Y, R for ERRSIG, EXPSIG,
EXPKEYSIG, and REVKEYSIG so that a user of %G? gets more information
about the absence of a 'G' on first glance.
Requested-by: Alex <agrambot@gmail.com>
Signed-off-by: Michael J Gruber <git@drmicha.warpmail.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-10-12 13:04:15 +00:00
|
|
|
case 'E':
|
2013-03-31 16:03:22 +00:00
|
|
|
case 'N':
|
gpg-interface: use more status letters
According to gpg2's doc/DETAILS:
For each signature only one of the codes GOODSIG, BADSIG,
EXPSIG, EXPKEYSIG, REVKEYSIG or ERRSIG will be emitted.
gpg1 ("classic") behaves the same (although doc/DETAILS differs).
Currently, we parse gpg's status output for GOODSIG, BADSIG and
trust information and translate that into status codes G, B, U, N
for the %G? format specifier.
git-verify-* returns success in the GOODSIG case only. This is
somewhat in disagreement with gpg, which considers the first 5 of
the 6 above as VALIDSIG, but we err on the very safe side.
Introduce additional status codes E, X, Y, R for ERRSIG, EXPSIG,
EXPKEYSIG, and REVKEYSIG so that a user of %G? gets more information
about the absence of a 'G' on first glance.
Requested-by: Alex <agrambot@gmail.com>
Signed-off-by: Michael J Gruber <git@drmicha.warpmail.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-10-12 13:04:15 +00:00
|
|
|
case 'X':
|
|
|
|
case 'Y':
|
|
|
|
case 'R':
|
2013-03-31 16:00:14 +00:00
|
|
|
strbuf_addch(sb, c->signature_check.result);
|
2011-10-22 04:06:02 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'S':
|
2013-03-31 16:00:14 +00:00
|
|
|
if (c->signature_check.signer)
|
|
|
|
strbuf_addstr(sb, c->signature_check.signer);
|
2011-10-22 04:06:02 +00:00
|
|
|
break;
|
2013-02-14 16:04:46 +00:00
|
|
|
case 'K':
|
2013-03-31 16:00:14 +00:00
|
|
|
if (c->signature_check.key)
|
|
|
|
strbuf_addstr(sb, c->signature_check.key);
|
2013-02-14 16:04:46 +00:00
|
|
|
break;
|
2018-10-22 16:38:20 +00:00
|
|
|
case 'F':
|
|
|
|
if (c->signature_check.fingerprint)
|
|
|
|
strbuf_addstr(sb, c->signature_check.fingerprint);
|
|
|
|
break;
|
2018-10-22 16:38:21 +00:00
|
|
|
case 'P':
|
|
|
|
if (c->signature_check.primary_key_fingerprint)
|
|
|
|
strbuf_addstr(sb, c->signature_check.primary_key_fingerprint);
|
|
|
|
break;
|
gpg-interface: add minTrustLevel as a configuration option
Previously, signature verification for merge and pull operations checked
if the key had a trust-level of either TRUST_NEVER or TRUST_UNDEFINED in
verify_merge_signature(). If that was the case, the process die()d.
The other code paths that did signature verification relied entirely on
the return code from check_commit_signature(). And signatures made with
a good key, irregardless of its trust level, was considered valid by
check_commit_signature().
This difference in behavior might induce users to erroneously assume
that the trust level of a key in their keyring is always considered by
Git, even for operations where it is not (e.g. during a verify-commit or
verify-tag).
The way it worked was by gpg-interface.c storing the result from the
key/signature status *and* the lowest-two trust levels in the `result`
member of the signature_check structure (the last of these status lines
that were encountered got written to `result`). These are documented in
GPG under the subsection `General status codes` and `Key related`,
respectively [1].
The GPG documentation says the following on the TRUST_ status codes [1]:
"""
These are several similar status codes:
- TRUST_UNDEFINED <error_token>
- TRUST_NEVER <error_token>
- TRUST_MARGINAL [0 [<validation_model>]]
- TRUST_FULLY [0 [<validation_model>]]
- TRUST_ULTIMATE [0 [<validation_model>]]
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.
The error token values are currently only emitted by gpgsm.
"""
My interpretation is that the trust level is conceptionally different
from the validity of the key and/or signature. That seems to also have
been the assumption of the old code in check_signature() where a result
of 'G' (as in GOODSIG) and 'U' (as in TRUST_NEVER or TRUST_UNDEFINED)
were both considered a success.
The two cases where a result of 'U' had special meaning were in
verify_merge_signature() (where this caused git to die()) and in
format_commit_one() (where it affected the output of the %G? format
specifier).
I think it makes sense to refactor the processing of TRUST_ status lines
such that users can configure a minimum trust level that is enforced
globally, rather than have individual parts of git (e.g. merge) do it
themselves (except for a grace period with backward compatibility).
I also think it makes sense to not store the trust level in the same
struct member as the key/signature status. While the presence of a
TRUST_ status code does imply that the signature is good (see the first
paragraph in the included snippet above), as far as I can tell, the
order of the status lines from GPG isn't well-defined; thus it would
seem plausible that the trust level could be overwritten with the
key/signature status if they were stored in the same member of the
signature_check structure.
This patch introduces a new configuration option: gpg.minTrustLevel. It
consolidates trust-level verification to gpg-interface.c and adds a new
`trust_level` member to the signature_check structure.
Backward-compatibility is maintained by introducing a special case in
verify_merge_signature() such that if no user-configurable
gpg.minTrustLevel is set, then the old behavior of rejecting
TRUST_UNDEFINED and TRUST_NEVER is enforced. If, on the other hand,
gpg.minTrustLevel is set, then that value overrides the old behavior.
Similarly, the %G? format specifier will continue show 'U' for
signatures made with a key that has a trust level of TRUST_UNDEFINED or
TRUST_NEVER, even though the 'U' character no longer exist in the
`result` member of the signature_check structure. A new format
specifier, %GT, is also introduced for users that want to show all
possible trust levels for a signature.
Another approach would have been to simply drop the trust-level
requirement in verify_merge_signature(). This would also have made the
behavior consistent with other parts of git that perform signature
verification. However, requiring a minimum trust level for signing keys
does seem to have a real-world use-case. For example, the build system
used by the Qubes OS project currently parses the raw output from
verify-tag in order to assert a minimum trust level for keys used to
sign git tags [2].
[1] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/doc/DETAILS;h=bd00006e933ac56719b1edd2478ecd79273eae72;hb=refs/heads/master
[2] https://github.com/QubesOS/qubes-builder/blob/9674c1991deef45b1a1b1c71fddfab14ba50dccf/scripts/verify-git-tag#L43
Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-12-27 13:55:57 +00:00
|
|
|
case 'T':
|
2022-07-11 05:00:50 +00:00
|
|
|
strbuf_addstr(sb, gpg_trust_level_to_str(c->signature_check.trust_level));
|
gpg-interface: add minTrustLevel as a configuration option
Previously, signature verification for merge and pull operations checked
if the key had a trust-level of either TRUST_NEVER or TRUST_UNDEFINED in
verify_merge_signature(). If that was the case, the process die()d.
The other code paths that did signature verification relied entirely on
the return code from check_commit_signature(). And signatures made with
a good key, irregardless of its trust level, was considered valid by
check_commit_signature().
This difference in behavior might induce users to erroneously assume
that the trust level of a key in their keyring is always considered by
Git, even for operations where it is not (e.g. during a verify-commit or
verify-tag).
The way it worked was by gpg-interface.c storing the result from the
key/signature status *and* the lowest-two trust levels in the `result`
member of the signature_check structure (the last of these status lines
that were encountered got written to `result`). These are documented in
GPG under the subsection `General status codes` and `Key related`,
respectively [1].
The GPG documentation says the following on the TRUST_ status codes [1]:
"""
These are several similar status codes:
- TRUST_UNDEFINED <error_token>
- TRUST_NEVER <error_token>
- TRUST_MARGINAL [0 [<validation_model>]]
- TRUST_FULLY [0 [<validation_model>]]
- TRUST_ULTIMATE [0 [<validation_model>]]
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.
The error token values are currently only emitted by gpgsm.
"""
My interpretation is that the trust level is conceptionally different
from the validity of the key and/or signature. That seems to also have
been the assumption of the old code in check_signature() where a result
of 'G' (as in GOODSIG) and 'U' (as in TRUST_NEVER or TRUST_UNDEFINED)
were both considered a success.
The two cases where a result of 'U' had special meaning were in
verify_merge_signature() (where this caused git to die()) and in
format_commit_one() (where it affected the output of the %G? format
specifier).
I think it makes sense to refactor the processing of TRUST_ status lines
such that users can configure a minimum trust level that is enforced
globally, rather than have individual parts of git (e.g. merge) do it
themselves (except for a grace period with backward compatibility).
I also think it makes sense to not store the trust level in the same
struct member as the key/signature status. While the presence of a
TRUST_ status code does imply that the signature is good (see the first
paragraph in the included snippet above), as far as I can tell, the
order of the status lines from GPG isn't well-defined; thus it would
seem plausible that the trust level could be overwritten with the
key/signature status if they were stored in the same member of the
signature_check structure.
This patch introduces a new configuration option: gpg.minTrustLevel. It
consolidates trust-level verification to gpg-interface.c and adds a new
`trust_level` member to the signature_check structure.
Backward-compatibility is maintained by introducing a special case in
verify_merge_signature() such that if no user-configurable
gpg.minTrustLevel is set, then the old behavior of rejecting
TRUST_UNDEFINED and TRUST_NEVER is enforced. If, on the other hand,
gpg.minTrustLevel is set, then that value overrides the old behavior.
Similarly, the %G? format specifier will continue show 'U' for
signatures made with a key that has a trust level of TRUST_UNDEFINED or
TRUST_NEVER, even though the 'U' character no longer exist in the
`result` member of the signature_check structure. A new format
specifier, %GT, is also introduced for users that want to show all
possible trust levels for a signature.
Another approach would have been to simply drop the trust-level
requirement in verify_merge_signature(). This would also have made the
behavior consistent with other parts of git that perform signature
verification. However, requiring a minimum trust level for signing keys
does seem to have a real-world use-case. For example, the build system
used by the Qubes OS project currently parses the raw output from
verify-tag in order to assert a minimum trust level for keys used to
sign git tags [2].
[1] https://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=doc/doc/DETAILS;h=bd00006e933ac56719b1edd2478ecd79273eae72;hb=refs/heads/master
[2] https://github.com/QubesOS/qubes-builder/blob/9674c1991deef45b1a1b1c71fddfab14ba50dccf/scripts/verify-git-tag#L43
Signed-off-by: Hans Jerry Illikainen <hji@dyntopia.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-12-27 13:55:57 +00:00
|
|
|
break;
|
2014-06-17 00:07:07 +00:00
|
|
|
default:
|
|
|
|
return 0;
|
2011-10-22 04:06:02 +00:00
|
|
|
}
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
|
2023-08-20 18:50:07 +00:00
|
|
|
if (skip_prefix(placeholder, "(decorate", &arg)) {
|
|
|
|
struct decoration_options opts = { NULL };
|
|
|
|
size_t ret = 0;
|
|
|
|
|
|
|
|
if (*arg == ':') {
|
|
|
|
arg++;
|
|
|
|
parse_decoration_options(&arg, &opts);
|
|
|
|
}
|
|
|
|
if (*arg == ')') {
|
|
|
|
format_decorations(sb, commit, c->auto_color, &opts);
|
|
|
|
ret = arg - placeholder + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
free_decoration_options(&opts);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2007-11-09 00:49:42 +00:00
|
|
|
/* For the rest we have to parse the commit header. */
|
pretty: lazy-load commit data when expanding user-format
When we expand a user-format, we try to avoid work that isn't necessary
for the output. For instance, we don't bother parsing the commit header
until we know we need the author, subject, etc.
But we do always load the commit object's contents from disk, even if
the format doesn't require it (e.g., just "%H"). Traditionally this
didn't matter much, because we'd have loaded it as part of the traversal
anyway, and we'd typically have those bytes attached to the commit
struct (or these days, cached in a commit-slab).
But when we have a commit-graph, we might easily get to the point of
pretty-printing a commit without ever having looked at the actual object
contents. We should push off that load (and reencoding) until we're
certain that it's needed.
I think the results of p4205 show the advantage pretty clearly (we serve
parent and tree oids out of the commit struct itself, so they benefit as
well):
# using git.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 0.40(0.39+0.01) 0.03(0.02+0.01) -92.5%
4205.2: log with %h 0.45(0.44+0.01) 0.09(0.09+0.00) -80.0%
4205.3: log with %T 0.40(0.39+0.00) 0.04(0.04+0.00) -90.0%
4205.4: log with %t 0.46(0.46+0.00) 0.09(0.08+0.01) -80.4%
4205.5: log with %P 0.39(0.39+0.00) 0.03(0.03+0.00) -92.3%
4205.6: log with %p 0.46(0.46+0.00) 0.10(0.09+0.00) -78.3%
4205.7: log with %h-%h-%h 0.52(0.51+0.01) 0.15(0.14+0.00) -71.2%
4205.8: log with %an-%ae-%s 0.42(0.41+0.00) 0.42(0.41+0.01) +0.0%
# using linux.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 7.12(6.97+0.14) 0.76(0.65+0.11) -89.3%
4205.2: log with %h 7.35(7.19+0.16) 1.30(1.19+0.11) -82.3%
4205.3: log with %T 7.58(7.42+0.15) 1.02(0.94+0.08) -86.5%
4205.4: log with %t 8.05(7.89+0.15) 1.55(1.41+0.13) -80.7%
4205.5: log with %P 7.12(7.01+0.10) 0.76(0.69+0.07) -89.3%
4205.6: log with %p 7.38(7.27+0.10) 1.32(1.20+0.12) -82.1%
4205.7: log with %h-%h-%h 7.81(7.67+0.13) 1.79(1.67+0.12) -77.1%
4205.8: log with %an-%ae-%s 7.90(7.74+0.15) 7.81(7.66+0.15) -1.1%
I added the final test to show where we don't improve (the 1% there is
just lucky noise), but also as a regression test to make sure we're not
doing anything stupid like loading the commit multiple times when there
are several placeholders that need it.
Reported-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-28 19:57:39 +00:00
|
|
|
if (!c->commit_header_parsed) {
|
|
|
|
msg = c->message =
|
|
|
|
repo_logmsg_reencode(c->repository, commit,
|
|
|
|
&c->commit_encoding, "UTF-8");
|
2007-11-10 11:14:20 +00:00
|
|
|
parse_commit_header(c);
|
pretty: lazy-load commit data when expanding user-format
When we expand a user-format, we try to avoid work that isn't necessary
for the output. For instance, we don't bother parsing the commit header
until we know we need the author, subject, etc.
But we do always load the commit object's contents from disk, even if
the format doesn't require it (e.g., just "%H"). Traditionally this
didn't matter much, because we'd have loaded it as part of the traversal
anyway, and we'd typically have those bytes attached to the commit
struct (or these days, cached in a commit-slab).
But when we have a commit-graph, we might easily get to the point of
pretty-printing a commit without ever having looked at the actual object
contents. We should push off that load (and reencoding) until we're
certain that it's needed.
I think the results of p4205 show the advantage pretty clearly (we serve
parent and tree oids out of the commit struct itself, so they benefit as
well):
# using git.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 0.40(0.39+0.01) 0.03(0.02+0.01) -92.5%
4205.2: log with %h 0.45(0.44+0.01) 0.09(0.09+0.00) -80.0%
4205.3: log with %T 0.40(0.39+0.00) 0.04(0.04+0.00) -90.0%
4205.4: log with %t 0.46(0.46+0.00) 0.09(0.08+0.01) -80.4%
4205.5: log with %P 0.39(0.39+0.00) 0.03(0.03+0.00) -92.3%
4205.6: log with %p 0.46(0.46+0.00) 0.10(0.09+0.00) -78.3%
4205.7: log with %h-%h-%h 0.52(0.51+0.01) 0.15(0.14+0.00) -71.2%
4205.8: log with %an-%ae-%s 0.42(0.41+0.00) 0.42(0.41+0.01) +0.0%
# using linux.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 7.12(6.97+0.14) 0.76(0.65+0.11) -89.3%
4205.2: log with %h 7.35(7.19+0.16) 1.30(1.19+0.11) -82.3%
4205.3: log with %T 7.58(7.42+0.15) 1.02(0.94+0.08) -86.5%
4205.4: log with %t 8.05(7.89+0.15) 1.55(1.41+0.13) -80.7%
4205.5: log with %P 7.12(7.01+0.10) 0.76(0.69+0.07) -89.3%
4205.6: log with %p 7.38(7.27+0.10) 1.32(1.20+0.12) -82.1%
4205.7: log with %h-%h-%h 7.81(7.67+0.13) 1.79(1.67+0.12) -77.1%
4205.8: log with %an-%ae-%s 7.90(7.74+0.15) 7.81(7.66+0.15) -1.1%
I added the final test to show where we don't improve (the 1% there is
just lucky noise), but also as a regression test to make sure we're not
doing anything stupid like loading the commit multiple times when there
are several placeholders that need it.
Reported-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-28 19:57:39 +00:00
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2007-11-10 11:14:20 +00:00
|
|
|
switch (placeholder[0]) {
|
2008-02-09 14:40:19 +00:00
|
|
|
case 'a': /* author ... */
|
|
|
|
return format_person_part(sb, placeholder[1],
|
2008-08-29 00:54:59 +00:00
|
|
|
msg + c->author.off, c->author.len,
|
2024-04-05 17:44:59 +00:00
|
|
|
c->pretty_ctx->date_mode);
|
2008-02-09 14:40:19 +00:00
|
|
|
case 'c': /* committer ... */
|
|
|
|
return format_person_part(sb, placeholder[1],
|
2008-08-29 00:54:59 +00:00
|
|
|
msg + c->committer.off, c->committer.len,
|
2024-04-05 17:44:59 +00:00
|
|
|
c->pretty_ctx->date_mode);
|
2008-02-09 14:40:19 +00:00
|
|
|
case 'e': /* encoding */
|
2013-04-18 23:08:41 +00:00
|
|
|
if (c->commit_encoding)
|
|
|
|
strbuf_addstr(sb, c->commit_encoding);
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2010-03-25 02:51:52 +00:00
|
|
|
case 'B': /* raw body */
|
|
|
|
/* message_off is always left at the initial newline */
|
|
|
|
strbuf_addstr(sb, msg + c->message_off + 1);
|
|
|
|
return 1;
|
2008-12-27 00:49:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Now we need to parse the commit message. */
|
|
|
|
if (!c->commit_message_parsed)
|
|
|
|
parse_commit_message(c);
|
|
|
|
|
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 's': /* subject */
|
|
|
|
format_subject(sb, msg + c->subject_off, " ");
|
|
|
|
return 1;
|
2009-03-23 02:14:01 +00:00
|
|
|
case 'f': /* sanitized subject */
|
2020-08-21 21:41:49 +00:00
|
|
|
eol = strchrnul(msg + c->subject_off, '\n');
|
|
|
|
format_sanitized_subject(sb, msg + c->subject_off, eol - (msg + c->subject_off));
|
2009-03-23 02:14:01 +00:00
|
|
|
return 1;
|
2008-02-09 14:40:19 +00:00
|
|
|
case 'b': /* body */
|
2007-11-10 11:14:20 +00:00
|
|
|
strbuf_addstr(sb, msg + c->body_off);
|
2008-02-09 14:40:19 +00:00
|
|
|
return 1;
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
2016-11-19 00:58:14 +00:00
|
|
|
|
2017-08-15 10:25:27 +00:00
|
|
|
if (skip_prefix(placeholder, "(trailers", &arg)) {
|
2017-08-15 10:23:56 +00:00
|
|
|
struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
|
2019-01-28 21:33:34 +00:00
|
|
|
struct string_list filter_list = STRING_LIST_INIT_NODUP;
|
2019-01-28 21:33:37 +00:00
|
|
|
struct strbuf sepbuf = STRBUF_INIT;
|
2020-12-09 15:52:08 +00:00
|
|
|
struct strbuf kvsepbuf = STRBUF_INIT;
|
2019-01-28 21:33:33 +00:00
|
|
|
size_t ret = 0;
|
2018-08-23 00:50:17 +00:00
|
|
|
|
|
|
|
opts.no_divider = 1;
|
|
|
|
|
2017-10-01 16:18:47 +00:00
|
|
|
if (*arg == ':') {
|
|
|
|
arg++;
|
2021-02-13 01:52:42 +00:00
|
|
|
if (format_set_trailers_options(&opts, &filter_list, &sepbuf, &kvsepbuf, &arg, NULL))
|
2021-02-13 01:52:41 +00:00
|
|
|
goto trailer_out;
|
2017-08-15 10:25:27 +00:00
|
|
|
}
|
|
|
|
if (*arg == ')') {
|
trailer: reorder format_trailers_from_commit() parameters
Currently there are two functions for formatting trailers in
<trailer.h>:
void format_trailers(const struct process_trailer_options *,
struct list_head *trailers, FILE *outfile);
void format_trailers_from_commit(struct strbuf *out, const char *msg,
const struct process_trailer_options *opts);
and although they are similar enough (even taking the same
process_trailer_options struct pointer) they are used quite differently.
One might intuitively think that format_trailers_from_commit() builds on
top of format_trailers(), but this is not the case. Instead
format_trailers_from_commit() calls format_trailer_info() and
format_trailers() is never called in that codepath.
This is a preparatory refactor to help us deprecate format_trailers() in
favor of format_trailer_info() (at which point we can rename the latter
to the former). When the deprecation is complete, both
format_trailers_from_commit(), and the interpret-trailers builtin will
be able to call into the same helper function (instead of
format_trailers() and format_trailer_info(), respectively). Unifying the
formatters is desirable because it simplifies the API.
Reorder parameters for format_trailers_from_commit() to prefer
const struct process_trailer_options *opts
as the first parameter, because these options are intimately tied to
formatting trailers. And take
struct strbuf *out
last, because it's an "out parameter" (something that the caller wants
to use as the output of this function).
Similarly, reorder parameters for format_trailer_info(), because later
on we will unify the two together.
Signed-off-by: Linus Arver <linusa@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-01 00:14:42 +00:00
|
|
|
format_trailers_from_commit(&opts, msg + c->subject_off, sb);
|
2019-01-28 21:33:33 +00:00
|
|
|
ret = arg - placeholder + 1;
|
2017-08-15 10:25:27 +00:00
|
|
|
}
|
2019-01-28 21:33:34 +00:00
|
|
|
trailer_out:
|
|
|
|
string_list_clear(&filter_list, 0);
|
2019-01-28 21:33:37 +00:00
|
|
|
strbuf_release(&sepbuf);
|
2019-01-28 21:33:33 +00:00
|
|
|
return ret;
|
2016-11-19 00:58:14 +00:00
|
|
|
}
|
|
|
|
|
2008-02-09 14:40:19 +00:00
|
|
|
return 0; /* unknown placeholder */
|
2007-11-09 00:49:42 +00:00
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:50 +00:00
|
|
|
static size_t format_and_pad_commit(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
|
|
|
struct format_commit_context *c)
|
|
|
|
{
|
|
|
|
struct strbuf local_sb = STRBUF_INIT;
|
2022-12-01 14:46:25 +00:00
|
|
|
size_t total_consumed = 0;
|
|
|
|
int len, padding = c->padding;
|
|
|
|
|
2013-04-18 23:08:50 +00:00
|
|
|
if (padding < 0) {
|
|
|
|
const char *start = strrchr(sb->buf, '\n');
|
|
|
|
int occupied;
|
|
|
|
if (!start)
|
|
|
|
start = sb->buf;
|
2022-12-01 14:46:53 +00:00
|
|
|
occupied = utf8_strnwidth(start, strlen(start), 1);
|
2016-06-16 13:18:37 +00:00
|
|
|
occupied += c->pretty_ctx->graph_width;
|
2013-04-18 23:08:50 +00:00
|
|
|
padding = (-padding) - occupied;
|
|
|
|
}
|
|
|
|
while (1) {
|
|
|
|
int modifier = *placeholder == 'C';
|
2022-12-01 14:46:25 +00:00
|
|
|
size_t consumed = format_commit_one(&local_sb, placeholder, c);
|
2013-04-18 23:08:50 +00:00
|
|
|
total_consumed += consumed;
|
|
|
|
|
|
|
|
if (!modifier)
|
|
|
|
break;
|
|
|
|
|
|
|
|
placeholder += consumed;
|
|
|
|
if (*placeholder != '%')
|
|
|
|
break;
|
|
|
|
placeholder++;
|
|
|
|
total_consumed++;
|
|
|
|
}
|
2022-12-01 14:46:53 +00:00
|
|
|
len = utf8_strnwidth(local_sb.buf, local_sb.len, 1);
|
2013-04-18 23:08:52 +00:00
|
|
|
|
|
|
|
if (c->flush_type == flush_left_and_steal) {
|
|
|
|
const char *ch = sb->buf + sb->len - 1;
|
|
|
|
while (len > padding && ch > sb->buf) {
|
|
|
|
const char *p;
|
|
|
|
if (*ch == ' ') {
|
|
|
|
ch--;
|
|
|
|
padding++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* check for trailing ansi sequences */
|
|
|
|
if (*ch != 'm')
|
|
|
|
break;
|
|
|
|
p = ch - 1;
|
2022-12-01 14:46:30 +00:00
|
|
|
while (p > sb->buf && ch - p < 10 && *p != '\033')
|
2013-04-18 23:08:52 +00:00
|
|
|
p--;
|
|
|
|
if (*p != '\033' ||
|
|
|
|
ch + 1 - p != display_mode_esc_sequence_len(p))
|
|
|
|
break;
|
|
|
|
/*
|
|
|
|
* got a good ansi sequence, put it back to
|
|
|
|
* local_sb as we're cutting sb
|
|
|
|
*/
|
|
|
|
strbuf_insert(&local_sb, 0, p, ch + 1 - p);
|
|
|
|
ch = p - 1;
|
|
|
|
}
|
|
|
|
strbuf_setlen(sb, ch + 1 - sb->buf);
|
|
|
|
c->flush_type = flush_left;
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:51 +00:00
|
|
|
if (len > padding) {
|
|
|
|
switch (c->truncate) {
|
|
|
|
case trunc_left:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
0, len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_middle:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
padding / 2 - 1,
|
|
|
|
len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_right:
|
|
|
|
strbuf_utf8_replace(&local_sb,
|
|
|
|
padding - 2, len - (padding - 2),
|
|
|
|
"..");
|
|
|
|
break;
|
|
|
|
case trunc_none:
|
|
|
|
break;
|
|
|
|
}
|
2014-07-10 08:52:21 +00:00
|
|
|
strbuf_addbuf(sb, &local_sb);
|
2013-04-18 23:08:51 +00:00
|
|
|
} else {
|
2022-12-01 14:46:25 +00:00
|
|
|
size_t sb_len = sb->len, offset = 0;
|
2013-04-18 23:08:50 +00:00
|
|
|
if (c->flush_type == flush_left)
|
|
|
|
offset = padding - len;
|
|
|
|
else if (c->flush_type == flush_both)
|
|
|
|
offset = (padding - len) / 2;
|
|
|
|
/*
|
|
|
|
* we calculate padding in columns, now
|
|
|
|
* convert it back to chars
|
|
|
|
*/
|
|
|
|
padding = padding - len + local_sb.len;
|
2014-09-07 07:06:42 +00:00
|
|
|
strbuf_addchars(sb, ' ', padding);
|
2013-04-18 23:08:50 +00:00
|
|
|
memcpy(sb->buf + sb_len + offset, local_sb.buf,
|
|
|
|
local_sb.len);
|
|
|
|
}
|
|
|
|
strbuf_release(&local_sb);
|
|
|
|
c->flush_type = no_flush;
|
|
|
|
return total_consumed;
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:47 +00:00
|
|
|
static size_t format_commit_item(struct strbuf *sb, /* in UTF-8 */
|
|
|
|
const char *placeholder,
|
2023-06-17 20:43:17 +00:00
|
|
|
struct format_commit_context *context)
|
2009-10-05 06:43:32 +00:00
|
|
|
{
|
2022-12-01 14:46:25 +00:00
|
|
|
size_t consumed, orig_len;
|
2009-10-05 06:43:32 +00:00
|
|
|
enum {
|
|
|
|
NO_MAGIC,
|
|
|
|
ADD_LF_BEFORE_NON_EMPTY,
|
|
|
|
DEL_LF_BEFORE_EMPTY,
|
2010-06-22 16:45:22 +00:00
|
|
|
ADD_SP_BEFORE_NON_EMPTY
|
2009-10-05 06:43:32 +00:00
|
|
|
} magic = NO_MAGIC;
|
|
|
|
|
|
|
|
switch (placeholder[0]) {
|
|
|
|
case '-':
|
|
|
|
magic = DEL_LF_BEFORE_EMPTY;
|
|
|
|
break;
|
|
|
|
case '+':
|
|
|
|
magic = ADD_LF_BEFORE_NON_EMPTY;
|
|
|
|
break;
|
2010-06-14 16:12:29 +00:00
|
|
|
case ' ':
|
|
|
|
magic = ADD_SP_BEFORE_NON_EMPTY;
|
|
|
|
break;
|
2009-10-05 06:43:32 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
pretty: fix adding linefeed when placeholder is not expanded
When a formatting directive has a `+` or ` ` after the `%`, then we add
either a line feed or space if the placeholder expands to a non-empty
string. In specific cases though this logic doesn't work as expected,
and we try to add the character even in the case where the formatting
directive is empty.
One such pattern is `%w(1)%+d%+w(2)`. `%+d` expands to reference names
pointing to a certain commit, like in `git log --decorate`. For a tagged
commit this would for example expand to `\n (tag: v1.0.0)`, which has a
leading newline due to the `+` modifier and a space added by `%d`. Now
the second wrapping directive will cause us to rewrap the text to
`\n(tag:\nv1.0.0)`, which is one byte shorter due to the missing leading
space. The code that handles the `+` magic now notices that the length
has changed and will thus try to insert a leading line feed at the
original posititon. But as the string was shortened, the original
position is past the buffer's boundary and thus we die with an error.
Now there are two issues here:
1. We check whether the buffer length has changed, not whether it
has been extended. This causes us to try and add the character
past the string boundary.
2. The current logic does not make any sense whatsoever. When the
string got expanded due to the rewrap, putting the separator into
the original position is likely to put it somewhere into the
middle of the rewrapped contents.
It is debatable whether `%+w()` makes any sense in the first place.
Strictly speaking, the placeholder never expands to a non-empty string,
and consequentially we shouldn't ever accept this combination. We thus
fix the bug by simply refusing `%+w()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-12-01 14:46:39 +00:00
|
|
|
if (magic != NO_MAGIC) {
|
2009-10-05 06:43:32 +00:00
|
|
|
placeholder++;
|
|
|
|
|
pretty: fix adding linefeed when placeholder is not expanded
When a formatting directive has a `+` or ` ` after the `%`, then we add
either a line feed or space if the placeholder expands to a non-empty
string. In specific cases though this logic doesn't work as expected,
and we try to add the character even in the case where the formatting
directive is empty.
One such pattern is `%w(1)%+d%+w(2)`. `%+d` expands to reference names
pointing to a certain commit, like in `git log --decorate`. For a tagged
commit this would for example expand to `\n (tag: v1.0.0)`, which has a
leading newline due to the `+` modifier and a space added by `%d`. Now
the second wrapping directive will cause us to rewrap the text to
`\n(tag:\nv1.0.0)`, which is one byte shorter due to the missing leading
space. The code that handles the `+` magic now notices that the length
has changed and will thus try to insert a leading line feed at the
original posititon. But as the string was shortened, the original
position is past the buffer's boundary and thus we die with an error.
Now there are two issues here:
1. We check whether the buffer length has changed, not whether it
has been extended. This causes us to try and add the character
past the string boundary.
2. The current logic does not make any sense whatsoever. When the
string got expanded due to the rewrap, putting the separator into
the original position is likely to put it somewhere into the
middle of the rewrapped contents.
It is debatable whether `%+w()` makes any sense in the first place.
Strictly speaking, the placeholder never expands to a non-empty string,
and consequentially we shouldn't ever accept this combination. We thus
fix the bug by simply refusing `%+w()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2022-12-01 14:46:39 +00:00
|
|
|
switch (placeholder[0]) {
|
|
|
|
case 'w':
|
|
|
|
/*
|
|
|
|
* `%+w()` cannot ever expand to a non-empty string,
|
|
|
|
* and it potentially changes the layout of preceding
|
|
|
|
* contents. We're thus not able to handle the magic in
|
|
|
|
* this combination and refuse the pattern.
|
|
|
|
*/
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2009-10-05 06:43:32 +00:00
|
|
|
orig_len = sb->len;
|
2023-07-07 18:46:26 +00:00
|
|
|
if (context->flush_type == no_flush)
|
2013-04-18 23:08:50 +00:00
|
|
|
consumed = format_commit_one(sb, placeholder, context);
|
2023-07-07 18:46:26 +00:00
|
|
|
else
|
|
|
|
consumed = format_and_pad_commit(sb, placeholder, context);
|
2009-10-05 06:43:32 +00:00
|
|
|
if (magic == NO_MAGIC)
|
|
|
|
return consumed;
|
|
|
|
|
|
|
|
if ((orig_len == sb->len) && magic == DEL_LF_BEFORE_EMPTY) {
|
|
|
|
while (sb->len && sb->buf[sb->len - 1] == '\n')
|
|
|
|
strbuf_setlen(sb, sb->len - 1);
|
2010-06-14 16:12:29 +00:00
|
|
|
} else if (orig_len != sb->len) {
|
|
|
|
if (magic == ADD_LF_BEFORE_NON_EMPTY)
|
2020-02-09 13:44:23 +00:00
|
|
|
strbuf_insertstr(sb, orig_len, "\n");
|
2010-06-14 16:12:29 +00:00
|
|
|
else if (magic == ADD_SP_BEFORE_NON_EMPTY)
|
2020-02-09 13:44:23 +00:00
|
|
|
strbuf_insertstr(sb, orig_len, " ");
|
2009-10-05 06:43:32 +00:00
|
|
|
}
|
|
|
|
return consumed + 1;
|
|
|
|
}
|
|
|
|
|
2010-04-13 20:31:12 +00:00
|
|
|
void userformat_find_requirements(const char *fmt, struct userformat_want *w)
|
|
|
|
{
|
|
|
|
if (!fmt) {
|
|
|
|
if (!user_format)
|
|
|
|
return;
|
|
|
|
fmt = user_format;
|
|
|
|
}
|
2023-07-07 19:08:33 +00:00
|
|
|
while ((fmt = strchr(fmt, '%'))) {
|
|
|
|
fmt++;
|
2023-06-17 20:43:17 +00:00
|
|
|
if (skip_prefix(fmt, "%", &fmt))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (*fmt == '+' || *fmt == '-' || *fmt == ' ')
|
|
|
|
fmt++;
|
|
|
|
|
|
|
|
switch (*fmt) {
|
|
|
|
case 'N':
|
|
|
|
w->notes = 1;
|
|
|
|
break;
|
|
|
|
case 'S':
|
|
|
|
w->source = 1;
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
|
|
case 'D':
|
|
|
|
w->decorate = 1;
|
|
|
|
break;
|
2023-10-08 20:23:07 +00:00
|
|
|
case '(':
|
|
|
|
if (starts_with(fmt + 1, "decorate"))
|
|
|
|
w->decorate = 1;
|
|
|
|
break;
|
2023-06-17 20:43:17 +00:00
|
|
|
}
|
|
|
|
}
|
2010-04-13 20:31:12 +00:00
|
|
|
}
|
|
|
|
|
2018-11-14 00:13:00 +00:00
|
|
|
void repo_format_commit_message(struct repository *r,
|
|
|
|
const struct commit *commit,
|
|
|
|
const char *format, struct strbuf *sb,
|
|
|
|
const struct pretty_print_context *pretty_ctx)
|
2007-11-09 00:49:42 +00:00
|
|
|
{
|
2019-11-20 00:51:16 +00:00
|
|
|
struct format_commit_context context = {
|
pretty: lazy-load commit data when expanding user-format
When we expand a user-format, we try to avoid work that isn't necessary
for the output. For instance, we don't bother parsing the commit header
until we know we need the author, subject, etc.
But we do always load the commit object's contents from disk, even if
the format doesn't require it (e.g., just "%H"). Traditionally this
didn't matter much, because we'd have loaded it as part of the traversal
anyway, and we'd typically have those bytes attached to the commit
struct (or these days, cached in a commit-slab).
But when we have a commit-graph, we might easily get to the point of
pretty-printing a commit without ever having looked at the actual object
contents. We should push off that load (and reencoding) until we're
certain that it's needed.
I think the results of p4205 show the advantage pretty clearly (we serve
parent and tree oids out of the commit struct itself, so they benefit as
well):
# using git.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 0.40(0.39+0.01) 0.03(0.02+0.01) -92.5%
4205.2: log with %h 0.45(0.44+0.01) 0.09(0.09+0.00) -80.0%
4205.3: log with %T 0.40(0.39+0.00) 0.04(0.04+0.00) -90.0%
4205.4: log with %t 0.46(0.46+0.00) 0.09(0.08+0.01) -80.4%
4205.5: log with %P 0.39(0.39+0.00) 0.03(0.03+0.00) -92.3%
4205.6: log with %p 0.46(0.46+0.00) 0.10(0.09+0.00) -78.3%
4205.7: log with %h-%h-%h 0.52(0.51+0.01) 0.15(0.14+0.00) -71.2%
4205.8: log with %an-%ae-%s 0.42(0.41+0.00) 0.42(0.41+0.01) +0.0%
# using linux.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 7.12(6.97+0.14) 0.76(0.65+0.11) -89.3%
4205.2: log with %h 7.35(7.19+0.16) 1.30(1.19+0.11) -82.3%
4205.3: log with %T 7.58(7.42+0.15) 1.02(0.94+0.08) -86.5%
4205.4: log with %t 8.05(7.89+0.15) 1.55(1.41+0.13) -80.7%
4205.5: log with %P 7.12(7.01+0.10) 0.76(0.69+0.07) -89.3%
4205.6: log with %p 7.38(7.27+0.10) 1.32(1.20+0.12) -82.1%
4205.7: log with %h-%h-%h 7.81(7.67+0.13) 1.79(1.67+0.12) -77.1%
4205.8: log with %an-%ae-%s 7.90(7.74+0.15) 7.81(7.66+0.15) -1.1%
I added the final test to show where we don't improve (the 1% there is
just lucky noise), but also as a regression test to make sure we're not
doing anything stupid like loading the commit multiple times when there
are several placeholders that need it.
Reported-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-28 19:57:39 +00:00
|
|
|
.repository = r,
|
2019-11-20 00:51:16 +00:00
|
|
|
.commit = commit,
|
|
|
|
.pretty_ctx = pretty_ctx,
|
|
|
|
.wrap_start = sb->len
|
|
|
|
};
|
2010-11-02 19:59:08 +00:00
|
|
|
const char *output_enc = pretty_ctx->output_encoding;
|
2013-04-18 23:08:47 +00:00
|
|
|
const char *utf8 = "UTF-8";
|
2007-11-10 11:14:20 +00:00
|
|
|
|
2023-06-17 20:43:17 +00:00
|
|
|
while (strbuf_expand_step(sb, &format)) {
|
|
|
|
size_t len;
|
|
|
|
|
|
|
|
if (skip_prefix(format, "%", &format))
|
|
|
|
strbuf_addch(sb, '%');
|
|
|
|
else if ((len = format_commit_item(sb, format, &context)))
|
|
|
|
format += len;
|
|
|
|
else
|
|
|
|
strbuf_addch(sb, '%');
|
|
|
|
}
|
2009-10-17 21:04:19 +00:00
|
|
|
rewrap_message_tail(sb, &context, 0, 0, 0);
|
2010-11-02 19:59:08 +00:00
|
|
|
|
pretty: lazy-load commit data when expanding user-format
When we expand a user-format, we try to avoid work that isn't necessary
for the output. For instance, we don't bother parsing the commit header
until we know we need the author, subject, etc.
But we do always load the commit object's contents from disk, even if
the format doesn't require it (e.g., just "%H"). Traditionally this
didn't matter much, because we'd have loaded it as part of the traversal
anyway, and we'd typically have those bytes attached to the commit
struct (or these days, cached in a commit-slab).
But when we have a commit-graph, we might easily get to the point of
pretty-printing a commit without ever having looked at the actual object
contents. We should push off that load (and reencoding) until we're
certain that it's needed.
I think the results of p4205 show the advantage pretty clearly (we serve
parent and tree oids out of the commit struct itself, so they benefit as
well):
# using git.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 0.40(0.39+0.01) 0.03(0.02+0.01) -92.5%
4205.2: log with %h 0.45(0.44+0.01) 0.09(0.09+0.00) -80.0%
4205.3: log with %T 0.40(0.39+0.00) 0.04(0.04+0.00) -90.0%
4205.4: log with %t 0.46(0.46+0.00) 0.09(0.08+0.01) -80.4%
4205.5: log with %P 0.39(0.39+0.00) 0.03(0.03+0.00) -92.3%
4205.6: log with %p 0.46(0.46+0.00) 0.10(0.09+0.00) -78.3%
4205.7: log with %h-%h-%h 0.52(0.51+0.01) 0.15(0.14+0.00) -71.2%
4205.8: log with %an-%ae-%s 0.42(0.41+0.00) 0.42(0.41+0.01) +0.0%
# using linux.git as the test repo
Test HEAD^ HEAD
----------------------------------------------------------------------
4205.1: log with %H 7.12(6.97+0.14) 0.76(0.65+0.11) -89.3%
4205.2: log with %h 7.35(7.19+0.16) 1.30(1.19+0.11) -82.3%
4205.3: log with %T 7.58(7.42+0.15) 1.02(0.94+0.08) -86.5%
4205.4: log with %t 8.05(7.89+0.15) 1.55(1.41+0.13) -80.7%
4205.5: log with %P 7.12(7.01+0.10) 0.76(0.69+0.07) -89.3%
4205.6: log with %p 7.38(7.27+0.10) 1.32(1.20+0.12) -82.1%
4205.7: log with %h-%h-%h 7.81(7.67+0.13) 1.79(1.67+0.12) -77.1%
4205.8: log with %an-%ae-%s 7.90(7.74+0.15) 7.81(7.66+0.15) -1.1%
I added the final test to show where we don't improve (the 1% there is
just lucky noise), but also as a regression test to make sure we're not
doing anything stupid like loading the commit multiple times when there
are several placeholders that need it.
Reported-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2021-01-28 19:57:39 +00:00
|
|
|
/*
|
|
|
|
* Convert output to an actual output encoding; note that
|
|
|
|
* format_commit_item() will always use UTF-8, so we don't
|
|
|
|
* have to bother if that's what the output wants.
|
|
|
|
*/
|
2013-04-18 23:08:47 +00:00
|
|
|
if (output_enc) {
|
|
|
|
if (same_encoding(utf8, output_enc))
|
|
|
|
output_enc = NULL;
|
|
|
|
} else {
|
|
|
|
if (context.commit_encoding &&
|
|
|
|
!same_encoding(context.commit_encoding, utf8))
|
|
|
|
output_enc = context.commit_encoding;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (output_enc) {
|
2018-07-24 10:50:33 +00:00
|
|
|
size_t outsz;
|
2013-04-18 23:08:47 +00:00
|
|
|
char *out = reencode_string_len(sb->buf, sb->len,
|
|
|
|
output_enc, utf8, &outsz);
|
|
|
|
if (out)
|
|
|
|
strbuf_attach(sb, out, outsz, outsz + 1);
|
|
|
|
}
|
|
|
|
|
2013-04-18 23:08:41 +00:00
|
|
|
free(context.commit_encoding);
|
2018-11-14 00:13:00 +00:00
|
|
|
repo_unuse_commit_buffer(r, commit, context.message);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
|
2013-07-03 07:07:48 +00:00
|
|
|
static void pp_header(struct pretty_print_context *pp,
|
2007-11-04 19:15:06 +00:00
|
|
|
const char *encoding,
|
|
|
|
const struct commit *commit,
|
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb)
|
|
|
|
{
|
|
|
|
int parents_shown = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
2014-10-04 18:54:50 +00:00
|
|
|
const char *name, *line = *msg_p;
|
2007-11-04 19:15:06 +00:00
|
|
|
int linelen = get_one_line(*msg_p);
|
|
|
|
|
|
|
|
if (!linelen)
|
|
|
|
return;
|
|
|
|
*msg_p += linelen;
|
|
|
|
|
|
|
|
if (linelen == 1)
|
|
|
|
/* End of header */
|
|
|
|
return;
|
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_RAW) {
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_add(sb, line, linelen);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-11-30 20:55:40 +00:00
|
|
|
if (starts_with(line, "parent ")) {
|
2018-07-16 01:28:08 +00:00
|
|
|
if (linelen != the_hash_algo->hexsz + 8)
|
2007-11-04 19:15:06 +00:00
|
|
|
die("bad parent line in commit");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!parents_shown) {
|
2014-07-16 23:52:09 +00:00
|
|
|
unsigned num = commit_list_count(commit->parents);
|
2007-11-04 19:15:06 +00:00
|
|
|
/* with enough slop */
|
2018-07-16 01:28:08 +00:00
|
|
|
strbuf_grow(sb, num * (GIT_MAX_HEXSZ + 10) + 20);
|
2011-05-26 22:27:49 +00:00
|
|
|
add_merge_info(pp, sb, commit);
|
2007-11-04 19:15:06 +00:00
|
|
|
parents_shown = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MEDIUM == DEFAULT shows only author with dates.
|
|
|
|
* FULL shows both authors but not dates.
|
|
|
|
* FULLER shows both authors and dates.
|
|
|
|
*/
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(line, "author ", &name)) {
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_grow(sb, linelen + 80);
|
2014-10-04 18:54:50 +00:00
|
|
|
pp_user_info(pp, "Author", sb, name, encoding);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
2014-10-04 18:54:50 +00:00
|
|
|
if (skip_prefix(line, "committer ", &name) &&
|
2011-05-26 22:27:49 +00:00
|
|
|
(pp->fmt == CMIT_FMT_FULL || pp->fmt == CMIT_FMT_FULLER)) {
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_grow(sb, linelen + 80);
|
2014-10-04 18:54:50 +00:00
|
|
|
pp_user_info(pp, "Commit", sb, name, encoding);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
pretty: split oneline and email subject printing
The pp_title_line() function is used for two formats: the oneline format
and the subject line of the email format. But most of the logic in the
function does not make any sense for oneline; it is about special
formatting of email headers.
Lumping the two formats together made sense long ago in 4234a76167
(Extend --pretty=oneline to cover the first paragraph, 2007-06-11), when
there was a lot of manual logic to paste lines together. But later,
88c44735ab (pretty: factor out format_subject(), 2008-12-27) pulled that
logic into its own function.
We can implement the oneline format by just calling that one function.
This makes the intention of the code much more clear, as we know we only
need to worry about those extra email options when dealing with actual
email.
While the intent here is cleanup, it is possible to trigger these cases
in practice by running format-patch with an explicit --oneline option.
But if you did, the results are basically nonsense. For example, with
the preserve_subject flag:
$ printf "%s\n" one two three | git commit --allow-empty -F -
$ git format-patch -1 --stdout -k | grep ^Subject
Subject: =?UTF-8?q?one=0Atwo=0Athree?=
$ git format-patch -1 --stdout -k --oneline --no-signature
2af7fbe one
two
three
Or with extra headers:
$ git format-patch -1 --stdout --cc=me --oneline --no-signature
2af7fbe one two three
Cc: me
So I'd actually consider this to be an improvement, though you are
probably crazy to use other formats with format-patch in the first place
(arguably it should forbid non-email formats entirely, but that's a
bigger change).
As a bonus, it eliminates some pointless extra allocations for the
oneline output. The email code, since it has to deal with wrapping,
formats into an extra auxiliary buffer. The speedup is tiny, though like
"rev-list --no-abbrev --format=oneline" seems to improve by a consistent
1-2% for me.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-20 00:28:51 +00:00
|
|
|
void pp_email_subject(struct pretty_print_context *pp,
|
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb,
|
|
|
|
const char *encoding,
|
|
|
|
int need_8bit_cte)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2012-10-18 14:43:33 +00:00
|
|
|
static const int max_length = 78; /* per rfc2047 */
|
2007-11-04 19:15:06 +00:00
|
|
|
struct strbuf title;
|
|
|
|
|
|
|
|
strbuf_init(&title, 80);
|
2011-05-26 22:28:17 +00:00
|
|
|
*msg_p = format_subject(&title, *msg_p,
|
|
|
|
pp->preserve_subject ? "\n" : " ");
|
2007-11-04 19:15:06 +00:00
|
|
|
|
|
|
|
strbuf_grow(sb, title.len + 1024);
|
pretty: drop print_email_subject flag
With one exception, the print_email_subject flag is set if and only if
the commit format is email based:
- in make_cover_letter() we set it along with CMIT_FMT_EMAIL
explicitly
- in show_log(), we set it if cmit_fmt_is_mail() is true. That covers
format-patch as well as "git log --format=email" (or mboxrd).
The one exception is "rev-list --format=email", which somewhat
nonsensically prints the author and date as email headers, but no
subject, like:
$ git rev-list --format=email HEAD
commit 64fc4c2cdd4db2645eaabb47aa4bac820b03cdba
From: Jeff King <peff@peff.net>
Date: Tue, 19 Mar 2024 19:39:26 -0400
this is the subject
this is the body
It's doubtful that this is a useful format at all (the "commit" lines
replace the "From" lines that would make it work as an actual mbox).
But I think that printing the subject as a header (like this patch does)
is the least surprising thing to do.
So let's drop this field, making the code a little simpler and easier to
reason about. Note that we do need to set the "rev" field of the
pretty_print_context in rev-list, since that is used to check for
subject_prefix, etc. It's not possible to set those fields via rev-list,
so we'll always just print "Subject: ". But unless we pass in our
rev_info, fmt_output_email_subject() would segfault trying to figure it
out.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-20 00:30:44 +00:00
|
|
|
fmt_output_email_subject(sb, pp->rev);
|
|
|
|
if (pp->encode_email_headers &&
|
|
|
|
needs_rfc2047_encoding(title.buf, title.len))
|
|
|
|
add_rfc2047(sb, title.buf, title.len,
|
|
|
|
encoding, RFC2047_SUBJECT);
|
|
|
|
else
|
|
|
|
strbuf_add_wrapped_bytes(sb, title.buf, title.len,
|
2012-10-18 14:43:33 +00:00
|
|
|
-last_line_length(sb), 1, max_length);
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 07:08:22 +00:00
|
|
|
if (need_8bit_cte == 0) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < pp->in_body_headers.nr; i++) {
|
|
|
|
if (has_non_ascii(pp->in_body_headers.items[i].string)) {
|
|
|
|
need_8bit_cte = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-15 00:10:09 +00:00
|
|
|
if (need_8bit_cte > 0) {
|
2007-11-04 19:15:06 +00:00
|
|
|
const char *header_fmt =
|
|
|
|
"MIME-Version: 1.0\n"
|
|
|
|
"Content-Type: text/plain; charset=%s\n"
|
|
|
|
"Content-Transfer-Encoding: 8bit\n";
|
|
|
|
strbuf_addf(sb, header_fmt, encoding);
|
|
|
|
}
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->after_subject) {
|
|
|
|
strbuf_addstr(sb, pp->after_subject);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
pretty: split oneline and email subject printing
The pp_title_line() function is used for two formats: the oneline format
and the subject line of the email format. But most of the logic in the
function does not make any sense for oneline; it is about special
formatting of email headers.
Lumping the two formats together made sense long ago in 4234a76167
(Extend --pretty=oneline to cover the first paragraph, 2007-06-11), when
there was a lot of manual logic to paste lines together. But later,
88c44735ab (pretty: factor out format_subject(), 2008-12-27) pulled that
logic into its own function.
We can implement the oneline format by just calling that one function.
This makes the intention of the code much more clear, as we know we only
need to worry about those extra email options when dealing with actual
email.
While the intent here is cleanup, it is possible to trigger these cases
in practice by running format-patch with an explicit --oneline option.
But if you did, the results are basically nonsense. For example, with
the preserve_subject flag:
$ printf "%s\n" one two three | git commit --allow-empty -F -
$ git format-patch -1 --stdout -k | grep ^Subject
Subject: =?UTF-8?q?one=0Atwo=0Athree?=
$ git format-patch -1 --stdout -k --oneline --no-signature
2af7fbe one
two
three
Or with extra headers:
$ git format-patch -1 --stdout --cc=me --oneline --no-signature
2af7fbe one two three
Cc: me
So I'd actually consider this to be an improvement, though you are
probably crazy to use other formats with format-patch in the first place
(arguably it should forbid non-email formats entirely, but that's a
bigger change).
As a bonus, it eliminates some pointless extra allocations for the
oneline output. The email code, since it has to deal with wrapping,
formats into an extra auxiliary buffer. The speedup is tiny, though like
"rev-list --no-abbrev --format=oneline" seems to improve by a consistent
1-2% for me.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-20 00:28:51 +00:00
|
|
|
|
|
|
|
strbuf_addch(sb, '\n');
|
teach format-patch to place other authors into in-body "From"
Format-patch generates emails with the "From" address set to the
author of each patch. If you are going to send the emails, however,
you would want to replace the author identity with yours (if they
are not the same), and bump the author identity to an in-body
header.
Normally this is handled by git-send-email, which does the
transformation before sending out the emails. However, some
workflows may not use send-email (e.g., imap-send, or a custom
script which feeds the mbox to a non-git MUA). They could each
implement this feature themselves, but getting it right is
non-trivial (one must canonicalize the identities by reversing any
RFC2047 encoding or RFC822 quoting of the headers, which has caused
many bugs in send-email over the years).
This patch takes a different approach: it teaches format-patch a
"--from" option which handles the ident check and in-body header
while it is writing out the email. It's much simpler to do at this
level (because we haven't done any quoting yet), and any workflow
based on format-patch can easily turn it on.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-07-03 07:08:22 +00:00
|
|
|
|
|
|
|
if (pp->in_body_headers.nr) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < pp->in_body_headers.nr; i++) {
|
|
|
|
strbuf_addstr(sb, pp->in_body_headers.items[i].string);
|
|
|
|
free(pp->in_body_headers.items[i].string);
|
|
|
|
}
|
|
|
|
string_list_clear(&pp->in_body_headers, 0);
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_release(&title);
|
|
|
|
}
|
|
|
|
|
2016-03-16 16:15:53 +00:00
|
|
|
static int pp_utf8_width(const char *start, const char *end)
|
|
|
|
{
|
|
|
|
int width = 0;
|
|
|
|
size_t remain = end - start;
|
|
|
|
|
|
|
|
while (remain) {
|
|
|
|
int n = utf8_width(&start, &remain);
|
|
|
|
if (n < 0 || !start)
|
|
|
|
return -1;
|
|
|
|
width += n;
|
|
|
|
}
|
|
|
|
return width;
|
|
|
|
}
|
|
|
|
|
2021-10-07 20:31:47 +00:00
|
|
|
static void strbuf_add_tabexpand(struct strbuf *sb, struct grep_opt *opt,
|
|
|
|
int color, int tabwidth, const char *line,
|
|
|
|
int linelen)
|
2016-03-16 16:15:53 +00:00
|
|
|
{
|
|
|
|
const char *tab;
|
|
|
|
|
|
|
|
while ((tab = memchr(line, '\t', linelen)) != NULL) {
|
|
|
|
int width = pp_utf8_width(line, tab);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If it wasn't well-formed utf8, or it
|
|
|
|
* had characters with badly defined
|
|
|
|
* width (control characters etc), just
|
|
|
|
* give up on trying to align things.
|
|
|
|
*/
|
|
|
|
if (width < 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
/* Output the data .. */
|
2021-10-07 20:31:47 +00:00
|
|
|
append_line_with_color(sb, opt, line, tab - line, color,
|
|
|
|
GREP_CONTEXT_BODY,
|
|
|
|
GREP_HEADER_FIELD_MAX);
|
2016-03-16 16:15:53 +00:00
|
|
|
|
|
|
|
/* .. and the de-tabified tab */
|
2016-03-29 23:05:39 +00:00
|
|
|
strbuf_addchars(sb, ' ', tabwidth - (width % tabwidth));
|
2016-03-16 16:15:53 +00:00
|
|
|
|
|
|
|
/* Skip over the printed part .. */
|
|
|
|
linelen -= tab + 1 - line;
|
|
|
|
line = tab + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print out everything after the last tab without
|
|
|
|
* worrying about width - there's nothing more to
|
|
|
|
* align.
|
|
|
|
*/
|
2021-10-07 20:31:47 +00:00
|
|
|
append_line_with_color(sb, opt, line, linelen, color, GREP_CONTEXT_BODY,
|
|
|
|
GREP_HEADER_FIELD_MAX);
|
2016-03-16 16:15:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* pp_handle_indent() prints out the intendation, and
|
|
|
|
* the whole line (without the final newline), after
|
|
|
|
* de-tabifying.
|
|
|
|
*/
|
|
|
|
static void pp_handle_indent(struct pretty_print_context *pp,
|
|
|
|
struct strbuf *sb, int indent,
|
|
|
|
const char *line, int linelen)
|
|
|
|
{
|
2021-10-07 20:31:47 +00:00
|
|
|
struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
|
|
|
|
|
2016-03-16 16:15:53 +00:00
|
|
|
strbuf_addchars(sb, ' ', indent);
|
|
|
|
if (pp->expand_tabs_in_log)
|
2021-10-07 20:31:47 +00:00
|
|
|
strbuf_add_tabexpand(sb, opt, pp->color, pp->expand_tabs_in_log,
|
|
|
|
line, linelen);
|
2016-03-16 16:15:53 +00:00
|
|
|
else
|
2021-10-07 20:31:47 +00:00
|
|
|
append_line_with_color(sb, opt, line, linelen, pp->color,
|
|
|
|
GREP_CONTEXT_BODY,
|
|
|
|
GREP_HEADER_FIELD_MAX);
|
2016-03-16 16:15:53 +00:00
|
|
|
}
|
|
|
|
|
2016-06-05 04:46:39 +00:00
|
|
|
static int is_mboxrd_from(const char *line, int len)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* a line matching /^From $/ here would only have len == 4
|
|
|
|
* at this point because is_empty_line would've trimmed all
|
|
|
|
* trailing space
|
|
|
|
*/
|
|
|
|
return len > 4 && starts_with(line + strspn(line, ">"), "From ");
|
|
|
|
}
|
|
|
|
|
2013-07-03 07:07:48 +00:00
|
|
|
void pp_remainder(struct pretty_print_context *pp,
|
2008-02-19 03:56:08 +00:00
|
|
|
const char **msg_p,
|
|
|
|
struct strbuf *sb,
|
|
|
|
int indent)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
2021-10-07 20:31:47 +00:00
|
|
|
struct grep_opt *opt = pp->rev ? &pp->rev->grep_filter : NULL;
|
2007-11-04 19:15:06 +00:00
|
|
|
int first = 1;
|
2021-10-07 20:31:47 +00:00
|
|
|
|
2007-11-04 19:15:06 +00:00
|
|
|
for (;;) {
|
|
|
|
const char *line = *msg_p;
|
|
|
|
int linelen = get_one_line(line);
|
|
|
|
*msg_p += linelen;
|
|
|
|
|
|
|
|
if (!linelen)
|
|
|
|
break;
|
|
|
|
|
2016-06-22 20:20:16 +00:00
|
|
|
if (is_blank_line(line, &linelen)) {
|
2007-11-04 19:15:06 +00:00
|
|
|
if (first)
|
|
|
|
continue;
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_SHORT)
|
2007-11-04 19:15:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
first = 0;
|
|
|
|
|
|
|
|
strbuf_grow(sb, linelen + indent + 20);
|
2014-09-07 07:06:42 +00:00
|
|
|
if (indent)
|
2016-03-16 16:15:53 +00:00
|
|
|
pp_handle_indent(pp, sb, indent, line, linelen);
|
2016-03-29 22:49:24 +00:00
|
|
|
else if (pp->expand_tabs_in_log)
|
2021-10-07 20:31:47 +00:00
|
|
|
strbuf_add_tabexpand(sb, opt, pp->color,
|
|
|
|
pp->expand_tabs_in_log, line,
|
|
|
|
linelen);
|
2016-06-05 04:46:39 +00:00
|
|
|
else {
|
|
|
|
if (pp->fmt == CMIT_FMT_MBOXRD &&
|
|
|
|
is_mboxrd_from(line, linelen))
|
|
|
|
strbuf_addch(sb, '>');
|
|
|
|
|
2021-10-07 20:31:47 +00:00
|
|
|
append_line_with_color(sb, opt, line, linelen,
|
|
|
|
pp->color, GREP_CONTEXT_BODY,
|
|
|
|
GREP_HEADER_FIELD_MAX);
|
2016-06-05 04:46:39 +00:00
|
|
|
}
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-03 07:07:48 +00:00
|
|
|
void pretty_print_commit(struct pretty_print_context *pp,
|
2011-05-26 22:27:49 +00:00
|
|
|
const struct commit *commit,
|
|
|
|
struct strbuf *sb)
|
2007-11-04 19:15:06 +00:00
|
|
|
{
|
|
|
|
unsigned long beginning_of_body;
|
|
|
|
int indent = 4;
|
2013-01-26 09:44:06 +00:00
|
|
|
const char *msg;
|
2014-06-10 21:39:30 +00:00
|
|
|
const char *reencoded;
|
2007-11-04 19:15:06 +00:00
|
|
|
const char *encoding;
|
2011-05-26 22:27:49 +00:00
|
|
|
int need_8bit_cte = pp->need_8bit_cte;
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_USERFORMAT) {
|
2023-03-28 13:58:51 +00:00
|
|
|
repo_format_commit_message(the_repository, commit,
|
|
|
|
user_format, sb, pp);
|
2007-11-04 19:15:06 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-18 00:12:55 +00:00
|
|
|
encoding = get_log_output_encoding();
|
2023-03-28 13:58:48 +00:00
|
|
|
msg = reencoded = repo_logmsg_reencode(the_repository, commit, NULL,
|
|
|
|
encoding);
|
2007-11-04 19:15:06 +00:00
|
|
|
|
2016-06-05 04:46:39 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE || cmit_fmt_is_mail(pp->fmt))
|
2007-11-04 19:15:06 +00:00
|
|
|
indent = 0;
|
|
|
|
|
2008-03-15 00:10:09 +00:00
|
|
|
/*
|
|
|
|
* We need to check and emit Content-type: to mark it
|
|
|
|
* as 8-bit if we haven't done so.
|
2007-11-04 19:15:06 +00:00
|
|
|
*/
|
2016-06-05 04:46:39 +00:00
|
|
|
if (cmit_fmt_is_mail(pp->fmt) && need_8bit_cte == 0) {
|
2007-11-04 19:15:06 +00:00
|
|
|
int i, ch, in_body;
|
|
|
|
|
|
|
|
for (in_body = i = 0; (ch = msg[i]); i++) {
|
|
|
|
if (!in_body) {
|
|
|
|
/* author could be non 7-bit ASCII but
|
|
|
|
* the log may be so; skip over the
|
|
|
|
* header part first.
|
|
|
|
*/
|
|
|
|
if (ch == '\n' && msg[i+1] == '\n')
|
|
|
|
in_body = 1;
|
|
|
|
}
|
|
|
|
else if (non_ascii(ch)) {
|
2008-03-15 00:10:09 +00:00
|
|
|
need_8bit_cte = 1;
|
2007-11-04 19:15:06 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-26 22:27:49 +00:00
|
|
|
pp_header(pp, encoding, commit, &msg, sb);
|
pretty: drop print_email_subject flag
With one exception, the print_email_subject flag is set if and only if
the commit format is email based:
- in make_cover_letter() we set it along with CMIT_FMT_EMAIL
explicitly
- in show_log(), we set it if cmit_fmt_is_mail() is true. That covers
format-patch as well as "git log --format=email" (or mboxrd).
The one exception is "rev-list --format=email", which somewhat
nonsensically prints the author and date as email headers, but no
subject, like:
$ git rev-list --format=email HEAD
commit 64fc4c2cdd4db2645eaabb47aa4bac820b03cdba
From: Jeff King <peff@peff.net>
Date: Tue, 19 Mar 2024 19:39:26 -0400
this is the subject
this is the body
It's doubtful that this is a useful format at all (the "commit" lines
replace the "From" lines that would make it work as an actual mbox).
But I think that printing the subject as a header (like this patch does)
is the least surprising thing to do.
So let's drop this field, making the code a little simpler and easier to
reason about. Note that we do need to set the "rev" field of the
pretty_print_context in rev-list, since that is used to check for
subject_prefix, etc. It's not possible to set those fields via rev-list,
so we'll always just print "Subject: ". But unless we pass in our
rev_info, fmt_output_email_subject() would segfault trying to figure it
out.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-20 00:30:44 +00:00
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE && !cmit_fmt_is_mail(pp->fmt)) {
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Skip excess blank lines at the beginning of body, if any... */
|
2016-06-22 20:20:16 +00:00
|
|
|
msg = skip_blank_lines(msg);
|
2007-11-04 19:15:06 +00:00
|
|
|
|
|
|
|
/* These formats treat the title line specially. */
|
pretty: split oneline and email subject printing
The pp_title_line() function is used for two formats: the oneline format
and the subject line of the email format. But most of the logic in the
function does not make any sense for oneline; it is about special
formatting of email headers.
Lumping the two formats together made sense long ago in 4234a76167
(Extend --pretty=oneline to cover the first paragraph, 2007-06-11), when
there was a lot of manual logic to paste lines together. But later,
88c44735ab (pretty: factor out format_subject(), 2008-12-27) pulled that
logic into its own function.
We can implement the oneline format by just calling that one function.
This makes the intention of the code much more clear, as we know we only
need to worry about those extra email options when dealing with actual
email.
While the intent here is cleanup, it is possible to trigger these cases
in practice by running format-patch with an explicit --oneline option.
But if you did, the results are basically nonsense. For example, with
the preserve_subject flag:
$ printf "%s\n" one two three | git commit --allow-empty -F -
$ git format-patch -1 --stdout -k | grep ^Subject
Subject: =?UTF-8?q?one=0Atwo=0Athree?=
$ git format-patch -1 --stdout -k --oneline --no-signature
2af7fbe one
two
three
Or with extra headers:
$ git format-patch -1 --stdout --cc=me --oneline --no-signature
2af7fbe one two three
Cc: me
So I'd actually consider this to be an improvement, though you are
probably crazy to use other formats with format-patch in the first place
(arguably it should forbid non-email formats entirely, but that's a
bigger change).
As a bonus, it eliminates some pointless extra allocations for the
oneline output. The email code, since it has to deal with wrapping,
formats into an extra auxiliary buffer. The speedup is tiny, though like
"rev-list --no-abbrev --format=oneline" seems to improve by a consistent
1-2% for me.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2024-03-20 00:28:51 +00:00
|
|
|
if (pp->fmt == CMIT_FMT_ONELINE) {
|
|
|
|
msg = format_subject(sb, msg, " ");
|
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
} else if (cmit_fmt_is_mail(pp->fmt))
|
|
|
|
pp_email_subject(pp, &msg, sb, encoding, need_8bit_cte);
|
2007-11-04 19:15:06 +00:00
|
|
|
|
|
|
|
beginning_of_body = sb->len;
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE)
|
|
|
|
pp_remainder(pp, &msg, sb, indent);
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_rtrim(sb);
|
|
|
|
|
|
|
|
/* Make sure there is an EOLN for the non-oneline case */
|
2011-05-26 22:27:49 +00:00
|
|
|
if (pp->fmt != CMIT_FMT_ONELINE)
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addch(sb, '\n');
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The caller may append additional body text in e-mail
|
|
|
|
* format. Make sure we did not strip the blank line
|
|
|
|
* between the header and the body.
|
|
|
|
*/
|
2016-06-05 04:46:39 +00:00
|
|
|
if (cmit_fmt_is_mail(pp->fmt) && sb->len <= beginning_of_body)
|
2007-11-04 19:15:06 +00:00
|
|
|
strbuf_addch(sb, '\n');
|
2009-10-09 10:21:57 +00:00
|
|
|
|
2023-03-28 13:58:48 +00:00
|
|
|
repo_unuse_commit_buffer(the_repository, commit, reencoded);
|
2007-11-04 19:15:06 +00:00
|
|
|
}
|
2011-05-26 22:27:24 +00:00
|
|
|
|
|
|
|
void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
|
|
|
|
struct strbuf *sb)
|
|
|
|
{
|
|
|
|
struct pretty_print_context pp = {0};
|
2011-05-26 22:27:49 +00:00
|
|
|
pp.fmt = fmt;
|
|
|
|
pretty_print_commit(&pp, commit, sb);
|
2011-05-26 22:27:24 +00:00
|
|
|
}
|