Merge branch 'db/cover-letter'

* db/cover-letter:
  Improve collection of information for format-patch --cover-letter
  Add API access to shortlog
  t4014: Replace sed's non-standard 'Q' by standard 'q'
  Support a --cc=<email> option in format-patch
  Combine To: and Cc: headers
  Fix format.headers not ending with a newline
  Add tests for extra headers in format-patch
  Add a --cover-letter option to format-patch
  Export some email and pretty-printing functions
  Improve message-id generation flow control for format-patch
  Add more tests for format-patch

Conflicts:

	builtin-log.c
	builtin-shortlog.c
	pretty.c
This commit is contained in:
Junio C Hamano 2008-02-27 12:06:41 -08:00
commit 992221d05e
12 changed files with 702 additions and 237 deletions

View File

@ -10,13 +10,15 @@ SYNOPSIS
--------
[verse]
'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
[--attach[=<boundary>] | --inline[=<boundary>]]
[-s | --signoff] [<common diff options>]
[-n | --numbered | -N | --no-numbered]
[--start-number <n>] [--numbered-files]
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
[--ignore-if-in-upstream]
[--subject-prefix=Subject-Prefix]
[--attach[=<boundary>] | --inline[=<boundary>]]
[-s | --signoff] [<common diff options>]
[-n | --numbered | -N | --no-numbered]
[--start-number <n>] [--numbered-files]
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
[--ignore-if-in-upstream]
[--subject-prefix=Subject-Prefix]
[--cc=<email>]
[--cover-letter]
[ <since> | <revision range> ]
DESCRIPTION
@ -135,6 +137,15 @@ include::diff-options.txt[]
allows for useful naming of a patch series, and can be
combined with the --numbered option.
--cc=<email>::
Add a "Cc:" header to the email headers. This is in addition
to any configured headers, and may be used multiple times.
--cover-letter::
Generate a cover letter template. You still have to fill in
a description, but the shortlog and the diffstat will be
generated for you.
--suffix=.<sfx>::
Instead of using `.patch` as the suffix for generated
filenames, use specified suffix. A common alternative is

View File

