From: Junio C Hamano Date: Mon, 1 Apr 2013 15:59:23 +0000 (-0700) Subject: Merge branch 'bc/append-signed-off-by' X-Git-Tag: v1.8.3-rc0~149 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/900c8ecb5c98c3cae256dcc030476392fdf2bbf2?ds=inline;hp=-c Merge branch 'bc/append-signed-off-by' Consolidate codepaths that inspect log-message-to-be and decide to add a new Signed-off-by line in various commands. * bc/append-signed-off-by: git-commit: populate the edit buffer with 2 blank lines before s-o-b Unify appending signoff in format-patch, commit and sequencer format-patch: update append_signoff prototype t4014: more tests about appending s-o-b lines sequencer.c: teach append_signoff to avoid adding a duplicate newline sequencer.c: teach append_signoff how to detect duplicate s-o-b sequencer.c: always separate "(cherry picked from" from commit body sequencer.c: require a conforming footer to be preceded by a blank line sequencer.c: recognize "(cherry picked from ..." as part of s-o-b footer t/t3511: add some tests of 'cherry-pick -s' functionality t/test-lib-functions.sh: allow to specify the tag name to test_commit commit, cherry-pick -s: remove broken support for multiline rfc2822 fields sequencer.c: rework search for start of footer to improve clarity --- 900c8ecb5c98c3cae256dcc030476392fdf2bbf2 diff --combined builtin/commit.c index d21d07a1a8,7b9e2ac2eb..46204375e7 --- a/builtin/commit.c +++ b/builtin/commit.c @@@ -31,12 -31,12 +31,12 @@@ #include "sequencer.h" static const char * const builtin_commit_usage[] = { - N_("git commit [options] [--] ..."), + N_("git commit [options] [--] ..."), NULL }; static const char * const builtin_status_usage[] = { - N_("git status [options] [--] ..."), + N_("git status [options] [--] ..."), NULL }; @@@ -103,7 -103,7 +103,7 @@@ static enum CLEANUP_NONE, CLEANUP_ALL } cleanup_mode; -static char *cleanup_arg; +static const char *cleanup_arg; static enum commit_whence whence; static int use_editor = 1, include_status = 1; @@@ -124,10 -124,8 +124,10 @@@ static int opt_parse_m(const struct opt if (unset) strbuf_setlen(buf, 0); else { + if (buf->len) + strbuf_addch(buf, '\n'); strbuf_addstr(buf, arg); - strbuf_addstr(buf, "\n\n"); + strbuf_complete_line(buf); } return 0; } @@@ -702,7 -700,7 +702,7 @@@ static int prepare_to_commit(const cha previous = eol; } - append_signoff(&sb, ignore_footer); + append_signoff(&sb, ignore_footer, 0); } if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) @@@ -735,15 -733,15 +735,15 @@@ if (cleanup_mode == CLEANUP_ALL) status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." - " Lines starting\nwith '#' will be ignored, and an empty" - " message aborts the commit.\n")); + " Lines starting\nwith '%c' will be ignored, and an empty" + " message aborts the commit.\n"), comment_line_char); else /* CLEANUP_SPACE, that is. */ status_printf(s, GIT_COLOR_NORMAL, _("Please enter the commit message for your changes." - " Lines starting\n" - "with '#' will be kept; you may remove them" - " yourself if you want to.\n" - "An empty message aborts the commit.\n")); + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"), comment_line_char); if (only_include_assumed) status_printf_ln(s, GIT_COLOR_NORMAL, "%s", only_include_assumed); @@@ -757,7 -755,7 +757,7 @@@ ident_shown++ ? "" : "\n", author_ident->buf); - if (!user_ident_sufficiently_given()) + if (!committer_ident_sufficiently_given()) status_printf_ln(s, GIT_COLOR_NORMAL, _("%s" "Committer: %s"), @@@ -948,14 -946,24 +948,14 @@@ static void handle_untracked_files_arg( static const char *read_commit_message(const char *name) { - const char *out_enc, *out; + const char *out_enc; struct commit *commit; commit = lookup_commit_reference_by_name(name); if (!commit) die(_("could not lookup commit %s"), name); out_enc = get_commit_output_encoding(); - out = logmsg_reencode(commit, out_enc); - - /* - * If we failed to reencode the buffer, just copy it - * byte for byte so the user can try to fix it up. - * This also handles the case where input and output - * encodings are identical. - */ - if (out == NULL) - out = xstrdup(commit->buffer); - return out; + return logmsg_reencode(commit, out_enc); } static int parse_and_validate_options(int argc, const char *argv[], @@@ -1257,7 -1265,7 +1257,7 @@@ static void print_summary(const char *p strbuf_addstr(&format, "\n Author: "); strbuf_addbuf_percentquote(&format, &author_ident); } - if (!user_ident_sufficiently_given()) { + if (!committer_ident_sufficiently_given()) { strbuf_addstr(&format, "\n Committer: "); strbuf_addbuf_percentquote(&format, &committer_ident); if (advice_implicit_identity) { @@@ -1312,8 -1320,6 +1312,8 @@@ static int git_commit_config(const cha include_status = git_config_bool(k, v); return 0; } + if (!strcmp(k, "commit.cleanup")) + return git_config_string(&cleanup_arg, k, v); status = git_gpg_config(k, v, NULL); if (status) @@@ -1321,6 -1327,8 +1321,6 @@@ return git_status_config(k, v, s); } -static const char post_rewrite_hook[] = "hooks/post-rewrite"; - static int run_rewrite_hook(const unsigned char *oldsha1, const unsigned char *newsha1) { @@@ -1331,10 -1339,10 +1331,10 @@@ int code; size_t n; - if (access(git_path(post_rewrite_hook), X_OK) < 0) + argv[0] = find_hook("post-rewrite"); + if (!argv[0]) return 0; - argv[0] = git_path(post_rewrite_hook); argv[1] = "amend"; argv[2] = NULL; diff --combined builtin/log.c index 8f0b2e84fe,bb48344113..59de484bc2 --- a/builtin/log.c +++ b/builtin/log.c @@@ -22,7 -22,6 +22,7 @@@ #include "branch.h" #include "streaming.h" #include "version.h" +#include "mailmap.h" /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@@ -31,7 -30,6 +31,7 @@@ static int default_abbrev_commit static int default_show_root = 1; 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; @@@ -96,18 -94,16 +96,18 @@@ static void cmd_log_init_finish(int arg 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; const struct option builtin_log_options[] = { OPT_BOOLEAN(0, "quiet", &quiet, N_("suppress diff output")), OPT_BOOLEAN(0, "source", &source, N_("show source")), + OPT_BOOLEAN(0, "use-mailmap", &mailmap, N_("Use mail map file")), { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback}, OPT_END() }; + mailmap = use_mailmap_config; argc = parse_options(argc, argv, prefix, builtin_log_options, builtin_log_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | @@@ -140,11 -136,6 +140,11 @@@ 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 @@@ -360,11 -351,6 +360,11 @@@ static int git_log_config(const char *v } if (!prefixcmp(var, "color.decorate.")) return parse_decorate_color_config(var, 15, 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; return git_diff_ui_config(var, value, cb); @@@ -692,7 -678,7 +692,7 @@@ static int reopen_stdout(struct commit 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); @@@ -703,12 -689,7 +703,12 @@@ 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); @@@ -792,6 -773,7 +792,6 @@@ static void add_branch_description(stru } 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 *branch_name, @@@ -814,7 -796,7 +814,7 @@@ 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, @@@ -1034,9 -1016,8 +1034,9 @@@ static char *find_branch_name(struct re { int i, positive = -1; unsigned char branch_sha1[20]; - struct strbuf buf = STRBUF_INIT; - const char *branch; + const unsigned char *tip_sha1; + const char *ref; + char *full_ref, *branch = NULL; for (i = 0; i < rev->cmdline.nr; i++) { if (rev->cmdline.rev[i].flags & UNINTERESTING) @@@ -1046,27 -1027,18 +1046,27 @@@ else return NULL; } - if (positive < 0) + if (0 <= positive) { + ref = rev->cmdline.rev[positive].name; + tip_sha1 = rev->cmdline.rev[positive].item->sha1; + } else if (!rev->cmdline.nr && rev->pending.nr == 1 && + !strcmp(rev->pending.objects[0].name, "HEAD")) { + /* + * No actual ref from command line, but "HEAD" from + * rev->def was added in setup_revisions() + * e.g. format-patch --cover-letter -12 + */ + ref = "HEAD"; + tip_sha1 = rev->pending.objects[0].item->sha1; + } else { 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; + } + if (dwim_ref(ref, strlen(ref), branch_sha1, &full_ref) && + !prefixcmp(full_ref, "refs/heads/") && + !hashcmp(tip_sha1, branch_sha1)) + branch = xstrdup(full_ref + strlen("refs/heads/")); + free(full_ref); + return branch; } int cmd_format_patch(int argc, const char **argv, const char *prefix) @@@ -1078,7 -1050,7 +1078,7 @@@ 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 boundary_count = 0; @@@ -1086,11 -1058,9 +1086,10 @@@ struct commit *origin = NULL, *head = 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; const struct option builtin_format_patch_options[] = { { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, @@@ -1104,14 -1074,12 +1103,14 @@@ N_("print patches to standard out")), OPT_BOOLEAN(0, "cover-letter", &cover_letter, N_("generate a cover letter")), - OPT_BOOLEAN(0, "numbered-files", &numbered_files, + OPT_BOOLEAN(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 instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, N_("start numbering patches at 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 [] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@@ -1185,24 -1153,6 +1184,14 @@@ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + 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); + } + - 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); - } - for (i = 0; i < extra_hdr.nr; i++) { strbuf_addstr(&buf, extra_hdr.items[i].string); strbuf_addch(&buf, '\n'); @@@ -1383,17 -1333,17 +1372,17 @@@ 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, + make_cover_letter(&rev, use_stdout, origin, nr, list, head, branch_name, quiet); total++; start_number--; } - rev.add_signoff = add_signoff; + rev.add_signoff = do_signoff; while (0 <= --nr) { int shown; commit = list[nr]; @@@ -1435,7 -1385,7 +1424,7 @@@ } 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); diff --combined log-tree.c index 3d88823871,cf274058ca..92bb2bf48e --- a/log-tree.c +++ b/log-tree.c @@@ -9,6 -9,7 +9,7 @@@ #include "string-list.h" #include "color.h" #include "gpg-interface.h" + #include "sequencer.h" struct decoration name_decoration = { "object names" }; @@@ -206,89 -207,6 +207,6 @@@ void show_decorations(struct rev_info * putchar(')'); } - /* - * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches - * Signed-off-by: and Acked-by: lines. - */ - static int detect_any_signoff(char *letter, int size) - { - char *cp; - int seen_colon = 0; - int seen_at = 0; - int seen_name = 0; - int seen_head = 0; - - cp = letter + size; - while (letter <= --cp && *cp == '\n') - continue; - - while (letter <= cp) { - char ch = *cp--; - if (ch == '\n') - break; - - if (!seen_at) { - if (ch == '@') - seen_at = 1; - continue; - } - if (!seen_colon) { - if (ch == '@') - return 0; - else if (ch == ':') - seen_colon = 1; - else - seen_name = 1; - continue; - } - if (('A' <= ch && ch <= 'Z') || - ('a' <= ch && ch <= 'z') || - ch == '-') { - seen_head = 1; - continue; - } - /* no empty last line doesn't match */ - return 0; - } - return seen_head && seen_name; - } - - static void append_signoff(struct strbuf *sb, const char *signoff) - { - static const char signed_off_by[] = "Signed-off-by: "; - size_t signoff_len = strlen(signoff); - int has_signoff = 0; - char *cp; - - cp = sb->buf; - - /* First see if we already have the sign-off by the signer */ - while ((cp = strstr(cp, signed_off_by))) { - - has_signoff = 1; - - cp += strlen(signed_off_by); - if (cp + signoff_len >= sb->buf + sb->len) - break; - if (strncmp(cp, signoff, signoff_len)) - continue; - if (!isspace(cp[signoff_len])) - continue; - /* we already have him */ - return; - } - - if (!has_signoff) - has_signoff = detect_any_signoff(sb->buf, sb->len); - - if (!has_signoff) - strbuf_addch(sb, '\n'); - - strbuf_addstr(sb, signed_off_by); - strbuf_add(sb, signoff, signoff_len); - strbuf_addch(sb, '\n'); - } - static unsigned int digits_in_number(unsigned int number) { unsigned int i = 10, result = 1; @@@ -299,34 -217,26 +217,34 @@@ return result; } -void get_patch_filename(struct commit *commit, const char *subject, int nr, - const char *suffix, struct strbuf *buf) +void fmt_output_subject(struct strbuf *filename, + const char *subject, + struct rev_info *info) { - int suffix_len = strlen(suffix) + 1; - int start_len = buf->len; - - strbuf_addf(buf, commit || subject ? "%04d-" : "%d", nr); - if (commit || subject) { - int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len; - struct pretty_print_context ctx = {0}; - - if (subject) - strbuf_addstr(buf, subject); - else if (commit) - format_commit_message(commit, "%f", buf, &ctx); - - if (max_len < buf->len) - strbuf_setlen(buf, max_len); - strbuf_addstr(buf, suffix); - } + const char *suffix = info->patch_suffix; + int nr = info->nr; + int start_len = filename->len; + int max_len = start_len + FORMAT_PATCH_NAME_MAX - (strlen(suffix) + 1); + + if (0 < info->reroll_count) + strbuf_addf(filename, "v%d-", info->reroll_count); + strbuf_addf(filename, "%04d-%s", nr, subject); + + if (max_len < filename->len) + strbuf_setlen(filename, max_len); + strbuf_addstr(filename, suffix); +} + +void fmt_output_commit(struct strbuf *filename, + struct commit *commit, + struct rev_info *info) +{ + struct pretty_print_context ctx = {0}; + struct strbuf subject = STRBUF_INIT; + + format_commit_message(commit, "%f", &subject, &ctx); + fmt_output_subject(filename, subject.buf, info); + strbuf_release(&subject); } void log_write_email_headers(struct rev_info *opt, struct commit *commit, @@@ -395,10 -305,8 +313,10 @@@ mime_boundary_leader, opt->mime_boundary); extra_headers = subject_buffer; - get_patch_filename(opt->numbered_files ? NULL : commit, NULL, - opt->nr, opt->patch_suffix, &filename); + if (opt->numbered_files) + strbuf_addf(&filename, "%d", opt->nr); + else + fmt_output_commit(&filename, commit, opt); snprintf(buffer, sizeof(buffer) - 1, "\n--%s%s\n" "Content-Type: text/x-patch;" @@@ -444,7 -352,7 +362,7 @@@ static void show_signature(struct rev_i status = verify_signed_buffer(payload.buf, payload.len, signature.buf, signature.len, - &gpg_output); + &gpg_output, NULL); if (status && !gpg_output.len) strbuf_addstr(&gpg_output, "No signature\n"); @@@ -508,17 -416,20 +426,17 @@@ static void show_one_mergetag(struct re gpg_message_offset = verify_message.len; payload_size = parse_signature(extra->value, extra->len); - if ((extra->len <= payload_size) || - (verify_signed_buffer(extra->value, payload_size, - extra->value + payload_size, - extra->len - payload_size, - &verify_message) && - verify_message.len <= gpg_message_offset)) { - strbuf_addstr(&verify_message, "No signature\n"); - status = -1; - } - else if (strstr(verify_message.buf + gpg_message_offset, - ": Good signature from ")) - status = 0; - else - status = -1; + status = -1; + if (extra->len > payload_size) + if (verify_signed_buffer(extra->value, payload_size, + extra->value + payload_size, + extra->len - payload_size, + &verify_message, NULL)) { + if (verify_message.len <= gpg_message_offset) + strbuf_addstr(&verify_message, "No signature\n"); + else + status = 0; + } show_sig_lines(opt, status, verify_message.buf); strbuf_release(&verify_message); @@@ -669,8 -580,10 +587,10 @@@ void show_log(struct rev_info *opt /* * And then the pretty-printed message itself */ - if (ctx.need_8bit_cte >= 0) - ctx.need_8bit_cte = has_non_ascii(opt->add_signoff); + if (ctx.need_8bit_cte >= 0 && opt->add_signoff) + ctx.need_8bit_cte = + has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"))); ctx.date_mode = opt->date_mode; ctx.date_mode_explicit = opt->date_mode_explicit; ctx.abbrev = opt->diffopt.abbrev; @@@ -678,12 -591,10 +598,12 @@@ ctx.preserve_subject = opt->preserve_subject; ctx.reflog_info = opt->reflog_info; ctx.fmt = opt->commit_format; + ctx.mailmap = opt->mailmap; + ctx.color = opt->diffopt.use_color; pretty_print_commit(&ctx, commit, &msgbuf); if (opt->add_signoff) - append_signoff(&msgbuf, opt->add_signoff); + append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP); if ((ctx.fmt != CMIT_FMT_USERFORMAT) && ctx.notes_message && *ctx.notes_message) { diff --combined revision.h index 5da09ee3ef,d20defabd5..01bd2b7c07 --- a/revision.h +++ b/revision.h @@@ -135,16 -135,14 +135,16 @@@ struct rev_info const char *mime_boundary; const char *patch_suffix; int numbered_files; + int reroll_count; char *message_id; struct string_list *ref_message_ids; - const char *add_signoff; + int add_signoff; const char *extra_headers; const char *log_reencode; const char *subject_prefix; int no_inline; int show_log_size; + struct string_list *mailmap; /* Filter by commit log message */ struct grep_opt grep_filter; diff --combined sequencer.c index aef5e8a017,a07d2d00b9..baa031052e --- a/sequencer.c +++ b/sequencer.c @@@ -18,6 -18,89 +18,89 @@@ #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION" const char sign_off_header[] = "Signed-off-by: "; + static const char cherry_picked_prefix[] = "(cherry picked from commit "; + + static int is_rfc2822_line(const char *buf, int len) + { + int i; + + for (i = 0; i < len; i++) { + int ch = buf[i]; + if (ch == ':') + return 1; + if (!isalnum(ch) && ch != '-') + break; + } + + return 0; + } + + static int is_cherry_picked_from_line(const char *buf, int len) + { + /* + * We only care that it looks roughly like (cherry picked from ...) + */ + return len > strlen(cherry_picked_prefix) + 1 && + !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')'; + } + + /* + * Returns 0 for non-conforming footer + * Returns 1 for conforming footer + * Returns 2 when sob exists within conforming footer + * Returns 3 when sob exists within conforming footer as last entry + */ + static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob, + int ignore_footer) + { + char prev; + int i, k; + int len = sb->len - ignore_footer; + const char *buf = sb->buf; + int found_sob = 0; + + /* footer must end with newline */ + if (!len || buf[len - 1] != '\n') + return 0; + + prev = '\0'; + for (i = len - 1; i > 0; i--) { + char ch = buf[i]; + if (prev == '\n' && ch == '\n') /* paragraph break */ + break; + prev = ch; + } + + /* require at least one blank line */ + if (prev != '\n' || buf[i] != '\n') + return 0; + + /* advance to start of last paragraph */ + while (i < len - 1 && buf[i] == '\n') + i++; + + for (; i < len; i = k) { + int found_rfc2822; + + for (k = i; k < len && buf[k] != '\n'; k++) + ; /* do nothing */ + k++; + + found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1); + if (found_rfc2822 && sob && + !strncmp(buf + i, sob->buf, sob->len)) + found_sob = k; + + if (!(found_rfc2822 || + is_cherry_picked_from_line(buf + i, k - i - 1))) + return 0; + } + if (found_sob == i) + return 3; + if (found_sob) + return 2; + return 1; + } static void remove_sequencer_state(void) { @@@ -186,15 -269,14 +269,15 @@@ static int error_dirty_index(struct rep return -1; } -static int fast_forward_to(const unsigned char *to, const unsigned char *from) +static int fast_forward_to(const unsigned char *to, const unsigned char *from, + int unborn) { struct ref_lock *ref_lock; read_cache(); if (checkout_fast_forward(from, to, 1)) exit(1); /* the callee should have complained already */ - ref_lock = lock_any_ref_for_update("HEAD", from, 0); + ref_lock = lock_any_ref_for_update("HEAD", unborn ? null_sha1 : from, 0); return write_ref_sha1(ref_lock, to, "cherry-pick"); } @@@ -237,7 -319,7 +320,7 @@@ static int do_recursive_merge(struct co rollback_lock_file(&index_lock); if (opts->signoff) - append_signoff(msgbuf, 0); + append_signoff(msgbuf, 0, 0); if (!clean) { int i; @@@ -391,7 -473,7 +474,7 @@@ static int do_pick_commit(struct commi struct commit_message msg = { NULL, NULL, NULL, NULL, NULL }; char *defmsg = NULL; struct strbuf msgbuf = STRBUF_INIT; - int res; + int res, unborn = 0; if (opts->no_commit) { /* @@@ -403,10 -485,9 +486,10 @@@ if (write_cache_as_tree(head, 0, NULL)) die (_("Your index file is unmerged.")); } else { - if (get_sha1("HEAD", head)) - return error(_("You do not have a valid HEAD")); - if (index_differs_from("HEAD", 0)) + unborn = get_sha1("HEAD", head); + if (unborn) + hashcpy(head, EMPTY_TREE_SHA1_BIN); + if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD", 0)) return error_dirty_index(opts); } discard_cache(); @@@ -437,10 -518,8 +520,10 @@@ else parent = commit->parents->item; - if (opts->allow_ff && parent && !hashcmp(parent->object.sha1, head)) - return fast_forward_to(commit->object.sha1, head); + if (opts->allow_ff && + ((parent && !hashcmp(parent->object.sha1, head)) || + (!parent && unborn))) + return fast_forward_to(commit->object.sha1, head, unborn); if (parent && parse_commit(parent) < 0) /* TRANSLATORS: The first %s will be "revert" or @@@ -496,7 -575,9 +579,9 @@@ } if (opts->record_origin) { - strbuf_addstr(&msgbuf, "(cherry picked from commit "); + if (!has_conforming_footer(&msgbuf, NULL, 0)) + strbuf_addch(&msgbuf, '\n'); + strbuf_addstr(&msgbuf, cherry_picked_prefix); strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1)); strbuf_addstr(&msgbuf, ")\n"); } @@@ -1021,62 -1102,67 +1106,67 @@@ int sequencer_pick_revisions(struct rep return pick_commits(todo_list, opts); } - static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer) - { - int ch; - int hit = 0; - int i, j, k; - int len = sb->len - ignore_footer; - int first = 1; - const char *buf = sb->buf; - - for (i = len - 1; i > 0; i--) { - if (hit && buf[i] == '\n') - break; - hit = (buf[i] == '\n'); - } - - while (i < len - 1 && buf[i] == '\n') - i++; - - for (; i < len; i = k) { - for (k = i; k < len && buf[k] != '\n'; k++) - ; /* do nothing */ - k++; - - if ((buf[k] == ' ' || buf[k] == '\t') && !first) - continue; - - first = 0; - - for (j = 0; i + j < len; j++) { - ch = buf[i + j]; - if (ch == ':') - break; - if (isalnum(ch) || - (ch == '-')) - continue; - return 0; - } - } - return 1; - } - - void append_signoff(struct strbuf *msgbuf, int ignore_footer) + void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag) { + unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; struct strbuf sob = STRBUF_INIT; - int i; + int has_footer; strbuf_addstr(&sob, sign_off_header); strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"), getenv("GIT_COMMITTER_EMAIL"))); strbuf_addch(&sob, '\n'); - for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--) - ; /* do nothing */ - if (prefixcmp(msgbuf->buf + i, sob.buf)) { - if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer)) - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1); - strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len); + + /* + * If the whole message buffer is equal to the sob, pretend that we + * found a conforming footer with a matching sob + */ + if (msgbuf->len - ignore_footer == sob.len && + !strncmp(msgbuf->buf, sob.buf, sob.len)) + has_footer = 3; + else + has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer); + + if (!has_footer) { + const char *append_newlines = NULL; + size_t len = msgbuf->len - ignore_footer; + + if (!len) { + /* + * The buffer is completely empty. Leave foom for + * the title and body to be filled in by the user. + */ + append_newlines = "\n\n"; + } else if (msgbuf->buf[len - 1] != '\n') { + /* + * Incomplete line. Complete the line and add a + * blank one so that there is an empty line between + * the message body and the sob. + */ + append_newlines = "\n\n"; + } else if (len == 1) { + /* + * Buffer contains a single newline. Add another + * so that we leave room for the title and body. + */ + append_newlines = "\n"; + } else if (msgbuf->buf[len - 2] != '\n') { + /* + * Buffer ends with a single newline. Add another + * so that there is an empty line between the message + * body and the sob. + */ + append_newlines = "\n"; + } /* else, the buffer already ends with two newlines. */ + + if (append_newlines) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + append_newlines, strlen(append_newlines)); } + + if (has_footer != 3 && (!no_dup_sob || has_footer != 2)) + strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, + sob.buf, sob.len); + strbuf_release(&sob); } diff --combined t/t4014-format-patch.sh index bb1fc47fe8,95af73f596..b993dae645 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@@ -155,7 -155,7 +155,7 @@@ test_expect_failure 'additional comman git config --replace-all format.headers "Cc: R E Cipient " && git format-patch --cc="S. E. Cipient " --stdout master..side | sed -e "/^\$/q" >patch5 && grep "^Cc: R E Cipient ,\$" patch5 && - grep "^ *"S. E. Cipient" \$" patch5 + grep "^ *\"S. E. Cipient\" \$" patch5 ' test_expect_success 'command line headers' ' @@@ -183,7 -183,7 +183,7 @@@ test_expect_success 'command line To: h test_expect_failure 'command line To: header (rfc822)' ' git format-patch --to="R. E. Cipient " --stdout master..side | sed -e "/^\$/q" >patch8 && - grep "^To: "R. E. Cipient" \$" patch8 + grep "^To: \"R. E. Cipient\" \$" patch8 ' test_expect_failure 'command line To: header (rfc2047)' ' @@@ -203,7 -203,7 +203,7 @@@ test_expect_failure 'configuration To: git config format.to "R. E. Cipient " && git format-patch --stdout master..side | sed -e "/^\$/q" >patch9 && - grep "^To: "R. E. Cipient" \$" patch9 + grep "^To: \"R. E. Cipient\" \$" patch9 ' test_expect_failure 'configuration To: header (rfc2047)' ' @@@ -271,22 -271,6 +271,22 @@@ test_expect_success 'multiple files' 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 'reroll count' ' + rm -fr patches && + git format-patch -o patches --cover-letter --reroll-count 4 master..side >list && + ! grep -v "^patches/v4-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects +' + +test_expect_success 'reroll count (-v)' ' + rm -fr patches && + git format-patch -o patches --cover-letter -v4 master..side >list && + ! grep -v "^patches/v4-000[0-3]-" list && + sed -n -e "/^Subject: /p" $(cat list) >subjects && + ! grep -v "^Subject: \[PATCH v4 [0-3]/3\] " subjects +' + check_threading () { expect="$1" && shift && @@@ -837,26 -821,25 +837,26 @@@ Subject: [PATCH] =?UTF-8?q?f=C3=B6=C3=B =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?= =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?= - =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?= - =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= - =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?= - =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= + =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= + =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= + =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?= + =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?= + =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?= =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?= - =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?= - =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= - =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?= - =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= + =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?= =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?= - =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?= - =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= - =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?= - =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= - =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= + =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?= + =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?= + =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?= + =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?= + =?UTF-8?q?bar?= EOF test_expect_success 'format-patch wraps extremely long subject (rfc2047)' ' rm -rf patches/ && @@@ -972,54 -955,274 +972,316 @@@ test_expect_success 'empty subject pref test_cmp expect actual ' -test_expect_success 'format patch ignores color.ui' ' - test_unconfig color.ui && - git format-patch --stdout -1 >expect && - test_config color.ui always && - git format-patch --stdout -1 >actual && - test_cmp expect actual -' - + append_signoff() + { + C=$(git commit-tree HEAD^^{tree} -p HEAD) && + git format-patch --stdout --signoff $C^..$C >append_signoff.patch && + sed -n -e "1,/^---$/p" append_signoff.patch | + egrep -n "^Subject|Sign|^$" + } + + test_expect_success 'signoff: commit with no body' ' + append_signoff actual && + cat <<\EOF | sed "s/EOL$//" >expected && + 4:Subject: [PATCH] EOL + 8: + 9:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: commit with only subject' ' + echo subject | append_signoff >actual && + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 9:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: commit with only subject that does not end with NL' ' + printf subject | append_signoff >actual && + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 9:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: no existing signoffs' ' + append_signoff <<\EOF >actual && + subject + + body + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: no existing signoffs and no trailing NL' ' + printf "subject\n\nbody" | append_signoff >actual && + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: some random signoff' ' + append_signoff <<\EOF >actual && + subject + + body + + Signed-off-by: my@house + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: my@house + 12:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: misc conforming footer elements' ' + append_signoff <<\EOF >actual && + subject + + body + + Signed-off-by: my@house + (cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709) + Tested-by: Some One + Bug: 1234 + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: my@house + 15:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: some random signoff-alike' ' + append_signoff <<\EOF >actual && + subject + + body + Fooled-by-me: my@house + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 11: + 12:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: not really a signoff' ' + append_signoff <<\EOF >actual && + subject + + I want to mention about Signed-off-by: here. + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 9:I want to mention about Signed-off-by: here. + 10: + 11:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: not really a signoff (2)' ' + append_signoff <<\EOF >actual && + subject + + My unfortunate + Signed-off-by: example happens to be wrapped here. + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10:Signed-off-by: example happens to be wrapped here. + 11: + 12:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: valid S-o-b paragraph in the middle' ' + append_signoff <<\EOF >actual && + subject + + Signed-off-by: my@house + Signed-off-by: your@house + + A lot of houses. + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 9:Signed-off-by: my@house + 10:Signed-off-by: your@house + 11: + 13: + 14:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: the same signoff at the end' ' + append_signoff <<\EOF >actual && + subject + + body + + Signed-off-by: C O Mitter + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: the same signoff at the end, no trailing NL' ' + printf "subject\n\nSigned-off-by: C O Mitter " | + append_signoff >actual && + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 9:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: the same signoff NOT at the end' ' + append_signoff <<\EOF >actual && + subject + + body + + Signed-off-by: C O Mitter + Signed-off-by: my@house + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 11:Signed-off-by: C O Mitter + 12:Signed-off-by: my@house + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: detect garbage in non-conforming footer' ' + append_signoff <<\EOF >actual && + subject + + body + + Tested-by: my@house + Some Trash + Signed-off-by: C O Mitter + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 13:Signed-off-by: C O Mitter + 14: + 15:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + + test_expect_success 'signoff: footer begins with non-signoff without @ sign' ' + append_signoff <<\EOF >actual && + subject + + body + + Reviewed-id: Noone + Tested-by: my@house + Change-id: Ideadbeef + Signed-off-by: C O Mitter + Bug: 1234 + EOF + cat >expected <<\EOF && + 4:Subject: [PATCH] subject + 8: + 10: + 14:Signed-off-by: C O Mitter + EOF + test_cmp expected actual + ' + +test_expect_success 'format patch ignores color.ui' ' + test_unconfig color.ui && + git format-patch --stdout -1 >expect && + test_config color.ui always && + git format-patch --stdout -1 >actual && + test_cmp expect actual +' + +test_expect_success 'cover letter using branch description (1)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter master >actual && + grep hello actual >/dev/null +' + +test_expect_success 'cover letter using branch description (2)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter rebuild-1~2..rebuild-1 >actual && + grep hello actual >/dev/null +' + +test_expect_success 'cover letter using branch description (3)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter ^master rebuild-1 >actual && + grep hello actual >/dev/null +' + +test_expect_success 'cover letter using branch description (4)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter master.. >actual && + grep hello actual >/dev/null +' + +test_expect_success 'cover letter using branch description (5)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter -2 HEAD >actual && + grep hello actual >/dev/null +' + +test_expect_success 'cover letter using branch description (6)' ' + git checkout rebuild-1 && + test_config branch.rebuild-1.description hello && + git format-patch --stdout --cover-letter -2 >actual && + grep hello actual >/dev/null +' + test_done diff --combined t/t7502-commit.sh index 256137f07a,c06a7526c1..a4938b1e45 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@@ -4,15 -4,6 +4,15 @@@ test_description='git commit porcelain- . ./test-lib.sh +commit_msg_is () { + expect=commit_msg_is.expect + actual=commit_msg_is.actual + + printf "%s" "$(git log --pretty=format:%s%b -1)" >$actual && + printf "%s" "$1" >$expect && + test_i18ncmp $expect $actual +} + # Arguments: [] [] check_summary_oneline() { test_tick && @@@ -171,30 -162,23 +171,30 @@@ test_expect_success 'verbose' test_expect_success 'verbose respects diff config' ' - git config color.diff always && + test_config color.diff always && git status -v >actual && - grep "\[1mdiff --git" actual && - git config --unset color.diff + grep "\[1mdiff --git" actual ' -test_expect_success 'cleanup commit messages (verbatim,-t)' ' +mesg_with_comment_and_newlines=' +# text + +' + +test_expect_success 'prepare file with comment line and trailing newlines' ' + printf "%s" "$mesg_with_comment_and_newlines" >expect +' + +test_expect_success 'cleanup commit messages (verbatim option,-t)' ' echo >>negative && - { echo;echo "# text";echo; } >expect && - git commit --cleanup=verbatim -t expect -a && - git cat-file -p HEAD |sed -e "1,/^\$/d" |head -n 3 >actual && + git commit --cleanup=verbatim --no-status -t expect -a && + git cat-file -p HEAD |sed -e "1,/^\$/d" >actual && test_cmp expect actual ' -test_expect_success 'cleanup commit messages (verbatim,-F)' ' +test_expect_success 'cleanup commit messages (verbatim option,-F)' ' echo >>negative && git commit --cleanup=verbatim -F expect -a && @@@ -203,16 -187,16 +203,16 @@@ ' -test_expect_success 'cleanup commit messages (verbatim,-m)' ' +test_expect_success 'cleanup commit messages (verbatim option,-m)' ' echo >>negative && - git commit --cleanup=verbatim -m "$(cat expect)" -a && + git commit --cleanup=verbatim -m "$mesg_with_comment_and_newlines" -a && git cat-file -p HEAD |sed -e "1,/^\$/d">actual && test_cmp expect actual ' -test_expect_success 'cleanup commit messages (whitespace,-F)' ' +test_expect_success 'cleanup commit messages (whitespace option,-F)' ' echo >>negative && { echo;echo "# text";echo; } >text && @@@ -223,7 -207,7 +223,7 @@@ ' -test_expect_success 'cleanup commit messages (strip,-F)' ' +test_expect_success 'cleanup commit messages (strip option,-F)' ' echo >>negative && { echo;echo "# text";echo sample;echo; } >text && @@@ -234,7 -218,7 +234,7 @@@ ' -test_expect_success 'cleanup commit messages (strip,-F,-e)' ' +test_expect_success 'cleanup commit messages (strip option,-F,-e)' ' echo >>negative && { echo;echo sample;echo; } >text && @@@ -247,79 -231,10 +247,79 @@@ echo "sampl # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit." >expect -test_expect_success 'cleanup commit messages (strip,-F,-e): output' ' +test_expect_success 'cleanup commit messages (strip option,-F,-e): output' ' test_i18ncmp expect actual ' +test_expect_success 'cleanup commit message (fail on invalid cleanup mode option)' ' + test_must_fail git commit --cleanup=non-existent +' + +test_expect_success 'cleanup commit message (fail on invalid cleanup mode configuration)' ' + test_must_fail git -c commit.cleanup=non-existent commit +' + +test_expect_success 'cleanup commit message (no config and no option uses default)' ' + echo content >>file && + git add file && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --no-status + ) && + commit_msg_is "commit message" +' + +test_expect_success 'cleanup commit message (option overrides default)' ' + echo content >>file && + git add file && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git commit --cleanup=whitespace --no-status + ) && + commit_msg_is "commit message # comment" +' + +test_expect_success 'cleanup commit message (config overrides default)' ' + echo content >>file && + git add file && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --no-status + ) && + commit_msg_is "commit message # comment" +' + +test_expect_success 'cleanup commit message (option overrides config)' ' + echo content >>file && + git add file && + ( + test_set_editor "$TEST_DIRECTORY"/t7500/add-content-and-comment && + git -c commit.cleanup=whitespace commit --cleanup=default + ) && + commit_msg_is "commit message" +' + +test_expect_success 'cleanup commit message (default, -m)' ' + echo content >>file && + git add file && + git commit -m "message #comment " && + commit_msg_is "message #comment" +' + +test_expect_success 'cleanup commit message (whitespace option, -m)' ' + echo content >>file && + git add file && + git commit --cleanup=whitespace --no-status -m "message #comment " && + commit_msg_is "message #comment" +' + +test_expect_success 'cleanup commit message (whitespace config, -m)' ' + echo content >>file && + git add file && + git -c commit.cleanup=whitespace commit --no-status -m "message #comment " && + commit_msg_is "message #comment" +' + test_expect_success 'message shows author when it is not equal to committer' ' echo >>negative && git commit -e -m "sample" -a && @@@ -328,6 -243,16 +328,6 @@@ .git/COMMIT_EDITMSG ' -test_expect_success 'setup auto-ident prerequisite' ' - if (sane_unset GIT_COMMITTER_EMAIL && - sane_unset GIT_COMMITTER_NAME && - git var GIT_COMMITTER_IDENT); then - test_set_prereq AUTOIDENT - else - test_set_prereq NOAUTOIDENT - fi -' - test_expect_success AUTOIDENT 'message shows committer when it is automatic' ' echo >>negative && @@@ -346,7 -271,7 +346,7 @@@ echo editor started > "$(pwd)/.git/resu exit 0 EOF -test_expect_success NOAUTOIDENT 'do not fire editor when committer is bogus' ' +test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' ' >.git/result >expect && @@@ -424,6 -349,18 +424,18 @@@ test_expect_success 'A single-liner sub ' + test_expect_success 'commit -s places sob on third line after two empty lines' ' + git commit -s --allow-empty --allow-empty-message && + cat <<-EOF >expect && + + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + + EOF + sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual && + test_cmp expect actual + ' + write_script .git/FAKE_EDITOR <<\EOF mv "$1" "$1.orig" ( @@@ -434,6 -371,16 +446,6 @@@ EO echo '## Custom template' >template -clear_config () { - ( - git config --unset-all "$1" - case $? in - 0|5) exit 0 ;; - *) exit 1 ;; - esac - ) -} - try_commit () { git reset --hard && echo >>negative && @@@ -449,57 -396,67 +461,57 @@@ try_commit_status_combo () { test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit' ' - clear_config commit.status && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status' ' - clear_config commit.status && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status' ' - clear_config commit.status && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit "" && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit "" && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = yes' ' - clear_config commit.status && - git config commit.status yes && + test_config commit.status yes && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --status && test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG ' test_expect_success 'commit --no-status with commit.status = no' ' - clear_config commit.status && - git config commit.status no && + test_config commit.status no && try_commit --no-status && test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG ' @@@ -512,10 -469,4 +524,10 @@@ use_template="-t template try_commit_status_combo +test_expect_success 'commit --status with custom comment character' ' + test_config core.commentchar ";" && + try_commit --status && + test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG +' + test_done diff --combined t/test-lib-functions.sh index fa62d010f6,7c4c633592..61d0804435 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@@ -135,12 -135,12 +135,12 @@@ test_pause () fi } - # Call test_commit with the arguments " [ []]" + # Call test_commit with the arguments " [ [ []]]" # # This will commit a file with the given contents and the given commit - # message. It will also add a tag with as name. + # message, and tag the resulting commit with the given tag name. # - # Both and default to . + # , , and all default to . test_commit () { notick= && @@@ -168,7 -168,7 +168,7 @@@ test_tick fi && git commit $signoff -m "$1" && - git tag "$1" + git tag "${4:-$1}" } # Call test_merge with the arguments " ", where @@@ -275,15 -275,6 +275,15 @@@ test_have_prereq () for prerequisite do + case "$prerequisite" in + !*) + negative_prereq=t + prerequisite=${prerequisite#!} + ;; + *) + negative_prereq= + esac + case " $lazily_tested_prereq " in *" $prerequisite "*) ;; @@@ -303,20 -294,10 +303,20 @@@ total_prereq=$(($total_prereq + 1)) case "$satisfied_prereq" in *" $prerequisite "*) + satisfied_this_prereq=t + ;; + *) + satisfied_this_prereq= + esac + + case "$satisfied_this_prereq,$negative_prereq" in + t,|,t) ok_prereq=$(($ok_prereq + 1)) ;; *) - # Keep a list of missing prerequisites + # Keep a list of missing prerequisites; restore + # the negative marker if necessary. + prerequisite=${negative_prereq:+!}$prerequisite if test -z "$missing_prereq" then missing_prereq=$prerequisite @@@ -602,13 -583,6 +602,13 @@@ test_cmp() $GIT_TEST_CMP "$@" } +# Tests that its two parameters refer to the same revision +test_cmp_rev () { + git rev-parse --verify "$1" >expect.rev && + git rev-parse --verify "$2" >actual.rev && + test_cmp expect.rev actual.rev +} + # Print a sequence of numbers or letters in increasing order. This is # similar to GNU seq(1), but the latter might not be available # everywhere (and does not do letters). It may be used like: