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:
commit
992221d05e
@ -17,6 +17,8 @@ SYNOPSIS
|
|||||||
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
|
[--in-reply-to=Message-Id] [--suffix=.<sfx>]
|
||||||
[--ignore-if-in-upstream]
|
[--ignore-if-in-upstream]
|
||||||
[--subject-prefix=Subject-Prefix]
|
[--subject-prefix=Subject-Prefix]
|
||||||
|
[--cc=<email>]
|
||||||
|
[--cover-letter]
|
||||||
[ <since> | <revision range> ]
|
[ <since> | <revision range> ]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
@ -135,6 +137,15 @@ include::diff-options.txt[]
|
|||||||
allows for useful naming of a patch series, and can be
|
allows for useful naming of a patch series, and can be
|
||||||
combined with the --numbered option.
|
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>::
|
--suffix=.<sfx>::
|
||||||
Instead of using `.patch` as the suffix for generated
|
Instead of using `.patch` as the suffix for generated
|
||||||
filenames, use specified suffix. A common alternative is
|
filenames, use specified suffix. A common alternative is
|
||||||
|
288
builtin-log.c
288
builtin-log.c
@ -15,6 +15,8 @@
|
|||||||
#include "reflog-walk.h"
|
#include "reflog-walk.h"
|
||||||
#include "patch-ids.h"
|
#include "patch-ids.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
|
#include "run-command.h"
|
||||||
|
#include "shortlog.h"
|
||||||
|
|
||||||
static int default_show_root = 1;
|
static int default_show_root = 1;
|
||||||
static const char *fmt_patch_subject_prefix = "PATCH";
|
static const char *fmt_patch_subject_prefix = "PATCH";
|
||||||
@ -428,24 +430,47 @@ static int istitlechar(char c)
|
|||||||
(c >= '0' && c <= '9') || c == '.' || 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 const char *fmt_patch_suffix = ".patch";
|
||||||
static int numbered = 0;
|
static int numbered = 0;
|
||||||
static int auto_number = 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)
|
static int git_format_config(const char *var, const char *value)
|
||||||
{
|
{
|
||||||
if (!strcmp(var, "format.headers")) {
|
if (!strcmp(var, "format.headers")) {
|
||||||
int len;
|
|
||||||
|
|
||||||
if (!value)
|
if (!value)
|
||||||
die("format.headers without value");
|
die("format.headers without value");
|
||||||
len = strlen(value);
|
add_header(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);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (!strcmp(var, "format.suffix")) {
|
if (!strcmp(var, "format.suffix")) {
|
||||||
@ -470,38 +495,19 @@ static int git_format_config(const char *var, const char *value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static FILE *realstdout = NULL;
|
static const char *get_oneline_for_filename(struct commit *commit,
|
||||||
static const char *output_directory = NULL;
|
int keep_subject)
|
||||||
|
|
||||||
static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
|
|
||||||
int numbered_files)
|
|
||||||
{
|
{
|
||||||
char filename[PATH_MAX];
|
static char filename[PATH_MAX];
|
||||||
char *sol;
|
char *sol;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
int suffix_len = strlen(fmt_patch_suffix) + 1;
|
int suffix_len = strlen(fmt_patch_suffix) + 1;
|
||||||
|
|
||||||
if (output_directory) {
|
|
||||||
if (strlen(output_directory) >=
|
|
||||||
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");
|
sol = strstr(commit->buffer, "\n\n");
|
||||||
if (sol) {
|
if (!sol)
|
||||||
int j, space = 1;
|
filename[0] = '\0';
|
||||||
|
else {
|
||||||
|
int j, space = 0;
|
||||||
|
|
||||||
sol += 2;
|
sol += 2;
|
||||||
/* strip [PATCH] or [PATCH blabla] */
|
/* strip [PATCH] or [PATCH blabla] */
|
||||||
@ -534,10 +540,36 @@ static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
|
|||||||
while (filename[len - 1] == '.'
|
while (filename[len - 1] == '.'
|
||||||
|| filename[len - 1] == '-')
|
|| filename[len - 1] == '-')
|
||||||
len--;
|
len--;
|
||||||
filename[len] = 0;
|
filename[len] = '\0';
|
||||||
}
|
}
|
||||||
if (len + suffix_len >= sizeof(filename))
|
return filename;
|
||||||
return error("Patch pathname too long");
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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");
|
||||||
|
if (filename[len - 1] != '/')
|
||||||
|
filename[len++] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
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;
|
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 *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
|
||||||
const char *email_start = strrchr(committer, '<');
|
const char *email_start = strrchr(committer, '<');
|
||||||
const char *email_end = 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.");
|
die("Could not extract email from committer identity.");
|
||||||
snprintf(dest, length, "%s.%lu.git.%.*s", base,
|
strbuf_init(&buf, 0);
|
||||||
|
strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
|
||||||
(unsigned long) time(NULL),
|
(unsigned long) time(NULL),
|
||||||
(int)(email_end - email_start - 1), email_start + 1);
|
(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)
|
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 subject_prefix = 0;
|
||||||
int ignore_if_in_upstream = 0;
|
int ignore_if_in_upstream = 0;
|
||||||
int thread = 0;
|
int thread = 0;
|
||||||
|
int cover_letter = 0;
|
||||||
|
int boundary_count = 0;
|
||||||
|
struct commit *origin = NULL, *head = NULL;
|
||||||
const char *in_reply_to = NULL;
|
const char *in_reply_to = NULL;
|
||||||
struct patch_ids ids;
|
struct patch_ids ids;
|
||||||
char *add_signoff = NULL;
|
char *add_signoff = NULL;
|
||||||
char message_id[1024];
|
struct strbuf buf;
|
||||||
char ref_message_id[1024];
|
|
||||||
|
|
||||||
git_config(git_format_config);
|
git_config(git_format_config);
|
||||||
init_revisions(&rev, prefix);
|
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);
|
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
|
||||||
|
|
||||||
rev.subject_prefix = fmt_patch_subject_prefix;
|
rev.subject_prefix = fmt_patch_subject_prefix;
|
||||||
rev.extra_headers = extra_headers;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse the arguments before setup_revisions(), or something
|
* 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");
|
die("Need a number for --start-number");
|
||||||
start_number = strtol(argv[i], NULL, 10);
|
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") ||
|
else if (!strcmp(argv[i], "-k") ||
|
||||||
!strcmp(argv[i], "--keep-subject")) {
|
!strcmp(argv[i], "--keep-subject")) {
|
||||||
keep_subject = 1;
|
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;
|
rev.subject_prefix = argv[i] + 17;
|
||||||
} else if (!prefixcmp(argv[i], "--suffix="))
|
} else if (!prefixcmp(argv[i], "--suffix="))
|
||||||
fmt_patch_suffix = argv[i] + 9;
|
fmt_patch_suffix = argv[i] + 9;
|
||||||
|
else if (!strcmp(argv[i], "--cover-letter"))
|
||||||
|
cover_letter = 1;
|
||||||
else
|
else
|
||||||
argv[j++] = argv[i];
|
argv[j++] = argv[i];
|
||||||
}
|
}
|
||||||
argc = j;
|
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)
|
if (start_number < 0)
|
||||||
start_number = 1;
|
start_number = 1;
|
||||||
if (numbered && keep_subject)
|
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.
|
* 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)
|
if (ignore_if_in_upstream)
|
||||||
get_patch_ids(&rev, &ids, prefix);
|
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))
|
if (prepare_revision_walk(&rev))
|
||||||
die("revision walk setup failed");
|
die("revision walk setup failed");
|
||||||
|
rev.boundary = 1;
|
||||||
while ((commit = get_revision(&rev)) != NULL) {
|
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 */
|
/* ignore merges */
|
||||||
if (commit->parents && commit->parents->next)
|
if (commit->parents && commit->parents->next)
|
||||||
continue;
|
continue;
|
||||||
@ -820,28 +983,41 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
|
|||||||
numbered = 1;
|
numbered = 1;
|
||||||
if (numbered)
|
if (numbered)
|
||||||
rev.total = total + start_number - 1;
|
rev.total = total + start_number - 1;
|
||||||
rev.add_signoff = add_signoff;
|
|
||||||
if (in_reply_to)
|
if (in_reply_to)
|
||||||
rev.ref_message_id = clean_message_id(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) {
|
while (0 <= --nr) {
|
||||||
int shown;
|
int shown;
|
||||||
commit = list[nr];
|
commit = list[nr];
|
||||||
rev.nr = total - nr + (start_number - 1);
|
rev.nr = total - nr + (start_number - 1);
|
||||||
/* Make the second and subsequent mails replies to the first */
|
/* Make the second and subsequent mails replies to the first */
|
||||||
if (thread) {
|
if (thread) {
|
||||||
if (nr == (total - 2)) {
|
/* Have we already had a message ID? */
|
||||||
strncpy(ref_message_id, message_id,
|
if (rev.message_id) {
|
||||||
sizeof(ref_message_id));
|
/*
|
||||||
ref_message_id[sizeof(ref_message_id)-1]='\0';
|
* If we've got the ID to be a reply
|
||||||
rev.ref_message_id = ref_message_id;
|
* 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),
|
gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
|
||||||
sha1_to_hex(commit->object.sha1));
|
|
||||||
rev.message_id = message_id;
|
|
||||||
}
|
}
|
||||||
if (!use_stdout)
|
if (!use_stdout && reopen_stdout(numbered_files ? NULL :
|
||||||
if (reopen_stdout(commit, rev.nr, keep_subject,
|
get_oneline_for_filename(commit, keep_subject),
|
||||||
numbered_files))
|
rev.nr, rev.total))
|
||||||
die("Failed to create output files");
|
die("Failed to create output files");
|
||||||
shown = log_tree_commit(&rev, commit);
|
shown = log_tree_commit(&rev, commit);
|
||||||
free(commit->buffer);
|
free(commit->buffer);
|
||||||
|
@ -6,13 +6,11 @@
|
|||||||
#include "revision.h"
|
#include "revision.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "mailmap.h"
|
#include "mailmap.h"
|
||||||
|
#include "shortlog.h"
|
||||||
|
|
||||||
static const char shortlog_usage[] =
|
static const char shortlog_usage[] =
|
||||||
"git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
|
"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)
|
static int compare_by_number(const void *a1, const void *a2)
|
||||||
{
|
{
|
||||||
const struct path_list_item *i1 = a1, *i2 = 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;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct path_list mailmap = {NULL, 0, 0, 0};
|
static void insert_one_record(struct shortlog *log,
|
||||||
|
|
||||||
static void insert_one_record(struct path_list *list,
|
|
||||||
const char *author,
|
const char *author,
|
||||||
const char *oneline)
|
const char *oneline)
|
||||||
{
|
{
|
||||||
const char *dot3 = common_repo_prefix;
|
const char *dot3 = log->common_repo_prefix;
|
||||||
char *buffer, *p;
|
char *buffer, *p;
|
||||||
struct path_list_item *item;
|
struct path_list_item *item;
|
||||||
struct path_list *onelines;
|
struct path_list *onelines;
|
||||||
@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list,
|
|||||||
eoemail = strchr(boemail, '>');
|
eoemail = strchr(boemail, '>');
|
||||||
if (!eoemail)
|
if (!eoemail)
|
||||||
return;
|
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))
|
while (author < boemail && isspace(*author))
|
||||||
author++;
|
author++;
|
||||||
for (len = 0;
|
for (len = 0;
|
||||||
@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list,
|
|||||||
else
|
else
|
||||||
len = strlen(namebuf);
|
len = strlen(namebuf);
|
||||||
|
|
||||||
if (email) {
|
if (log->email) {
|
||||||
size_t room = sizeof(namebuf) - len - 1;
|
size_t room = sizeof(namebuf) - len - 1;
|
||||||
int maillen = eoemail - boemail + 1;
|
int maillen = eoemail - boemail + 1;
|
||||||
snprintf(namebuf + len, room, " %.*s", maillen, boemail);
|
snprintf(namebuf + len, room, " %.*s", maillen, boemail);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = xstrdup(namebuf);
|
buffer = xstrdup(namebuf);
|
||||||
item = path_list_insert(buffer, list);
|
item = path_list_insert(buffer, &log->list);
|
||||||
if (item->util == NULL)
|
if (item->util == NULL)
|
||||||
item->util = xcalloc(1, sizeof(struct path_list));
|
item->util = xcalloc(1, sizeof(struct path_list));
|
||||||
else
|
else
|
||||||
@ -114,7 +110,7 @@ static void insert_one_record(struct path_list *list,
|
|||||||
onelines->items[onelines->nr++].path = buffer;
|
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];
|
char author[1024], oneline[1024];
|
||||||
|
|
||||||
@ -128,17 +124,12 @@ static void read_from_stdin(struct path_list *list)
|
|||||||
while (fgets(oneline, sizeof(oneline), stdin) &&
|
while (fgets(oneline, sizeof(oneline), stdin) &&
|
||||||
oneline[0] == '\n')
|
oneline[0] == '\n')
|
||||||
; /* discard blanks */
|
; /* 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)
|
||||||
{
|
{
|
||||||
struct commit *commit;
|
|
||||||
|
|
||||||
if (prepare_revision_walk(rev))
|
|
||||||
die("revision walk setup failed");
|
|
||||||
while ((commit = get_revision(rev)) != NULL) {
|
|
||||||
const char *author = NULL, *buffer;
|
const char *author = NULL, *buffer;
|
||||||
|
|
||||||
buffer = commit->buffer;
|
buffer = commit->buffer;
|
||||||
@ -159,8 +150,17 @@ static void get_from_rev(struct rev_info *rev, struct path_list *list)
|
|||||||
sha1_to_hex(commit->object.sha1));
|
sha1_to_hex(commit->object.sha1));
|
||||||
if (*buffer)
|
if (*buffer)
|
||||||
buffer++;
|
buffer++;
|
||||||
insert_one_record(list, author, !*buffer ? "<none>" : 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)
|
||||||
|
shortlog_add_commit(log, commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_uint(char const **arg, int comma)
|
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);
|
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)
|
int cmd_shortlog(int argc, const char **argv, const char *prefix)
|
||||||
{
|
{
|
||||||
|
struct shortlog log;
|
||||||
struct rev_info rev;
|
struct rev_info rev;
|
||||||
struct path_list list = { NULL, 0, 0, 1 };
|
|
||||||
int i, j, sort_by_number = 0, summary = 0;
|
shortlog_init(&log);
|
||||||
int wrap_lines = 0;
|
|
||||||
int wrap = DEFAULT_WRAPLEN;
|
|
||||||
int in1 = DEFAULT_INDENT1;
|
|
||||||
int in2 = DEFAULT_INDENT2;
|
|
||||||
|
|
||||||
/* since -n is a shadowed rev argument, parse our args first */
|
/* since -n is a shadowed rev argument, parse our args first */
|
||||||
while (argc > 1) {
|
while (argc > 1) {
|
||||||
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
|
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
|
||||||
sort_by_number = 1;
|
log.sort_by_number = 1;
|
||||||
else if (!strcmp(argv[1], "-s") ||
|
else if (!strcmp(argv[1], "-s") ||
|
||||||
!strcmp(argv[1], "--summary"))
|
!strcmp(argv[1], "--summary"))
|
||||||
summary = 1;
|
log.summary = 1;
|
||||||
else if (!strcmp(argv[1], "-e") ||
|
else if (!strcmp(argv[1], "-e") ||
|
||||||
!strcmp(argv[1], "--email"))
|
!strcmp(argv[1], "--email"))
|
||||||
email = 1;
|
log.email = 1;
|
||||||
else if (!prefixcmp(argv[1], "-w")) {
|
else if (!prefixcmp(argv[1], "-w")) {
|
||||||
wrap_lines = 1;
|
log.wrap_lines = 1;
|
||||||
parse_wrap_args(argv[1], &in1, &in2, &wrap);
|
parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
|
||||||
}
|
}
|
||||||
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
|
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
|
||||||
usage(shortlog_usage);
|
usage(shortlog_usage);
|
||||||
@ -248,34 +257,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
|
|||||||
if (argc > 1)
|
if (argc > 1)
|
||||||
die ("unrecognized argument: %s", argv[1]);
|
die ("unrecognized argument: %s", argv[1]);
|
||||||
|
|
||||||
read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
|
|
||||||
|
|
||||||
/* assume HEAD if from a tty */
|
/* assume HEAD if from a tty */
|
||||||
if (!rev.pending.nr && isatty(0))
|
if (!rev.pending.nr && isatty(0))
|
||||||
add_head_to_pending(&rev);
|
add_head_to_pending(&rev);
|
||||||
if (rev.pending.nr == 0) {
|
if (rev.pending.nr == 0) {
|
||||||
read_from_stdin(&list);
|
read_from_stdin(&log);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
get_from_rev(&rev, &list);
|
get_from_rev(&rev, &log);
|
||||||
|
|
||||||
if (sort_by_number)
|
shortlog_output(&log);
|
||||||
qsort(list.items, list.nr, sizeof(struct path_list_item),
|
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);
|
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++) {
|
if (log->summary) {
|
||||||
struct path_list *onelines = list.items[i].util;
|
printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
|
||||||
|
|
||||||
if (summary) {
|
|
||||||
printf("%6d\t%s\n", onelines->nr, list.items[i].path);
|
|
||||||
} else {
|
} 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--) {
|
for (j = onelines->nr - 1; j >= 0; j--) {
|
||||||
const char *msg = onelines->items[j].path;
|
const char *msg = onelines->items[j].path;
|
||||||
|
|
||||||
if (wrap_lines) {
|
if (log->wrap_lines) {
|
||||||
int col = print_wrapped_text(msg, in1, in2, wrap);
|
int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
|
||||||
if (col != wrap)
|
if (col != log->wrap)
|
||||||
putchar('\n');
|
putchar('\n');
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -287,13 +300,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
|
|||||||
onelines->strdup_paths = 1;
|
onelines->strdup_paths = 1;
|
||||||
path_list_clear(onelines, 1);
|
path_list_clear(onelines, 1);
|
||||||
free(onelines);
|
free(onelines);
|
||||||
list.items[i].util = NULL;
|
log->list.items[i].util = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.strdup_paths = 1;
|
log->list.strdup_paths = 1;
|
||||||
path_list_clear(&list, 1);
|
path_list_clear(&log->list, 1);
|
||||||
mailmap.strdup_paths = 1;
|
log->mailmap.strdup_paths = 1;
|
||||||
path_list_clear(&mailmap, 1);
|
path_list_clear(&log->mailmap, 1);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
15
commit.h
15
commit.h
@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
|
|||||||
int abbrev, const char *subject,
|
int abbrev, const char *subject,
|
||||||
const char *after_subject, enum date_mode,
|
const char *after_subject, enum date_mode,
|
||||||
int non_ascii_present);
|
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
|
/** Removes the first commit from a list sorted by date, and adds all
|
||||||
* of its parents.
|
* of its parents.
|
||||||
|
126
log-tree.c
126
log-tree.c
@ -137,6 +137,72 @@ static int has_non_ascii(const char *s)
|
|||||||
return 0;
|
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)
|
void show_log(struct rev_info *opt, const char *sep)
|
||||||
{
|
{
|
||||||
struct strbuf msgbuf;
|
struct strbuf msgbuf;
|
||||||
@ -188,64 +254,8 @@ void show_log(struct rev_info *opt, const char *sep)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
if (opt->commit_format == CMIT_FMT_EMAIL) {
|
if (opt->commit_format == CMIT_FMT_EMAIL) {
|
||||||
char *sha1 = sha1_to_hex(commit->object.sha1);
|
log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
|
||||||
if (opt->total > 0) {
|
&subject, &extra_headers);
|
||||||
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;
|
|
||||||
}
|
|
||||||
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
|
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
|
||||||
fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
|
fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
|
||||||
if (opt->commit_format != CMIT_FMT_ONELINE)
|
if (opt->commit_format != CMIT_FMT_ONELINE)
|
||||||
|
@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *);
|
|||||||
int log_tree_opt_parse(struct rev_info *, const char **, int);
|
int log_tree_opt_parse(struct rev_info *, const char **, int);
|
||||||
void show_log(struct rev_info *opt, const char *sep);
|
void show_log(struct rev_info *opt, const char *sep);
|
||||||
void show_decorations(struct commit *commit);
|
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
|
#endif
|
||||||
|
10
pretty.c
10
pretty.c
@ -110,7 +110,7 @@ needquote:
|
|||||||
strbuf_addstr(sb, "?=");
|
strbuf_addstr(sb, "?=");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
|
void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
|
||||||
const char *line, enum date_mode dmode,
|
const char *line, enum date_mode dmode,
|
||||||
const char *encoding)
|
const char *encoding)
|
||||||
{
|
{
|
||||||
@ -621,17 +621,17 @@ static void pp_header(enum cmit_fmt fmt,
|
|||||||
*/
|
*/
|
||||||
if (!memcmp(line, "author ", 7)) {
|
if (!memcmp(line, "author ", 7)) {
|
||||||
strbuf_grow(sb, linelen + 80);
|
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) &&
|
if (!memcmp(line, "committer ", 10) &&
|
||||||
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
|
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
|
||||||
strbuf_grow(sb, linelen + 80);
|
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,
|
void pp_title_line(enum cmit_fmt fmt,
|
||||||
const char **msg_p,
|
const char **msg_p,
|
||||||
struct strbuf *sb,
|
struct strbuf *sb,
|
||||||
const char *subject,
|
const char *subject,
|
||||||
@ -686,7 +686,7 @@ static void pp_title_line(enum cmit_fmt fmt,
|
|||||||
strbuf_release(&title);
|
strbuf_release(&title);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pp_remainder(enum cmit_fmt fmt,
|
void pp_remainder(enum cmit_fmt fmt,
|
||||||
const char **msg_p,
|
const char **msg_p,
|
||||||
struct strbuf *sb,
|
struct strbuf *sb,
|
||||||
int indent)
|
int indent)
|
||||||
|
@ -75,7 +75,7 @@ struct rev_info {
|
|||||||
struct log_info *loginfo;
|
struct log_info *loginfo;
|
||||||
int nr, total;
|
int nr, total;
|
||||||
const char *mime_boundary;
|
const char *mime_boundary;
|
||||||
const char *message_id;
|
char *message_id;
|
||||||
const char *ref_message_id;
|
const char *ref_message_id;
|
||||||
const char *add_signoff;
|
const char *add_signoff;
|
||||||
const char *extra_headers;
|
const char *extra_headers;
|
||||||
|
26
shortlog.h
Normal file
26
shortlog.h
Normal 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
|
@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master
|
|||||||
format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
|
format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
|
||||||
config format.subjectprefix DIFFERENT_PREFIX
|
config format.subjectprefix DIFFERENT_PREFIX
|
||||||
format-patch --inline --stdout initial..master^^
|
format-patch --inline --stdout initial..master^^
|
||||||
|
format-patch --stdout --cover-letter -n initial..master^
|
||||||
|
|
||||||
diff --abbrev initial..side
|
diff --abbrev initial..side
|
||||||
diff -r initial..side
|
diff -r initial..side
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
$
|
@ -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
|
test_done
|
||||||
|
Loading…
Reference in New Issue
Block a user