@@ -8,7 +8,7 @@ git-commit - Record changes to the repository
SYNOPSIS
--------
[verse]
-'git commit' [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]
+'git commit' [-a | --interactive | --patch] [-s[<mode>]] [-v] [-u<mode>] [--amend]
[--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]
[-F <file> | -m <msg>] [--reset-author] [--allow-empty]
[--allow-empty-message] [--no-verify] [-e] [--author=<author>]
@@ -1,7 +1,7 @@
ifdef::git-commit[]
--s::
+-s[<mode>]::
endif::git-commit[]
---signoff::
+--signoff[=<mode>]::
--no-signoff::
Add a `Signed-off-by` trailer by the committer at the end of the commit
log message. The meaning of a signoff depends on the project
@@ -16,3 +16,13 @@ endif::git-commit[]
+
The --no-signoff option can be used to countermand an earlier --signoff
option on the command line.
++
+The mode parameter is optional (defaults to no-dedup), and is used to specify
+if it should deduplicate the `Signed-off-by` trailer or not when trying to
+add it to the trailers.
++
+The possible options are:
++
+ - 'no-dedup' - Will add the trailer unless it's already the last line.
+ - 'dedup' - Will not add the trailer if it's already anywhere in the
+ trailers.
@@ -40,7 +40,7 @@
#include "pretty.h"
static const char * const builtin_commit_usage[] = {
- N_("git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\n"
+ N_("git commit [-a | --interactive | --patch] [-s[<mode>]] [-v] [-u<mode>] [--amend]\n"
" [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]\n"
" [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n"
" [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n"
@@ -116,13 +116,13 @@ static const char *author_message, *author_message_buffer;
static char *edit_message, *use_message;
static char *fixup_message, *fixup_commit, *squash_message;
static const char *fixup_prefix;
-static int all, also, interactive, patch_interactive, only, amend, signoff;
+static int all, also, interactive, patch_interactive, only, amend;
static int edit_flag = -1; /* unspecified */
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
static int config_commit_verbose = -1; /* unspecified */
static int no_post_rewrite, allow_empty_message, pathspec_file_nul;
static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
-static char *sign_commit, *pathspec_from_file;
+static char *sign_commit, *pathspec_from_file, *signoff_arg;
static struct strvec trailer_args = STRVEC_INIT;
/*
@@ -888,8 +888,14 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (clean_message_contents)
strbuf_stripspace(&sb, 0);
- if (signoff)
- append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
+ if (signoff_arg) {
+ if (!strcmp(signoff_arg, "dedup"))
+ append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 1);
+ else if (!strcmp(signoff_arg, "no-dedup"))
+ append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0);
+ else
+ die(_("Invalid signoff mode: %s"), signoff_arg);
+ }
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
die_errno(_("could not write commit template"));
@@ -1645,7 +1651,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
- OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
+ { OPTION_STRING, 's', "signoff", &signoff_arg, N_("mode"),
+ N_("add a Signed-off-by trailer, optional modes: no-dedup, dedup. (Default: no-dedup)"), PARSE_OPT_OPTARG, NULL, (intptr_t) "no-dedup" },
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
OPT_CLEANUP(&cleanup_arg),
@@ -541,6 +541,75 @@ test_expect_success 'signoff not confused by ---' '
test_cmp expected actual
'
+test_expect_success 'signoff default mode adds when not last in trailers' '
+ cat >message <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ EOF
+ cat >expected <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git commit --allow-empty --signoff -F message &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff no-dedup mode adds when not last in trailers' '
+ cat >message <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ EOF
+ cat >expected <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git commit --allow-empty --signoff=no-dedup -F message &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expected actual
+'
+
+
+test_expect_success 'signoff dedup mode does not duplicate when not last in trailers' '
+ cat >message <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ EOF
+ cat >expected <<-EOF &&
+ subject
+
+ body
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ Signed-off-by: A. U. <author@example.com>
+ EOF
+ git commit --allow-empty --signoff=dedup -F message &&
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'multiple -m' '
>negative &&