@ -15,6 +15,8 @@
#include "reflog-walk.h"
#include "patch-ids.h"
#include "refs.h"
#include "run-command.h"
#include "shortlog.h"
static int default_show_root = 1;
static const char *fmt_patch_subject_prefix = "PATCH";
@ -428,24 +430,47 @@ static int istitlechar(char c)
(c >= '0' && c <= '9') || c == '.' || c == '_';
}
static char *extra_headers = NULL;
static int extra_headers_size = 0;
static const char *fmt_patch_suffix = ".patch";
static int numbered = 0;
static int auto_number = 0;
static char **extra_hdr;
static int extra_hdr_nr;
static int extra_hdr_alloc;
static char **extra_to;
static int extra_to_nr;
static int extra_to_alloc;
static char **extra_cc;
static int extra_cc_nr;
static int extra_cc_alloc;
static void add_header(const char *value)
{
int len = strlen(value);
while (value[len - 1] == '\n')
len--;
if (!strncasecmp(value, "to: ", 4)) {
ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
return;
}
if (!strncasecmp(value, "cc: ", 4)) {
ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
return;
}
ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
}
static int git_format_config(const char *var, const char *value)
{
if (!strcmp(var, "format.headers")) {
int len;
if (!value)
die("format.headers without value");
len = strlen(value);
extra_headers_size += len + 1;
extra_headers = xrealloc(extra_headers, extra_headers_size);
extra_headers[extra_headers_size - len - 1] = 0;
strcat(extra_headers, value);
add_header(value);
return 0;
}
if (!strcmp(var, "format.suffix")) {
@ -470,74 +495,81 @@ static int git_format_config(const char *var, const char *value)
}
static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
int numbered_files)
static const char *get_oneline_for_filename(struct commit *commit,
int keep_subject)
{
char filename[PATH_MAX];
static char filename[PATH_MAX];
char *sol;
int len = 0;
int suffix_len = strlen(fmt_patch_suffix) + 1;
sol = strstr(commit->buffer, "\n\n");
if (!sol)
filename[0] = '\0';
else {
int j, space = 0;
sol += 2;
/* strip [PATCH] or [PATCH blabla] */
if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
char *eos = strchr(sol + 6, ']');
if (eos) {
while (isspace(*eos))
eos++;
sol = eos;
}
}
for (j = 0;
j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
len < sizeof(filename) - suffix_len &&
sol[j] && sol[j] != '\n';
j++) {
if (istitlechar(sol[j])) {
if (space) {
filename[len++] = '-';
space = 0;
}
filename[len++] = sol[j];
if (sol[j] == '.')
while (sol[j + 1] == '.')
j++;
} else
space = 1;
}
while (filename[len - 1] == '.'
|| filename[len - 1] == '-')
len--;
filename[len] = '\0';
}
return filename;
}
static FILE *realstdout = NULL;
static const char *output_directory = NULL;
static int reopen_stdout(const char *oneline, int nr, int total)
{
char filename[PATH_MAX];
int len = 0;
int suffix_len = strlen(fmt_patch_suffix) + 1;
if (output_directory) {
if (strlen(output_directory) >=
len = snprintf(filename, sizeof(filename), "%s",
output_directory);
if (len >=
sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
return error("name of output directory is too long");
strlcpy(filename, output_directory, sizeof(filename) - suffix_len);
len = strlen(filename);
if (filename[len - 1] != '/')
filename[len++] = '/';
}
if (numbered_files) {
sprintf(filename + len, "%d", nr);
len = strlen(filename);
} else {
sprintf(filename + len, "%04d", nr);
len = strlen(filename);
sol = strstr(commit->buffer, "\n\n");
if (sol) {
int j, space = 1;
sol += 2;
/* strip [PATCH] or [PATCH blabla] */
if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
char *eos = strchr(sol + 6, ']');
if (eos) {
while (isspace(*eos))
eos++;
sol = eos;
}
}
for (j = 0;
j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
len < sizeof(filename) - suffix_len &&
sol[j] && sol[j] != '\n';
j++) {
if (istitlechar(sol[j])) {
if (space) {
filename[len++] = '-';
space = 0;
}
filename[len++] = sol[j];
if (sol[j] == '.')
while (sol[j + 1] == '.')
j++;
} else
space = 1;
}
while (filename[len - 1] == '.'
|| filename[len - 1] == '-')
len--;
filename[len] = 0;
}
if (len + suffix_len >= sizeof(filename))
return error("Patch pathname too long");
if (!oneline)
len += sprintf(filename + len, "%d", nr);
else {
len += sprintf(filename + len, "%04d-", nr);
len += snprintf(filename + len, sizeof(filename) - len - 1
- suffix_len, "%s", oneline);
strcpy(filename + len, fmt_patch_suffix);
}
@ -594,16 +626,89 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
o2->flags = flags2;
}
static void gen_message_id(char *dest, unsigned int length, char *base)
static void gen_message_id(struct rev_info *info, char *base)
{
const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
const char *email_start = strrchr(committer, '<');
const char *email_end = strrchr(committer, '>');
if(!email_start || !email_end || email_start > email_end - 1)
struct strbuf buf;
if (!email_start || !email_end || email_start > email_end - 1)
die("Could not extract email from committer identity.");
snprintf(dest, length, "%s.%lu.git.%.*s", base,
(unsigned long) time(NULL),
(int)(email_end - email_start - 1), email_start + 1);
strbuf_init(&buf, 0);
strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
(unsigned long) time(NULL),
(int)(email_end - email_start - 1), email_start + 1);
info->message_id = strbuf_detach(&buf, NULL);
}
static void make_cover_letter(struct rev_info *rev, int use_stdout,
int numbered, int numbered_files,
struct commit *origin,
int nr, struct commit **list, struct commit *head)
{
const char *committer;
const char *origin_sha1, *head_sha1;
const char *argv[7];
const char *subject_start = NULL;
const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
const char *msg;
const char *extra_headers = rev->extra_headers;
struct shortlog log;
struct strbuf sb;
int i;
const char *encoding = "utf-8";
if (rev->commit_format != CMIT_FMT_EMAIL)
die("Cover letter needs email format");
if (!use_stdout && reopen_stdout(numbered_files ?
NULL : "cover-letter", 0, rev->total))
return;
head_sha1 = sha1_to_hex(head->object.sha1);
log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers);
committer = git_committer_info(0);
msg = body;
strbuf_init(&sb, 0);
pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
encoding);
pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
encoding, 0);
pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
printf("%s\n", sb.buf);
strbuf_release(&sb);
shortlog_init(&log);
for (i = 0; i < nr; i++)
shortlog_add_commit(&log, list[i]);
shortlog_output(&log);
/*
* We can only do diffstat with a unique reference point
*/
if (!origin)
return;
origin_sha1 = sha1_to_hex(origin->object.sha1);
argv[0] = "diff";
argv[1] = "--stat";
argv[2] = "--summary";
argv[3] = head_sha1;
argv[4] = "--not";
argv[5] = origin_sha1;
argv[6] = "--";
argv[7] = NULL;
fflush(stdout);
run_command_v_opt(argv, RUN_GIT_CMD);
fflush(stdout);
printf("\n");
}
static const char *clean_message_id(const char *msg_id)
@ -641,11 +746,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int subject_prefix = 0;
int ignore_if_in_upstream = 0;
int thread = 0;
int cover_letter = 0;
int boundary_count = 0;
struct commit *origin = NULL, *head = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
char *add_signoff = NULL;
char message_id[1024];
char ref_message_id[1024];
struct strbuf buf;
git_config(git_format_config);
init_revisions(&rev, prefix);
@ -658,7 +765,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
rev.subject_prefix = fmt_patch_subject_prefix;
rev.extra_headers = extra_headers;
/*
* Parse the arguments before setup_revisions(), or something
@ -686,6 +792,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
die("Need a number for --start-number");
start_number = strtol(argv[i], NULL, 10);
}
else if (!prefixcmp(argv[i], "--cc=")) {
ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
}
else if (!strcmp(argv[i], "-k") ||
!strcmp(argv[i], "--keep-subject")) {
keep_subject = 1;
@ -742,11 +852,44 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.subject_prefix = argv[i] + 17;
} else if (!prefixcmp(argv[i], "--suffix="))
fmt_patch_suffix = argv[i] + 9;
else if (!strcmp(argv[i], "--cover-letter"))
cover_letter = 1;
else
argv[j++] = argv[i];
}
argc = j;
strbuf_init(&buf, 0);
for (i = 0; i < extra_hdr_nr; i++) {
strbuf_addstr(&buf, extra_hdr[i]);
strbuf_addch(&buf, '\n');
}
if (extra_to_nr)
strbuf_addstr(&buf, "To: ");
for (i = 0; i < extra_to_nr; i++) {
if (i)
strbuf_addstr(&buf, " ");
strbuf_addstr(&buf, extra_to[i]);
if (i + 1 < extra_to_nr)
strbuf_addch(&buf, ',');
strbuf_addch(&buf, '\n');
}
if (extra_cc_nr)
strbuf_addstr(&buf, "Cc: ");
for (i = 0; i < extra_cc_nr; i++) {
if (i)
strbuf_addstr(&buf, " ");
strbuf_addstr(&buf, extra_cc[i]);
if (i + 1 < extra_cc_nr)
strbuf_addch(&buf, ',');
strbuf_addch(&buf, '\n');
}
rev.extra_headers = strbuf_detach(&buf, 0);
if (start_number < 0)
start_number = 1;
if (numbered && keep_subject)
@ -793,6 +936,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* get_revision() to do the usual traversal.
*/
}
if (cover_letter) {
/* remember the range */
int i;
for (i = 0; i < rev.pending.nr; i++) {
struct object *o = rev.pending.objects[i].item;
if (!(o->flags & UNINTERESTING))
head = (struct commit *)o;
}
/* We can't generate a cover letter without any patches */
if (!head)
return 0;
}
if (ignore_if_in_upstream)
get_patch_ids(&rev, &ids, prefix);
@ -802,7 +957,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
rev.boundary = 1;
while ((commit = get_revision(&rev)) != NULL) {
if (commit->object.flags & BOUNDARY) {
fprintf(stderr, "Boundary %s\n", sha1_to_hex(commit->object.sha1));
boundary_count++;
origin = (boundary_count == 1) ? commit : NULL;
continue;
}
/* ignore merges */
if (commit->parents && commit->parents->next)
continue;
@ -820,29 +983,42 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
rev.add_signoff = add_signoff;
if (in_reply_to)
rev.ref_message_id = clean_message_id(in_reply_to);
if (cover_letter) {
if (thread)
gen_message_id(&rev, "cover");
make_cover_letter(&rev, use_stdout, numbered, numbered_files,
origin, nr, list, head);
total++;
start_number--;
}
rev.add_signoff = add_signoff;
while (0 <= --nr) {
int shown;
commit = list[nr];
rev.nr = total - nr + (start_number - 1);
/* Make the second and subsequent mails replies to the first */
if (thread) {
if (nr == (total - 2)) {
strncpy(ref_message_id, message_id,
sizeof(ref_message_id));
ref_message_id[sizeof(ref_message_id)-1]='\0';
rev.ref_message_id = ref_message_id;
/* Have we already had a message ID? */
if (rev.message_id) {
/*
* If we've got the ID to be a reply
* to, discard the current ID;
* otherwise, make everything a reply
* to that.
*/
if (rev.ref_message_id)
free(rev.message_id);
else
rev.ref_message_id = rev.message_id;
}
gen_message_id(message_id, sizeof(message_id),
sha1_to_hex(commit->object.sha1));
rev.message_id = message_id;
gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
}
if (!use_stdout)
if (reopen_stdout(commit, rev.nr, keep_subject,
numbered_files))
die("Failed to create output files");
if (!use_stdout && reopen_stdout(numbered_files ? NULL :
get_oneline_for_filename(commit, keep_subject),
rev.nr, rev.total))
die("Failed to create output files");
shown = log_tree_commit(&rev, commit);
free(commit->buffer);
commit->buffer = NULL;

View File

@ -6,13 +6,11 @@
#include "revision.h"
#include "utf8.h"
#include "mailmap.h"
#include "shortlog.h"
static const char shortlog_usage[] =
"git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
static char *common_repo_prefix;
static int email;
static int compare_by_number(const void *a1, const void *a2)
{
const struct path_list_item *i1 = a1, *i2 = a2;
@ -26,13 +24,11 @@ static int compare_by_number(const void *a1, const void *a2)
return -1;
}
static struct path_list mailmap = {NULL, 0, 0, 0};
static void insert_one_record(struct path_list *list,
static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
{
const char *dot3 = common_repo_prefix;
const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct path_list_item *item;
struct path_list *onelines;
@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list,
eoemail = strchr(boemail, '>');
if (!eoemail)
return;
if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) {
if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
while (author < boemail && isspace(*author))
author++;
for (len = 0;
@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list,
else
len = strlen(namebuf);
if (email) {
if (log->email) {
size_t room = sizeof(namebuf) - len - 1;
int maillen = eoemail - boemail + 1;
snprintf(namebuf + len, room, " %.*s", maillen, boemail);
}
buffer = xstrdup(namebuf);
item = path_list_insert(buffer, list);
item = path_list_insert(buffer, &log->list);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct path_list));
else
@ -114,7 +110,7 @@ static void insert_one_record(struct path_list *list,
onelines->items[onelines->nr++].path = buffer;
}
static void read_from_stdin(struct path_list *list)
static void read_from_stdin(struct shortlog *log)
{
char author[1024], oneline[1024];
@ -128,39 +124,43 @@ static void read_from_stdin(struct path_list *list)
while (fgets(oneline, sizeof(oneline), stdin) &&
oneline[0] == '\n')
; /* discard blanks */
insert_one_record(list, author + 8, oneline);
insert_one_record(log, author + 8, oneline);
}
}
static void get_from_rev(struct rev_info *rev, struct path_list *list)
void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
const char *author = NULL, *buffer;
buffer = commit->buffer;
while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
if (eol == NULL)
eol = buffer + strlen(buffer);
else
eol++;
if (!prefixcmp(buffer, "author "))
author = buffer + 7;
buffer = eol;
}
if (!author)
die("Missing author: %s",
sha1_to_hex(commit->object.sha1));
if (*buffer)
buffer++;
insert_one_record(log, author, !*buffer ? "<none>" : buffer);
}
static void get_from_rev(struct rev_info *rev, struct shortlog *log)
{
struct commit *commit;
if (prepare_revision_walk(rev))
die("revision walk setup failed");
while ((commit = get_revision(rev)) != NULL) {
const char *author = NULL, *buffer;
buffer = commit->buffer;
while (*buffer && *buffer != '\n') {
const char *eol = strchr(buffer, '\n');
if (eol == NULL)
eol = buffer + strlen(buffer);
else
eol++;
if (!prefixcmp(buffer, "author "))
author = buffer + 7;
buffer = eol;
}
if (!author)
die("Missing author: %s",
sha1_to_hex(commit->object.sha1));
if (*buffer)
buffer++;
insert_one_record(list, author, !*buffer ? "<none>" : buffer);
}
while ((commit = get_revision(rev)) != NULL)
shortlog_add_commit(log, commit);
}
static int parse_uint(char const **arg, int comma)
@ -212,29 +212,38 @@ static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
die(wrap_arg_usage);
}
void shortlog_init(struct shortlog *log)
{
memset(log, 0, sizeof(*log));
read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
log->list.strdup_paths = 1;
log->wrap = DEFAULT_WRAPLEN;
log->in1 = DEFAULT_INDENT1;
log->in2 = DEFAULT_INDENT2;
}
int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
struct shortlog log;
struct rev_info rev;
struct path_list list = { NULL, 0, 0, 1 };
int i, j, sort_by_number = 0, summary = 0;
int wrap_lines = 0;
int wrap = DEFAULT_WRAPLEN;
int in1 = DEFAULT_INDENT1;
int in2 = DEFAULT_INDENT2;
shortlog_init(&log);
/* since -n is a shadowed rev argument, parse our args first */
while (argc > 1) {
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
sort_by_number = 1;
log.sort_by_number = 1;
else if (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--summary"))
summary = 1;
log.summary = 1;
else if (!strcmp(argv[1], "-e") ||
!strcmp(argv[1], "--email"))
email = 1;
log.email = 1;
else if (!prefixcmp(argv[1], "-w")) {
wrap_lines = 1;
parse_wrap_args(argv[1], &in1, &in2, &wrap);
log.wrap_lines = 1;
parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
}
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
usage(shortlog_usage);
@ -248,34 +257,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
if (argc > 1)
die ("unrecognized argument: %s", argv[1]);
read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
/* assume HEAD if from a tty */
if (!rev.pending.nr && isatty(0))
add_head_to_pending(&rev);
if (rev.pending.nr == 0) {
read_from_stdin(&list);
read_from_stdin(&log);
}
else
get_from_rev(&rev, &list);
get_from_rev(&rev, &log);
if (sort_by_number)
qsort(list.items, list.nr, sizeof(struct path_list_item),
shortlog_output(&log);
return 0;
}
void shortlog_output(struct shortlog *log)
{
int i, j;
if (log->sort_by_number)
qsort(log->list.items, log->list.nr, sizeof(struct path_list_item),
compare_by_number);
for (i = 0; i < log->list.nr; i++) {
struct path_list *onelines = log->list.items[i].util;
for (i = 0; i < list.nr; i++) {
struct path_list *onelines = list.items[i].util;
if (summary) {
printf("%6d\t%s\n", onelines->nr, list.items[i].path);
if (log->summary) {
printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
} else {
printf("%s (%d):\n", list.items[i].path, onelines->nr);
printf("%s (%d):\n", log->list.items[i].path, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
const char *msg = onelines->items[j].path;
if (wrap_lines) {
int col = print_wrapped_text(msg, in1, in2, wrap);
if (col != wrap)
if (log->wrap_lines) {
int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
if (col != log->wrap)
putchar('\n');
}
else
@ -287,13 +300,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
onelines->strdup_paths = 1;
path_list_clear(onelines, 1);
free(onelines);
list.items[i].util = NULL;
log->list.items[i].util = NULL;
}
list.strdup_paths = 1;
path_list_clear(&list, 1);
mailmap.strdup_paths = 1;
path_list_clear(&mailmap, 1);
return 0;
log->list.strdup_paths = 1;
path_list_clear(&log->list, 1);
log->mailmap.strdup_paths = 1;
path_list_clear(&log->mailmap, 1);
}

View File

@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
int abbrev, const char *subject,
const char *after_subject, enum date_mode,
int non_ascii_present);
void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
const char *line, enum date_mode dmode,
const char *encoding);
void pp_title_line(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
const char *subject,
const char *after_subject,
const char *encoding,
int plain_non_ascii);
void pp_remainder(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
int indent);
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.

View File

@ -137,6 +137,72 @@ static int has_non_ascii(const char *s)
return 0;
}
void log_write_email_headers(struct rev_info *opt, const char *name,
const char **subject_p, const char **extra_headers_p)
{
const char *subject = NULL;
const char *extra_headers = opt->extra_headers;
if (opt->total > 0) {
static char buffer[64];
snprintf(buffer, sizeof(buffer),
"Subject: [%s %0*d/%d] ",
opt->subject_prefix,
digits_in_number(opt->total),
opt->nr, opt->total);
subject = buffer;
} else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
static char buffer[256];
snprintf(buffer, sizeof(buffer),
"Subject: [%s] ",
opt->subject_prefix);
subject = buffer;
} else {
subject = "Subject: ";
}
printf("From %s Mon Sep 17 00:00:00 2001\n", name);
if (opt->message_id)
printf("Message-Id: <%s>\n", opt->message_id);
if (opt->ref_message_id)
printf("In-Reply-To: <%s>\nReferences: <%s>\n",
opt->ref_message_id, opt->ref_message_id);
if (opt->mime_boundary) {
static char subject_buffer[1024];
static char buffer[1024];
snprintf(subject_buffer, sizeof(subject_buffer) - 1,
"%s"
"MIME-Version: 1.0\n"
"Content-Type: multipart/mixed;"
" boundary=\"%s%s\"\n"
"\n"
"This is a multi-part message in MIME "
"format.\n"
"--%s%s\n"
"Content-Type: text/plain; "
"charset=UTF-8; format=fixed\n"
"Content-Transfer-Encoding: 8bit\n\n",
extra_headers ? extra_headers : "",
mime_boundary_leader, opt->mime_boundary,
mime_boundary_leader, opt->mime_boundary);
extra_headers = subject_buffer;
snprintf(buffer, sizeof(buffer) - 1,
"--%s%s\n"
"Content-Type: text/x-patch;"
" name=\"%s.diff\"\n"
"Content-Transfer-Encoding: 8bit\n"
"Content-Disposition: %s;"
" filename=\"%s.diff\"\n\n",
mime_boundary_leader, opt->mime_boundary,
name,
opt->no_inline ? "attachment" : "inline",
name);
opt->diffopt.stat_sep = buffer;
}
*subject_p = subject;
*extra_headers_p = extra_headers;
}
void show_log(struct rev_info *opt, const char *sep)
{
struct strbuf msgbuf;
@ -188,64 +254,8 @@ void show_log(struct rev_info *opt, const char *sep)
*/
if (opt->commit_format == CMIT_FMT_EMAIL) {
char *sha1 = sha1_to_hex(commit->object.sha1);
if (opt->total > 0) {
static char buffer[64];
snprintf(buffer, sizeof(buffer),
"Subject: [%s %0*d/%d] ",
opt->subject_prefix,
digits_in_number(opt->total),
opt->nr, opt->total);
subject = buffer;
} else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
static char buffer[256];
snprintf(buffer, sizeof(buffer),
"Subject: [%s] ",
opt->subject_prefix);
subject = buffer;
} else {
subject = "Subject: ";
}
printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
if (opt->message_id)
printf("Message-Id: <%s>\n", opt->message_id);
if (opt->ref_message_id)
printf("In-Reply-To: <%s>\nReferences: <%s>\n",
opt->ref_message_id, opt->ref_message_id);
if (opt->mime_boundary) {
static char subject_buffer[1024];
static char buffer[1024];
snprintf(subject_buffer, sizeof(subject_buffer) - 1,
"%s"
"MIME-Version: 1.0\n"
"Content-Type: multipart/mixed;"
" boundary=\"%s%s\"\n"
"\n"
"This is a multi-part message in MIME "
"format.\n"
"--%s%s\n"
"Content-Type: text/plain; "
"charset=UTF-8; format=fixed\n"
"Content-Transfer-Encoding: 8bit\n\n",
extra_headers ? extra_headers : "",
mime_boundary_leader, opt->mime_boundary,
mime_boundary_leader, opt->mime_boundary);
extra_headers = subject_buffer;
snprintf(buffer, sizeof(buffer) - 1,
"--%s%s\n"
"Content-Type: text/x-patch;"
" name=\"%s.diff\"\n"
"Content-Transfer-Encoding: 8bit\n"
"Content-Disposition: %s;"
" filename=\"%s.diff\"\n\n",
mime_boundary_leader, opt->mime_boundary,
sha1,
opt->no_inline ? "attachment" : "inline",
sha1);
opt->diffopt.stat_sep = buffer;
}
log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
&subject, &extra_headers);
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
if (opt->commit_format != CMIT_FMT_ONELINE)

View File

@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *);
int log_tree_opt_parse(struct rev_info *, const char **, int);
void show_log(struct rev_info *opt, const char *sep);
void show_decorations(struct commit *commit);
void log_write_email_headers(struct rev_info *opt, const char *name,
const char **subject_p, const char **extra_headers_p);
#endif

View File

@ -110,9 +110,9 @@ needquote:
strbuf_addstr(sb, "?=");
}
static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
const char *line, enum date_mode dmode,
const char *encoding)
void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
const char *line, enum date_mode dmode,
const char *encoding)
{
char *date;
int namelen;
@ -621,23 +621,23 @@ static void pp_header(enum cmit_fmt fmt,
*/
if (!memcmp(line, "author ", 7)) {
strbuf_grow(sb, linelen + 80);
add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
}
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
strbuf_grow(sb, linelen + 80);
add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
}
}
}
static void pp_title_line(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
const char *subject,
const char *after_subject,
const char *encoding,
int plain_non_ascii)
void pp_title_line(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
const char *subject,
const char *after_subject,
const char *encoding,
int plain_non_ascii)
{
struct strbuf title;
@ -686,10 +686,10 @@ static void pp_title_line(enum cmit_fmt fmt,
strbuf_release(&title);
}
static void pp_remainder(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
int indent)
void pp_remainder(enum cmit_fmt fmt,
const char **msg_p,
struct strbuf *sb,
int indent)
{
int first = 1;
for (;;) {

View File

@ -75,7 +75,7 @@ struct rev_info {
struct log_info *loginfo;
int nr, total;
const char *mime_boundary;
const char *message_id;
char *message_id;
const char *ref_message_id;
const char *add_signoff;
const char *extra_headers;

26
shortlog.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef SHORTLOG_H
#define SHORTLOG_H
#include "path-list.h"
struct shortlog {
struct path_list list;
int summary;
int wrap_lines;
int sort_by_number;
int wrap;
int in1;
int in2;
char *common_repo_prefix;
int email;
struct path_list mailmap;
};
void shortlog_init(struct shortlog *log);
void shortlog_add_commit(struct shortlog *log, struct commit *commit);
void shortlog_output(struct shortlog *log);
#endif

View File

@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master
format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
config format.subjectprefix DIFFERENT_PREFIX
format-patch --inline --stdout initial..master^^
format-patch --stdout --cover-letter -n initial..master^
diff --abbrev initial..side
diff -r initial..side

View File

@ -0,0 +1,100 @@
$ git format-patch --stdout --cover-letter -n initial..master^
From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
From: C O Mitter <committer@example.com>
Date: Mon, 26 Jun 2006 00:05:00 +0000
Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE ***
*** BLURB HERE ***
A U Thor (2):
Second
Third
dir/sub | 4 ++++
file0 | 3 +++
file1 | 3 +++
file2 | 3 ---
4 files changed, 10 insertions(+), 3 deletions(-)
create mode 100644 file1
delete mode 100644 file2
From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
From: A U Thor <author@example.com>
Date: Mon, 26 Jun 2006 00:01:00 +0000
Subject: [DIFFERENT_PREFIX 1/2] Second
This is the second commit.
---
dir/sub | 2 ++
file0 | 3 +++
file2 | 3 ---
3 files changed, 5 insertions(+), 3 deletions(-)
delete mode 100644 file2
diff --git a/dir/sub b/dir/sub
index 35d242b..8422d40 100644
--- a/dir/sub
+++ b/dir/sub
@@ -1,2 +1,4 @@
A
B
+C
+D
diff --git a/file0 b/file0
index 01e79c3..b414108 100644
--- a/file0
+++ b/file0
@@ -1,3 +1,6 @@
1
2
3
+4
+5
+6
diff --git a/file2 b/file2
deleted file mode 100644
index 01e79c3..0000000
--- a/file2
+++ /dev/null
@@ -1,3 +0,0 @@
-1
-2
-3
--
g-i-t--v-e-r-s-i-o-n
From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
From: A U Thor <author@example.com>
Date: Mon, 26 Jun 2006 00:02:00 +0000
Subject: [DIFFERENT_PREFIX 2/2] Third
---
dir/sub | 2 ++
file1 | 3 +++
2 files changed, 5 insertions(+), 0 deletions(-)
create mode 100644 file1
diff --git a/dir/sub b/dir/sub
index 8422d40..cead32e 100644
--- a/dir/sub
+++ b/dir/sub
@@ -2,3 +2,5 @@ A
B
C
D
+E
+F
diff --git a/file1 b/file1
new file mode 100644
index 0000000..b1e6722
--- /dev/null
+++ b/file1
@@ -0,0 +1,3 @@
+A
+B
+C
--
g-i-t--v-e-r-s-i-o-n
$

View File

@ -88,4 +88,117 @@ test_expect_success 'replay did not screw up the log message' '
'
test_expect_success 'extra headers' '
git config format.headers "To: R. E. Cipient <rcipient@example.com>
" &&
git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
" &&
git format-patch --stdout master..side > patch2 &&
sed -e "/^$/q" patch2 > hdrs2 &&
grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
'
test_expect_success 'extra headers without newlines' '
git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
git format-patch --stdout master..side >patch3 &&
sed -e "/^$/q" patch3 > hdrs3 &&
grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
'
test_expect_success 'extra headers with multiple To:s' '
git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
git format-patch --stdout master..side > patch4 &&
sed -e "/^$/q" patch4 > hdrs4 &&
grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
'
test_expect_success 'additional command line cc' '
git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
grep "^ *S. E. Cipient <scipient@example.com>$" patch5
'
test_expect_success 'multiple files' '
rm -rf patches/ &&
git checkout side &&
git format-patch -o patches/ master &&
ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
'
test_expect_success 'thread' '
rm -rf patches/ &&
git checkout side &&
git format-patch --thread -o patches/ master &&
FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
for i in patches/0002-* patches/0003-*
do
grep "References: $FIRST_MID" $i &&
grep "In-Reply-To: $FIRST_MID" $i
done
'
test_expect_success 'thread in-reply-to' '
rm -rf patches/ &&
git checkout side &&
git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
FIRST_MID="<test.message>" &&
for i in patches/*
do
grep "References: $FIRST_MID" $i &&
grep "In-Reply-To: $FIRST_MID" $i
done
'
test_expect_success 'thread cover-letter' '
rm -rf patches/ &&
git checkout side &&
git format-patch --cover-letter --thread -o patches/ master &&
FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
for i in patches/0001-* patches/0002-* patches/0003-*
do
grep "References: $FIRST_MID" $i &&
grep "In-Reply-To: $FIRST_MID" $i
done
'
test_expect_success 'thread cover-letter in-reply-to' '
rm -rf patches/ &&
git checkout side &&
git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
FIRST_MID="<test.message>" &&
for i in patches/*
do
grep "References: $FIRST_MID" $i &&
grep "In-Reply-To: $FIRST_MID" $i
done
'
test_expect_success 'excessive subject' '
rm -rf patches/ &&
git checkout side &&
for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file &&
git update-index file &&
git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." &&
git format-patch -o patches/ master..side &&
ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch
'
test_done