summaryrefslogtreecommitdiff
path: root/builtin/log.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/log.c')
-rw-r--r--builtin/log.c475
1 files changed, 313 insertions, 162 deletions
diff --git a/builtin/log.c b/builtin/log.c
index 1ff2d8d162..a491d3dea0 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -5,6 +5,7 @@
* 2006 Junio Hamano
*/
#include "cache.h"
+#include "refs.h"
#include "color.h"
#include "commit.h"
#include "diff.h"
@@ -19,26 +20,37 @@
#include "remote.h"
#include "string-list.h"
#include "parse-options.h"
+#include "line-log.h"
#include "branch.h"
#include "streaming.h"
#include "version.h"
+#include "mailmap.h"
+#include "gpg-interface.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
static int default_abbrev_commit;
static int default_show_root = 1;
+static int default_follow;
static int decoration_style;
static int decoration_given;
+static int use_mailmap_config;
static const char *fmt_patch_subject_prefix = "PATCH";
static const char *fmt_pretty;
static const char * const builtin_log_usage[] = {
- N_("git log [<options>] [<since>..<until>] [[--] <path>...]\n")
- N_(" or: git show [options] <object>..."),
+ N_("git log [<options>] [<revision-range>] [[--] <path>...]"),
+ N_("git show [<options>] <object>..."),
NULL
};
+struct line_opt_callback_data {
+ struct rev_info *rev;
+ const char *prefix;
+ struct string_list args;
+};
+
static int parse_decoration_style(const char *var, const char *value)
{
switch (git_config_maybe_bool(var, value)) {
@@ -53,6 +65,8 @@ static int parse_decoration_style(const char *var, const char *value)
return DECORATE_FULL_REFS;
else if (!strcmp(value, "short"))
return DECORATE_SHORT_REFS;
+ else if (!strcmp(value, "auto"))
+ return (isatty(1) || pager_in_use()) ? DECORATE_SHORT_REFS : 0;
return -1;
}
@@ -66,17 +80,32 @@ static int decorate_callback(const struct option *opt, const char *arg, int unse
decoration_style = DECORATE_SHORT_REFS;
if (decoration_style < 0)
- die("invalid --decorate option: %s", arg);
+ die(_("invalid --decorate option: %s"), arg);
decoration_given = 1;
return 0;
}
+static int log_line_range_callback(const struct option *option, const char *arg, int unset)
+{
+ struct line_opt_callback_data *data = option->value;
+
+ if (!arg)
+ return -1;
+
+ data->rev->line_level_traverse = 1;
+ string_list_append(&data->args, arg);
+
+ return 0;
+}
+
static void cmd_log_init_defaults(struct rev_info *rev)
{
if (fmt_pretty)
get_commit_format(fmt_pretty, rev);
+ if (default_follow)
+ DIFF_OPT_SET(&rev->diffopt, DEFAULT_FOLLOW_RENAMES);
rev->verbose_header = 1;
DIFF_OPT_SET(&rev->diffopt, RECURSIVE);
rev->diffopt.stat_width = -1; /* use full terminal width */
@@ -87,35 +116,45 @@ static void cmd_log_init_defaults(struct rev_info *rev)
DIFF_OPT_SET(&rev->diffopt, ALLOW_TEXTCONV);
if (default_date_mode)
- rev->date_mode = parse_date_format(default_date_mode);
+ parse_date_format(default_date_mode, &rev->date_mode);
+ rev->diffopt.touched_flags = 0;
}
static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
struct rev_info *rev, struct setup_revision_opt *opt)
{
struct userformat_want w;
- int quiet = 0, source = 0;
+ int quiet = 0, source = 0, mailmap = 0;
+ static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
const struct option builtin_log_options[] = {
- OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")),
- OPT_BOOLEAN(0, "source", &source, N_("show source")),
+ OPT__QUIET(&quiet, N_("suppress diff output")),
+ OPT_BOOL(0, "source", &source, N_("show source")),
+ OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
{ OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
PARSE_OPT_OPTARG, decorate_callback},
+ OPT_CALLBACK('L', NULL, &line_cb, "n,m:file",
+ N_("Process line range n,m in file, counting from 1"),
+ log_line_range_callback),
OPT_END()
};
+ line_cb.rev = rev;
+ line_cb.prefix = prefix;
+
+ mailmap = use_mailmap_config;
argc = parse_options(argc, argv, prefix,
builtin_log_options, builtin_log_usage,
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
- argc = setup_revisions(argc, argv, rev, opt);
if (quiet)
rev->diffopt.output_format |= DIFF_FORMAT_NO_OUTPUT;
+ argc = setup_revisions(argc, argv, rev, opt);
/* Any arguments at this point are not recognized */
if (argc > 1)
- die("unrecognized argument: %s", argv[1]);
+ die(_("unrecognized argument: %s"), argv[1]);
memset(&w, 0, sizeof(w));
userformat_find_requirements(NULL, &w);
@@ -125,17 +164,18 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
if (rev->show_notes)
init_display_notes(&rev->notes_opt);
- if (rev->diffopt.pickaxe || rev->diffopt.filter)
+ if (rev->diffopt.pickaxe || rev->diffopt.filter ||
+ DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES))
rev->always_show_header = 0;
- if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
- rev->always_show_header = 0;
- if (rev->diffopt.pathspec.nr != 1)
- usage("git logs can only follow renames on one pathname at a time");
- }
if (source)
rev->show_source = 1;
+ if (mailmap) {
+ rev->mailmap = xcalloc(1, sizeof(struct string_list));
+ read_mailmap(rev->mailmap, NULL);
+ }
+
if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) {
/*
* "log --pretty=raw" is special; ignore UI oriented
@@ -151,6 +191,10 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix,
rev->show_decorations = 1;
load_ref_decorations(decoration_style);
}
+
+ if (rev->line_level_traverse)
+ line_log_init(rev, line_cb.prefix, &line_cb.args);
+
setup_pager();
}
@@ -196,7 +240,7 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list)
int i = revs->early_output;
int show_header = 1;
- sort_in_topological_order(&list, revs->lifo);
+ sort_in_topological_order(&list, revs->sort_order);
while (list && i) {
struct commit *commit = list->item;
switch (simplify_commit(revs, commit)) {
@@ -298,8 +342,7 @@ static int cmd_log_walk(struct rev_info *rev)
* retain that state information if replacing rev->diffopt in this loop
*/
while ((commit = get_revision(rev)) != NULL) {
- if (!log_tree_commit(rev, commit) &&
- rev->max_count >= 0)
+ if (!log_tree_commit(rev, commit) && rev->max_count >= 0)
/*
* We decremented max_count in get_revision,
* but we didn't actually show the commit.
@@ -307,8 +350,7 @@ static int cmd_log_walk(struct rev_info *rev)
rev->max_count++;
if (!rev->reflog_info) {
/* we allow cycles in reflog ancestry */
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
}
free_commit_list(commit->parents);
commit->parents = NULL;
@@ -329,6 +371,8 @@ static int cmd_log_walk(struct rev_info *rev)
static int git_log_config(const char *var, const char *value, void *cb)
{
+ const char *slot_name;
+
if (!strcmp(var, "format.pretty"))
return git_config_string(&fmt_pretty, var, value);
if (!strcmp(var, "format.subjectprefix"))
@@ -349,9 +393,21 @@ static int git_log_config(const char *var, const char *value, void *cb)
default_show_root = git_config_bool(var, value);
return 0;
}
- if (!prefixcmp(var, "color.decorate."))
- return parse_decorate_color_config(var, 15, value);
+ if (!strcmp(var, "log.follow")) {
+ default_follow = git_config_bool(var, value);
+ return 0;
+ }
+ if (skip_prefix(var, "color.decorate.", &slot_name))
+ return parse_decorate_color_config(var, slot_name, value);
+ if (!strcmp(var, "log.mailmap")) {
+ use_mailmap_config = git_config_bool(var, value);
+ return 0;
+ }
+ if (grep_config(var, value, cb) < 0)
+ return -1;
+ if (git_gpg_config(var, value, cb) < 0)
+ return -1;
return git_diff_ui_config(var, value, cb);
}
@@ -360,6 +416,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -386,10 +443,29 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
strbuf_release(&out);
}
-static int show_blob_object(const unsigned char *sha1, struct rev_info *rev)
+static int show_blob_object(const unsigned char *sha1, struct rev_info *rev, const char *obj_name)
{
+ unsigned char sha1c[20];
+ struct object_context obj_context;
+ char *buf;
+ unsigned long size;
+
fflush(stdout);
- return stream_blob_to_fd(1, sha1, NULL, 0);
+ if (!DIFF_OPT_TOUCHED(&rev->diffopt, ALLOW_TEXTCONV) ||
+ !DIFF_OPT_TST(&rev->diffopt, ALLOW_TEXTCONV))
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+
+ if (get_sha1_with_context(obj_name, 0, sha1c, &obj_context))
+ die(_("Not a valid object name %s"), obj_name);
+ if (!obj_context.path[0] ||
+ !textconv_object(obj_context.path, obj_context.mode, sha1c, 1, &buf, &size))
+ return stream_blob_to_fd(1, sha1, NULL, 0);
+
+ if (!buf)
+ die(_("git show %s: bad file"), obj_name);
+
+ write_or_die(1, buf, size);
+ return 0;
}
static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
@@ -407,7 +483,7 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
int new_offset = offset + 1;
while (new_offset < size && buf[new_offset++] != '\n')
; /* do nothing */
- if (!prefixcmp(buf + offset, "tagger "))
+ if (starts_with(buf + offset, "tagger "))
show_tagger(buf + offset + 7,
new_offset - offset - 7, rev);
offset = new_offset;
@@ -420,20 +496,21 @@ static int show_tag_object(const unsigned char *sha1, struct rev_info *rev)
}
static int show_tree_object(const unsigned char *sha1,
- const char *base, int baselen,
+ struct strbuf *base,
const char *pathname, unsigned mode, int stage, void *context)
{
printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
return 0;
}
-static void show_rev_tweak_rev(struct rev_info *rev, struct setup_revision_opt *opt)
+static void show_setup_revisions_tweak(struct rev_info *rev,
+ struct setup_revision_opt *opt)
{
if (rev->ignore_merges) {
/* There was no "-m" on the command line */
rev->ignore_merges = 0;
if (!rev->first_parent_only && !rev->combine_merges) {
- /* No "--first-parent", "-c", nor "--cc" */
+ /* No "--first-parent", "-c", or "--cc" */
rev->combine_merges = 1;
rev->dense_combined_merges = 1;
}
@@ -450,18 +527,19 @@ int cmd_show(int argc, const char **argv, const char *prefix)
struct pathspec match_all;
int i, count, ret = 0;
+ init_grep_defaults();
git_config(git_log_config, NULL);
- init_pathspec(&match_all, NULL);
+ memset(&match_all, 0, sizeof(match_all));
init_revisions(&rev, prefix);
rev.diff = 1;
rev.always_show_header = 1;
- rev.no_walk = 1;
+ rev.no_walk = REVISION_WALK_NO_WALK_SORTED;
rev.diffopt.stat_width = -1; /* Scale to real terminal size */
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
- opt.tweak = show_rev_tweak_rev;
+ opt.tweak = show_setup_revisions_tweak;
cmd_log_init(argc, argv, prefix, &rev, &opt);
if (!rev.no_walk)
@@ -474,7 +552,7 @@ int cmd_show(int argc, const char **argv, const char *prefix)
const char *name = objects[i].name;
switch (o->type) {
case OBJ_BLOB:
- ret = show_blob_object(o->sha1, NULL);
+ ret = show_blob_object(o->sha1, &rev, name);
break;
case OBJ_TAG: {
struct tag *t = (struct tag *)o;
@@ -530,6 +608,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -547,11 +626,28 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix)
return cmd_log_walk(&rev);
}
+static void log_setup_revisions_tweak(struct rev_info *rev,
+ struct setup_revision_opt *opt)
+{
+ if (DIFF_OPT_TST(&rev->diffopt, DEFAULT_FOLLOW_RENAMES) &&
+ rev->prune_data.nr == 1)
+ DIFF_OPT_SET(&rev->diffopt, FOLLOW_RENAMES);
+
+ /* Turn --cc/-c into -p --cc/-c when -p was not given */
+ if (!rev->diffopt.output_format && rev->combine_merges)
+ rev->diffopt.output_format = DIFF_FORMAT_PATCH;
+
+ /* Turn -m on when --cc/-c was given */
+ if (rev->combine_merges)
+ rev->ignore_merges = 0;
+}
+
int cmd_log(int argc, const char **argv, const char *prefix)
{
struct rev_info rev;
struct setup_revision_opt opt;
+ init_grep_defaults();
git_config(git_log_config, NULL);
init_revisions(&rev, prefix);
@@ -559,6 +655,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
memset(&opt, 0, sizeof(opt));
opt.def = "HEAD";
opt.revarg_opt = REVARG_COMMITTISH;
+ opt.tweak = log_setup_revisions_tweak;
cmd_log_init(argc, argv, prefix, &rev, &opt);
return cmd_log_walk(&rev);
}
@@ -600,6 +697,15 @@ static void add_header(const char *value)
static int thread;
static int do_signoff;
static const char *signature = git_version_string;
+static const char *signature_file;
+static int config_cover_letter;
+
+enum {
+ COVER_UNSET,
+ COVER_OFF,
+ COVER_ON,
+ COVER_AUTO
+};
static int git_format_config(const char *var, const char *value, void *cb)
{
@@ -624,7 +730,7 @@ static int git_format_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff") ||
- !strcmp(var, "color.ui")) {
+ !strcmp(var, "color.ui") || !strcmp(var, "diff.submodule")) {
return 0;
}
if (!strcmp(var, "format.numbered")) {
@@ -661,6 +767,16 @@ static int git_format_config(const char *var, const char *value, void *cb)
}
if (!strcmp(var, "format.signature"))
return git_config_string(&signature, var, value);
+ if (!strcmp(var, "format.signaturefile"))
+ return git_config_pathname(&signature_file, var, value);
+ if (!strcmp(var, "format.coverletter")) {
+ if (value && !strcasecmp(value, "auto")) {
+ config_cover_letter = COVER_AUTO;
+ return 0;
+ }
+ config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF;
+ return 0;
+ }
return git_log_config(var, value, cb);
}
@@ -673,7 +789,7 @@ static int reopen_stdout(struct commit *commit, const char *subject,
struct rev_info *rev, int quiet)
{
struct strbuf filename = STRBUF_INIT;
- int suffix_len = strlen(fmt_patch_suffix) + 1;
+ int suffix_len = strlen(rev->patch_suffix) + 1;
if (output_directory) {
strbuf_addstr(&filename, output_directory);
@@ -684,7 +800,12 @@ static int reopen_stdout(struct commit *commit, const char *subject,
strbuf_addch(&filename, '/');
}
- get_patch_filename(commit, subject, rev->nr, fmt_patch_suffix, &filename);
+ if (rev->numbered_files)
+ strbuf_addf(&filename, "%d", rev->nr);
+ else if (commit)
+ fmt_output_commit(&filename, commit, rev);
+ else
+ fmt_output_subject(&filename, subject, rev);
if (!quiet)
fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
@@ -696,10 +817,10 @@ static int reopen_stdout(struct commit *commit, const char *subject,
return 0;
}
-static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
+static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
{
struct rev_info check_rev;
- struct commit *commit;
+ struct commit *commit, *c1, *c2;
struct object *o1, *o2;
unsigned flags1, flags2;
@@ -707,9 +828,11 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
die(_("Need exactly one range."));
o1 = rev->pending.objects[0].item;
- flags1 = o1->flags;
o2 = rev->pending.objects[1].item;
+ flags1 = o1->flags;
flags2 = o2->flags;
+ c1 = lookup_commit_reference(o1->sha1);
+ c2 = lookup_commit_reference(o2->sha1);
if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
die(_("Not a range."));
@@ -717,7 +840,8 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
init_patch_ids(ids);
/* given a range a..b get all patch ids for b..a */
- init_revisions(&check_rev, prefix);
+ init_revisions(&check_rev, rev->prefix);
+ check_rev.max_parents = 1;
o1->flags ^= UNINTERESTING;
o2->flags ^= UNINTERESTING;
add_pending_object(&check_rev, o1, "o1");
@@ -726,18 +850,12 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
die(_("revision walk setup failed"));
while ((commit = get_revision(&check_rev)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
add_commit_patch_id(commit, ids);
}
/* reset for next revision walk */
- clear_commit_marks((struct commit *)o1,
- SEEN | UNINTERESTING | SHOWN | ADDED);
- clear_commit_marks((struct commit *)o2,
- SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED);
+ clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED);
o1->flags = flags1;
o2->flags = flags2;
}
@@ -753,8 +871,13 @@ static void gen_message_id(struct rev_info *info, char *base)
static void print_signature(void)
{
- if (signature && *signature)
- printf("-- \n%s\n\n", signature);
+ if (!signature || !*signature)
+ return;
+
+ printf("-- \n%s", signature);
+ if (signature[strlen(signature)-1] != '\n')
+ putchar('\n');
+ putchar('\n');
}
static void add_branch_description(struct strbuf *buf, const char *branch_name)
@@ -765,15 +888,43 @@ static void add_branch_description(struct strbuf *buf, const char *branch_name)
read_branch_desc(&desc, branch_name);
if (desc.len) {
strbuf_addch(buf, '\n');
- strbuf_add(buf, desc.buf, desc.len);
+ strbuf_addbuf(buf, &desc);
strbuf_addch(buf, '\n');
}
+ strbuf_release(&desc);
+}
+
+static char *find_branch_name(struct rev_info *rev)
+{
+ int i, positive = -1;
+ unsigned char branch_sha1[20];
+ const unsigned char *tip_sha1;
+ const char *ref, *v;
+ char *full_ref, *branch = NULL;
+
+ for (i = 0; i < rev->cmdline.nr; i++) {
+ if (rev->cmdline.rev[i].flags & UNINTERESTING)
+ continue;
+ if (positive < 0)
+ positive = i;
+ else
+ return NULL;
+ }
+ if (positive < 0)
+ return NULL;
+ ref = rev->cmdline.rev[positive].name;
+ tip_sha1 = rev->cmdline.rev[positive].item->sha1;
+ if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) &&
+ skip_prefix(full_ref, "refs/heads/", &v) &&
+ !hashcmp(tip_sha1, branch_sha1))
+ branch = xstrdup(v);
+ free(full_ref);
+ return branch;
}
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,
+ int nr, struct commit **list,
const char *branch_name,
int quiet)
{
@@ -787,6 +938,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
struct diff_options opts;
int need_8bit_cte = 0;
struct pretty_print_context pp = {0};
+ struct commit *head = list[0];
if (rev->commit_format != CMIT_FMT_EMAIL)
die(_("Cover letter needs email format"));
@@ -794,19 +946,25 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
committer = git_committer_info(0);
if (!use_stdout &&
- reopen_stdout(NULL, numbered_files ? NULL : "cover-letter", rev, quiet))
+ reopen_stdout(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
return;
log_write_email_headers(rev, head, &pp.subject, &pp.after_subject,
&need_8bit_cte);
- for (i = 0; !need_8bit_cte && i < nr; i++)
- if (has_non_ascii(list[i]->buffer))
+ for (i = 0; !need_8bit_cte && i < nr; i++) {
+ const char *buf = get_commit_buffer(list[i], NULL);
+ if (has_non_ascii(buf))
need_8bit_cte = 1;
+ unuse_commit_buffer(list[i], buf);
+ }
+
+ if (!branch_name)
+ branch_name = find_branch_name(rev);
msg = body;
pp.fmt = CMIT_FMT_EMAIL;
- pp.date_mode = DATE_RFC2822;
+ pp.date_mode.type = DATE_RFC2822;
pp_user_info(&pp, NULL, &sb, committer, encoding);
pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte);
pp_remainder(&pp, &msg, &sb, 0);
@@ -890,7 +1048,7 @@ static const char *set_outdir(const char *prefix, const char *output_directory)
}
static const char * const builtin_format_patch_usage[] = {
- N_("git format-patch [options] [<since> | <revision range>]"),
+ N_("git format-patch [<options>] [<since> | <revision-range>]"),
NULL
};
@@ -1010,33 +1168,19 @@ static int cc_callback(const struct option *opt, const char *arg, int unset)
return 0;
}
-static char *find_branch_name(struct rev_info *rev)
+static int from_callback(const struct option *opt, const char *arg, int unset)
{
- int i, positive = -1;
- unsigned char branch_sha1[20];
- struct strbuf buf = STRBUF_INIT;
- const char *branch;
+ char **from = opt->value;
- for (i = 0; i < rev->cmdline.nr; i++) {
- if (rev->cmdline.rev[i].flags & UNINTERESTING)
- continue;
- if (positive < 0)
- positive = i;
- else
- return NULL;
- }
- if (positive < 0)
- return NULL;
- strbuf_addf(&buf, "refs/heads/%s", rev->cmdline.rev[positive].name);
- branch = resolve_ref_unsafe(buf.buf, branch_sha1, 1, NULL);
- if (!branch ||
- prefixcmp(branch, "refs/heads/") ||
- hashcmp(rev->cmdline.rev[positive].item->sha1, branch_sha1))
- branch = NULL;
- strbuf_release(&buf);
- if (branch)
- return xstrdup(rev->cmdline.rev[positive].name);
- return NULL;
+ free(*from);
+
+ if (unset)
+ *from = NULL;
+ else if (arg)
+ *from = xstrdup(arg);
+ else
+ *from = xstrdup(git_committer_info(IDENT_NO_DATE));
+ return 0;
}
int cmd_format_patch(int argc, const char **argv, const char *prefix)
@@ -1048,19 +1192,20 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int nr = 0, total, i;
int use_stdout = 0;
int start_number = -1;
- int numbered_files = 0; /* _just_ numbers */
+ int just_numbers = 0;
int ignore_if_in_upstream = 0;
- int cover_letter = 0;
+ int cover_letter = -1;
int boundary_count = 0;
int no_binary_diff = 0;
- struct commit *origin = NULL, *head = NULL;
+ struct commit *origin = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
- char *add_signoff = NULL;
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
int quiet = 0;
+ int reroll_count = -1;
char *branch_name = NULL;
+ char *from = NULL;
const struct option builtin_format_patch_options[] = {
{ OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
N_("use [PATCH n/m] even with a single patch"),
@@ -1068,17 +1213,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL,
N_("use [PATCH] even with multiple patches"),
PARSE_OPT_NOARG, no_numbered_callback },
- OPT_BOOLEAN('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
- OPT_BOOLEAN(0, "stdout", &use_stdout,
+ OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")),
+ OPT_BOOL(0, "stdout", &use_stdout,
N_("print patches to standard out")),
- OPT_BOOLEAN(0, "cover-letter", &cover_letter,
+ OPT_BOOL(0, "cover-letter", &cover_letter,
N_("generate a cover letter")),
- OPT_BOOLEAN(0, "numbered-files", &numbered_files,
+ OPT_BOOL(0, "numbered-files", &just_numbers,
N_("use simple number sequence for output file names")),
OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"),
N_("use <sfx> instead of '.patch'")),
OPT_INTEGER(0, "start-number", &start_number,
N_("start numbering patches at <n> instead of 1")),
+ OPT_INTEGER('v', "reroll-count", &reroll_count,
+ N_("mark the series as Nth re-roll")),
{ OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"),
N_("Use [<prefix>] instead of [PATCH]"),
PARSE_OPT_NONEG, subject_prefix_callback },
@@ -1088,13 +1235,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
{ OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
N_("don't strip/add [PATCH]"),
PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
- OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
- N_("don't output binary diffs")),
- OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
- N_("don't include a patch matching a commit upstream")),
- { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
+ OPT_BOOL(0, "no-binary", &no_binary_diff,
+ N_("don't output binary diffs")),
+ OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
+ N_("don't include a patch matching a commit upstream")),
+ { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL,
N_("show patch format instead of default (patch + stat)"),
- PARSE_OPT_NONEG | PARSE_OPT_NOARG },
+ PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1},
OPT_GROUP(N_("Messaging")),
{ OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
N_("add email header"), 0, header_callback },
@@ -1102,6 +1249,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
0, to_callback },
{ OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
0, cc_callback },
+ { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
+ N_("set From address to <ident> (or committer ident if absent)"),
+ PARSE_OPT_OPTARG, from_callback },
OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
N_("make first mail a reply to <message-id>")),
{ OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
@@ -1116,14 +1266,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_OPTARG, thread_callback },
OPT_STRING(0, "signature", &signature, N_("signature"),
N_("add a signature")),
- OPT_BOOLEAN(0, "quiet", &quiet,
- N_("don't print the patch filenames")),
+ OPT_FILENAME(0, "signature-file", &signature_file,
+ N_("add a signature from a file")),
+ OPT__QUIET(&quiet, N_("don't print the patch filenames")),
OPT_END()
};
extra_hdr.strdup_strings = 1;
extra_to.strdup_strings = 1;
extra_cc.strdup_strings = 1;
+ init_grep_defaults();
git_config(git_format_config, NULL);
init_revisions(&rev, prefix);
rev.commit_format = CMIT_FMT_EMAIL;
@@ -1151,14 +1303,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN |
PARSE_OPT_KEEP_DASHDASH);
- if (do_signoff) {
- const char *committer;
- const char *endpos;
- committer = git_committer_info(IDENT_STRICT);
- endpos = strchr(committer, '>');
- if (!endpos)
- die(_("bogus committer info %s"), committer);
- add_signoff = xmemdupz(committer, endpos - committer + 1);
+ if (0 < reroll_count) {
+ struct strbuf sprefix = STRBUF_INIT;
+ strbuf_addf(&sprefix, "%s v%d",
+ rev.subject_prefix, reroll_count);
+ rev.reroll_count = reroll_count;
+ rev.subject_prefix = strbuf_detach(&sprefix, NULL);
}
for (i = 0; i < extra_hdr.nr; i++) {
@@ -1190,6 +1340,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.extra_headers = strbuf_detach(&buf, NULL);
+ if (from) {
+ if (split_ident_line(&rev.from_ident, from, strlen(from)))
+ die(_("invalid ident line: %s"), from);
+ }
+
if (start_number < 0)
start_number = 1;
@@ -1246,28 +1401,37 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
if (rev.pending.nr == 1) {
+ int check_head = 0;
+
if (rev.max_count < 0 && !rev.show_root_diff) {
/*
* This is traditional behaviour of "git format-patch
* origin" that prepares what the origin side still
* does not have.
*/
- unsigned char sha1[20];
- const char *ref;
-
rev.pending.objects[0].item->flags |= UNINTERESTING;
add_head_to_pending(&rev);
- ref = resolve_ref_unsafe("HEAD", sha1, 1, NULL);
- if (ref && !prefixcmp(ref, "refs/heads/"))
- branch_name = xstrdup(ref + strlen("refs/heads/"));
- else
- branch_name = xstrdup(""); /* no branch */
+ check_head = 1;
}
/*
* Otherwise, it is "format-patch -22 HEAD", and/or
* "format-patch --root HEAD". The user wants
* get_revision() to do the usual traversal.
*/
+
+ if (!strcmp(rev.pending.objects[0].name, "HEAD"))
+ check_head = 1;
+
+ if (check_head) {
+ unsigned char sha1[20];
+ const char *ref, *v;
+ ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+ sha1, NULL);
+ if (ref && skip_prefix(ref, "refs/heads/", &v))
+ branch_name = xstrdup(v);
+ else
+ branch_name = xstrdup(""); /* no branch */
+ }
}
/*
@@ -1276,29 +1440,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
*/
rev.show_root_diff = 1;
- if (cover_letter) {
- /*
- * NEEDSWORK:randomly pick one positive commit to show
- * diffstat; this is often the tip and the command
- * happens to do the right thing in most cases, but a
- * complex command like "--cover-letter a b c ^bottom"
- * picks "c" and shows diffstat between bottom..c
- * which may not match what the series represents at
- * all and totally broken.
- */
- 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;
- }
- /* There is nothing to show; it is not an error, though. */
- if (!head)
- return 0;
- if (!branch_name)
- branch_name = find_branch_name(&rev);
- }
-
if (ignore_if_in_upstream) {
/* Don't say anything if head and upstream are the same. */
if (rev.pending.nr == 2) {
@@ -1306,7 +1447,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (hashcmp(o[0].item->sha1, o[1].item->sha1) == 0)
return 0;
}
- get_patch_ids(&rev, &ids, prefix);
+ get_patch_ids(&rev, &ids);
}
if (!use_stdout)
@@ -1322,36 +1463,57 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
continue;
}
- if (ignore_if_in_upstream &&
- has_commit_patch_id(commit, &ids))
+ if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids))
continue;
nr++;
- list = xrealloc(list, nr * sizeof(list[0]));
+ REALLOC_ARRAY(list, nr);
list[nr - 1] = commit;
}
+ if (nr == 0)
+ /* nothing to do */
+ return 0;
total = nr;
if (!keep_subject && auto_number && total > 1)
numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
+ if (cover_letter == -1) {
+ if (config_cover_letter == COVER_AUTO)
+ cover_letter = (total > 1);
+ else
+ cover_letter = (config_cover_letter == COVER_ON);
+ }
+
+ if (!signature) {
+ ; /* --no-signature inhibits all signatures */
+ } else if (signature && signature != git_version_string) {
+ ; /* non-default signature already set */
+ } else if (signature_file) {
+ struct strbuf buf = STRBUF_INIT;
+
+ if (strbuf_read_file(&buf, signature_file, 128) < 0)
+ die_errno(_("unable to read signature file '%s'"), signature_file);
+ signature = strbuf_detach(&buf, NULL);
+ }
+
if (in_reply_to || thread || cover_letter)
rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
if (in_reply_to) {
const char *msgid = clean_message_id(in_reply_to);
string_list_append(rev.ref_message_ids, msgid);
}
- rev.numbered_files = numbered_files;
+ rev.numbered_files = just_numbers;
rev.patch_suffix = fmt_patch_suffix;
if (cover_letter) {
if (thread)
gen_message_id(&rev, "cover");
- make_cover_letter(&rev, use_stdout, numbered, numbered_files,
- origin, nr, list, head, branch_name, quiet);
+ make_cover_letter(&rev, use_stdout,
+ origin, nr, list, branch_name, quiet);
total++;
start_number--;
}
- rev.add_signoff = add_signoff;
+ rev.add_signoff = do_signoff;
while (0 <= --nr) {
int shown;
commit = list[nr];
@@ -1393,11 +1555,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
}
if (!use_stdout &&
- reopen_stdout(numbered_files ? NULL : commit, NULL, &rev, quiet))
+ reopen_stdout(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
die(_("Failed to create output files"));
shown = log_tree_commit(&rev, commit);
- free(commit->buffer);
- commit->buffer = NULL;
+ free_commit_buffer(commit);
/* We put one extra blank line between formatted
* patches and this flag is used by log-tree code
@@ -1495,23 +1656,17 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
break;
default:
current_branch = branch_get(NULL);
- if (!current_branch || !current_branch->merge
- || !current_branch->merge[0]
- || !current_branch->merge[0]->dst) {
+ upstream = branch_get_upstream(current_branch, NULL);
+ if (!upstream) {
fprintf(stderr, _("Could not find a tracked"
" remote branch, please"
" specify <upstream> manually.\n"));
usage_with_options(cherry_usage, options);
}
-
- upstream = current_branch->merge[0]->dst;
}
init_revisions(&revs, prefix);
- revs.diff = 1;
- revs.combine_merges = 0;
- revs.ignore_merges = 1;
- DIFF_OPT_SET(&revs.diffopt, RECURSIVE);
+ revs.max_parents = 1;
if (add_pending_commit(head, &revs, 0))
die(_("Unknown commit %s"), head);
@@ -1525,7 +1680,7 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
return 0;
}
- get_patch_ids(&revs, &ids, prefix);
+ get_patch_ids(&revs, &ids);
if (limit && add_pending_commit(limit, &revs, UNINTERESTING))
die(_("Unknown commit %s"), limit);
@@ -1534,10 +1689,6 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&revs))
die(_("revision walk setup failed"));
while ((commit = get_revision(&revs)) != NULL) {
- /* ignore merges */
- if (commit->parents && commit->parents->next)
- continue;
-
commit_list_insert(commit, &list);
}