diff options
Diffstat (limited to '')
| -rw-r--r-- | t/meson.build | 1 | ||||
| -rwxr-xr-x | t/t3440-rebase-trailer.sh | 134 | ||||
| -rw-r--r-- | trailer.c | 129 | ||||
| -rw-r--r-- | trailer.h | 13 |
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 @@ -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); } @@ -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 */ |
