aboutsummaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--t/meson.build1
-rwxr-xr-xt/t3440-rebase-trailer.sh134
-rw-r--r--trailer.c129
-rw-r--r--trailer.h13
4 files changed, 265 insertions, 12 deletions
diff --git a/t/meson.build b/t/meson.build
index 4e070cbbf7..358477fbfc 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -385,6 +385,7 @@ integration_tests = [
't3436-rebase-more-options.sh',
't3437-rebase-fixup-options.sh',
't3438-rebase-broken-files.sh',
+ 't3440-rebase-trailer.sh',
't3450-history.sh',
't3451-history-reword.sh',
't3452-history-split.sh',
diff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh
new file mode 100755
index 0000000000..d0e0434664
--- /dev/null
+++ b/t/t3440-rebase-trailer.sh
@@ -0,0 +1,134 @@
+#!/bin/sh
+#
+
+test_description='git rebase --trailer integration tests
+We verify that --trailer works with the merge backend,
+and that it is rejected early when the apply backend is requested.'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh # test_commit_message, helpers
+
+REVIEWED_BY_TRAILER="Reviewed-by: Dev <dev@example.com>"
+
+expect_trailer_msg() {
+ test_commit_message "$1" <<-EOF
+ $2
+
+ ${3:-$REVIEWED_BY_TRAILER}
+ EOF
+}
+
+test_expect_success 'setup repo with a small history' '
+ git commit --allow-empty -m "Initial empty commit" &&
+ test_commit first file a &&
+ test_commit second file &&
+ git checkout -b conflict-branch first &&
+ test_commit file-2 file-2 &&
+ test_commit conflict file &&
+ test_commit third file
+'
+
+test_expect_success 'apply backend is rejected with --trailer' '
+ head_before=$(git rev-parse HEAD) &&
+ test_expect_code 128 \
+ git rebase --apply --trailer "$REVIEWED_BY_TRAILER" \
+ HEAD^ 2>err &&
+ test_grep "fatal: --trailer requires the merge backend" err &&
+ test_cmp_rev HEAD $head_before
+'
+
+test_expect_success 'reject empty --trailer argument' '
+ test_expect_code 128 git rebase -m --trailer "" HEAD^ 2>err &&
+ test_grep "empty --trailer" err
+'
+
+test_expect_success 'reject trailer with missing key before separator' '
+ test_expect_code 128 git rebase -m --trailer ": no-key" HEAD^ 2>err &&
+ test_grep "missing key before separator" err
+'
+
+test_expect_success 'allow trailer with missing value after separator' '
+ git rebase -m --trailer "Acked-by:" HEAD~1 third &&
+ sed -e "s/_/ /g" <<-\EOF >expect &&
+ third
+
+ Acked-by:_
+ EOF
+ test_commit_message HEAD expect
+'
+
+test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' '
+ git -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \
+ rebase -m --trailer "Bug: 123" --trailer "Bug: 456" HEAD~1 third &&
+ cat >expect <<-\EOF &&
+ third
+
+ Bug: 456
+ EOF
+ test_commit_message HEAD expect
+'
+
+test_expect_success 'multiple Signed-off-by trailers all preserved' '
+ git rebase -m \
+ --trailer "Signed-off-by: Dev A <a@example.com>" \
+ --trailer "Signed-off-by: Dev B <b@example.com>" HEAD~1 third &&
+ cat >expect <<-\EOF &&
+ third
+
+ Signed-off-by: Dev A <a@example.com>
+ Signed-off-by: Dev B <b@example.com>
+ EOF
+ test_commit_message HEAD expect
+'
+
+test_expect_success 'rebase -m --trailer adds trailer after conflicts' '
+ git checkout -B conflict-branch third &&
+ test_commit fourth file &&
+ test_must_fail git rebase -m \
+ --trailer "$REVIEWED_BY_TRAILER" \
+ second &&
+ git checkout --theirs file &&
+ git add file &&
+ git rebase --continue &&
+ expect_trailer_msg HEAD "fourth" &&
+ expect_trailer_msg HEAD^ "third"
+'
+
+test_expect_success '--trailer handles fixup commands in todo list' '
+ git checkout -B fixup-trailer HEAD &&
+ test_commit fixup-base base &&
+ test_commit fixup-second second &&
+ first_short=$(git rev-parse --short fixup-base) &&
+ second_short=$(git rev-parse --short fixup-second) &&
+ cat >todo <<EOF &&
+pick $first_short fixup-base
+fixup $second_short fixup-second
+EOF
+ (
+ set_replace_editor todo &&
+ git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2
+ ) &&
+ expect_trailer_msg HEAD "fixup-base" &&
+ git reset --hard fixup-second &&
+ cat >todo <<EOF &&
+pick $first_short fixup-base
+fixup -C $second_short fixup-second
+EOF
+ (
+ set_replace_editor todo &&
+ git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2
+ ) &&
+ expect_trailer_msg HEAD "fixup-second"
+'
+
+test_expect_success 'rebase --root --trailer updates every commit' '
+ git checkout first &&
+ git -c trailer.review.key=Reviewed-by rebase --root \
+ --trailer=review="Dev <dev@example.com>" &&
+ expect_trailer_msg HEAD "first" &&
+ expect_trailer_msg HEAD^ "Initial empty commit"
+'
+test_done
diff --git a/trailer.c b/trailer.c
index 911a81ed99..f6ff2f01ee 100644
--- a/trailer.c
+++ b/trailer.c
@@ -7,8 +7,11 @@
#include "string-list.h"
#include "run-command.h"
#include "commit.h"
+#include "strvec.h"
#include "trailer.h"
#include "list.h"
+#include "wrapper.h"
+
/*
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
*/
@@ -772,6 +775,30 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,
free(cl_separators);
}
+void validate_trailer_args_after_config(const struct strvec *cli_args)
+{
+ char *cl_separators;
+
+ trailer_config_init();
+
+ cl_separators = xstrfmt("=%s", separators);
+
+ for (size_t i = 0; i < cli_args->nr; i++) {
+ const char *txt = cli_args->v[i];
+ ssize_t separator_pos;
+
+ if (!*txt)
+ die(_("empty --trailer argument"));
+
+ separator_pos = find_separator(txt, cl_separators);
+ if (separator_pos == 0)
+ die(_("invalid trailer '%s': missing key before separator"),
+ txt);
+ }
+
+ free(cl_separators);
+}
+
static const char *next_line(const char *str)
{
const char *nl = strchrnul(str, '\n');
@@ -1224,14 +1251,98 @@ void trailer_iterator_release(struct trailer_iterator *iter)
strbuf_release(&iter->key);
}
-int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)
+int amend_strbuf_with_trailers(struct strbuf *buf,
+ const struct strvec *trailer_args)
{
- struct child_process run_trailer = CHILD_PROCESS_INIT;
-
- run_trailer.git_cmd = 1;
- strvec_pushl(&run_trailer.args, "interpret-trailers",
- "--in-place", "--no-divider",
- path, NULL);
- strvec_pushv(&run_trailer.args, trailer_args->v);
- return run_command(&run_trailer);
+ struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
+ LIST_HEAD(new_trailer_head);
+ struct strbuf out = STRBUF_INIT;
+ size_t i;
+
+ opts.no_divider = 1;
+
+ for (i = 0; i < trailer_args->nr; i++) {
+ const char *text = trailer_args->v[i];
+ struct new_trailer_item *item;
+
+ if (!*text)
+ continue;
+ item = xcalloc(1, sizeof(*item));
+ INIT_LIST_HEAD(&item->list);
+ item->text = text;
+ list_add_tail(&item->list, &new_trailer_head);
+ }
+
+ process_trailers(&opts, &new_trailer_head, buf, &out);
+
+ strbuf_swap(buf, &out);
+ strbuf_release(&out);
+ while (!list_empty(&new_trailer_head)) {
+ struct new_trailer_item *item =
+ list_first_entry(&new_trailer_head, struct new_trailer_item, list);
+ list_del(&item->list);
+ free(item);
+ }
+ return 0;
+}
+
+int amend_file_with_trailers(const char *path,
+ const struct strvec *trailer_args)
+{
+ struct strbuf buf = STRBUF_INIT;
+
+ if (!trailer_args || !trailer_args->nr)
+ return 0;
+
+ if (strbuf_read_file(&buf, path, 0) < 0)
+ return error_errno("could not read '%s'", path);
+
+ if (amend_strbuf_with_trailers(&buf, trailer_args)) {
+ strbuf_release(&buf);
+ return error("failed to append trailers");
+ }
+
+ if (write_file_buf_gently(path, buf.buf, buf.len)) {
+ strbuf_release(&buf);
+ return -1;
+ }
+
+ strbuf_release(&buf);
+ return 0;
+ }
+
+void process_trailers(const struct process_trailer_options *opts,
+ struct list_head *new_trailer_head,
+ struct strbuf *sb, struct strbuf *out)
+{
+ LIST_HEAD(head);
+ struct trailer_block *trailer_block;
+
+ trailer_block = parse_trailers(opts, sb->buf, &head);
+
+ /* Print the lines before the trailer block */
+ if (!opts->only_trailers)
+ strbuf_add(out, sb->buf, trailer_block_start(trailer_block));
+
+ if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block))
+ strbuf_addch(out, '\n');
+
+ if (!opts->only_input) {
+ LIST_HEAD(config_head);
+ LIST_HEAD(arg_head);
+ parse_trailers_from_config(&config_head);
+ parse_trailers_from_command_line_args(&arg_head, new_trailer_head);
+ list_splice(&config_head, &arg_head);
+ process_trailers_lists(&head, &arg_head);
+ }
+
+ /* Print trailer block. */
+ format_trailers(opts, &head, out);
+ free_trailers(&head);
+
+ /* Print the lines after the trailer block as is. */
+ if (!opts->only_trailers)
+ strbuf_add(out, sb->buf + trailer_block_end(trailer_block),
+ sb->len - trailer_block_end(trailer_block));
+ trailer_block_release(trailer_block);
}
diff --git a/trailer.h b/trailer.h
index 4740549586..541657a11f 100644
--- a/trailer.h
+++ b/trailer.h
@@ -68,6 +68,8 @@ void parse_trailers_from_config(struct list_head *config_head);
void parse_trailers_from_command_line_args(struct list_head *arg_head,
struct list_head *new_trailer_head);
+void validate_trailer_args_after_config(const struct strvec *cli_args);
+
void process_trailers_lists(struct list_head *head,
struct list_head *arg_head);
@@ -195,11 +197,16 @@ int trailer_iterator_advance(struct trailer_iterator *iter);
*/
void trailer_iterator_release(struct trailer_iterator *iter);
+int amend_strbuf_with_trailers(struct strbuf *buf,
+ const struct strvec *trailer_args);
+
/*
- * Augment a file to add trailers to it by running git-interpret-trailers.
- * This calls run_command() and its return value is the same (i.e. 0 for
- * success, various non-zero for other errors). See run-command.h.
+ * Augment a file to add trailers to it (similar to 'git interpret-trailers').
+ * Returns 0 on success or a non-zero error code on failure.
*/
int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);
+void process_trailers(const struct process_trailer_options *opts,
+ struct list_head *new_trailer_head,
+ struct strbuf *sb, struct strbuf *out);
#endif /* TRAILER_H */