diff options
-rw-r--r-- | Documentation/git-interpret-trailers.txt | 24 | ||||
-rw-r--r-- | builtin/interpret-trailers.c | 13 | ||||
-rwxr-xr-x | t/t7513-interpret-trailers.sh | 40 | ||||
-rw-r--r-- | trailer.c | 69 | ||||
-rw-r--r-- | trailer.h | 3 |
5 files changed, 129 insertions, 20 deletions
diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt index 0ecd497c4d..a77b901f1d 100644 --- a/Documentation/git-interpret-trailers.txt +++ b/Documentation/git-interpret-trailers.txt @@ -8,7 +8,7 @@ git-interpret-trailers - help add structured information into commit messages SYNOPSIS -------- [verse] -'git interpret-trailers' [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...] +'git interpret-trailers' [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...] DESCRIPTION ----------- @@ -64,6 +64,9 @@ folding rules, the encoding rules and probably many other rules. OPTIONS ------- +--in-place:: + Edit the files in place. + --trim-empty:: If the <value> part of any trailer contains only whitespace, the whole trailer will be removed from the resulting message. @@ -216,6 +219,25 @@ Signed-off-by: Alice <alice@example.com> Signed-off-by: Bob <bob@example.com> ------------ +* Use the '--in-place' option to edit a message file in place: ++ +------------ +$ cat msg.txt +subject + +message + +Signed-off-by: Bob <bob@example.com> +$ git interpret-trailers --trailer 'Acked-by: Alice <alice@example.com>' --in-place msg.txt +$ cat msg.txt +subject + +message + +Signed-off-by: Bob <bob@example.com> +Acked-by: Alice <alice@example.com> +------------ + * Extract the last commit as a patch, and add a 'Cc' and a 'Reviewed-by' trailer to it: + diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 46838d24a9..b99ae4be88 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c @@ -12,16 +12,18 @@ #include "trailer.h" static const char * const git_interpret_trailers_usage[] = { - N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), + N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"), NULL }; int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) { + int in_place = 0; int trim_empty = 0; struct string_list trailers = STRING_LIST_INIT_DUP; struct option options[] = { + OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")), OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")), OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"), N_("trailer(s) to add")), @@ -34,9 +36,12 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) if (argc) { int i; for (i = 0; i < argc; i++) - process_trailers(argv[i], trim_empty, &trailers); - } else - process_trailers(NULL, trim_empty, &trailers); + process_trailers(argv[i], in_place, trim_empty, &trailers); + } else { + if (in_place) + die(_("no input file given for in-place editing")); + process_trailers(NULL, in_place, trim_empty, &trailers); + } string_list_clear(&trailers, 0); diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 322c436a49..aee785cffa 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -326,6 +326,46 @@ test_expect_success 'with complex patch, args and --trim-empty' ' test_cmp expected actual ' +test_expect_success 'in-place editing with basic patch' ' + cat basic_message >message && + cat basic_patch >>message && + cat basic_message >expected && + echo >>expected && + cat basic_patch >>expected && + git interpret-trailers --in-place message && + test_cmp expected message +' + +test_expect_success 'in-place editing with additional trailer' ' + cat basic_message >message && + cat basic_patch >>message && + cat basic_message >expected && + echo >>expected && + cat >>expected <<-\EOF && + Reviewed-by: Alice + EOF + cat basic_patch >>expected && + git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message && + test_cmp expected message +' + +test_expect_success 'in-place editing on stdin disallowed' ' + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place < basic_message +' + +test_expect_success 'in-place editing on non-existing file' ' + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place nonexisting && + test_path_is_missing nonexisting +' + +test_expect_success POSIXPERM,SANITY "in-place editing doesn't clobber original file on error" ' + cat basic_message >message && + chmod -r message && + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message && + chmod +r message && + test_cmp message basic_message +' + test_expect_success 'using "where = before"' ' git config trailer.bug.where "before" && cat complex_message_body >expected && @@ -2,6 +2,7 @@ #include "string-list.h" #include "run-command.h" #include "commit.h" +#include "tempfile.h" #include "trailer.h" /* * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> @@ -108,23 +109,23 @@ static char last_non_space_char(const char *s) return '\0'; } -static void print_tok_val(const char *tok, const char *val) +static void print_tok_val(FILE *outfile, const char *tok, const char *val) { char c = last_non_space_char(tok); if (!c) return; if (strchr(separators, c)) - printf("%s%s\n", tok, val); + fprintf(outfile, "%s%s\n", tok, val); else - printf("%s%c %s\n", tok, separators[0], val); + fprintf(outfile, "%s%c %s\n", tok, separators[0], val); } -static void print_all(struct trailer_item *first, int trim_empty) +static void print_all(FILE *outfile, struct trailer_item *first, int trim_empty) { struct trailer_item *item; for (item = first; item; item = item->next) { if (!trim_empty || strlen(item->value) > 0) - print_tok_val(item->token, item->value); + print_tok_val(outfile, item->token, item->value); } } @@ -795,14 +796,15 @@ static int has_blank_line_before(struct strbuf **lines, int start) return 0; } -static void print_lines(struct strbuf **lines, int start, int end) +static void print_lines(FILE *outfile, struct strbuf **lines, int start, int end) { int i; for (i = start; lines[i] && i < end; i++) - printf("%s", lines[i]->buf); + fprintf(outfile, "%s", lines[i]->buf); } -static int process_input_file(struct strbuf **lines, +static int process_input_file(FILE *outfile, + struct strbuf **lines, struct trailer_item **in_tok_first, struct trailer_item **in_tok_last) { @@ -818,10 +820,10 @@ static int process_input_file(struct strbuf **lines, trailer_start = find_trailer_start(lines, trailer_end); /* Print lines before the trailers as is */ - print_lines(lines, 0, trailer_start); + print_lines(outfile, lines, 0, trailer_start); if (!has_blank_line_before(lines, trailer_start - 1)) - printf("\n"); + fprintf(outfile, "\n"); /* Parse trailer lines */ for (i = trailer_start; i < trailer_end; i++) { @@ -842,13 +844,45 @@ static void free_all(struct trailer_item **first) } } -void process_trailers(const char *file, int trim_empty, struct string_list *trailers) +static struct tempfile trailers_tempfile; + +static FILE *create_in_place_tempfile(const char *file) +{ + struct stat st; + struct strbuf template = STRBUF_INIT; + const char *tail; + FILE *outfile; + + if (stat(file, &st)) + die_errno(_("could not stat %s"), file); + if (!S_ISREG(st.st_mode)) + die(_("file %s is not a regular file"), file); + if (!(st.st_mode & S_IWUSR)) + die(_("file %s is not writable by user"), file); + + /* Create temporary file in the same directory as the original */ + tail = strrchr(file, '/'); + if (tail != NULL) + strbuf_add(&template, file, tail - file + 1); + strbuf_addstr(&template, "git-interpret-trailers-XXXXXX"); + + xmks_tempfile_m(&trailers_tempfile, template.buf, st.st_mode); + strbuf_release(&template); + outfile = fdopen_tempfile(&trailers_tempfile, "w"); + if (!outfile) + die_errno(_("could not open temporary file")); + + return outfile; +} + +void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers) { struct trailer_item *in_tok_first = NULL; struct trailer_item *in_tok_last = NULL; struct trailer_item *arg_tok_first; struct strbuf **lines; int trailer_end; + FILE *outfile = stdout; /* Default config must be setup first */ git_config(git_trailer_default_config, NULL); @@ -856,19 +890,26 @@ void process_trailers(const char *file, int trim_empty, struct string_list *trai lines = read_input_file(file); + if (in_place) + outfile = create_in_place_tempfile(file); + /* Print the lines before the trailers */ - trailer_end = process_input_file(lines, &in_tok_first, &in_tok_last); + trailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last); arg_tok_first = process_command_line_args(trailers); process_trailers_lists(&in_tok_first, &in_tok_last, &arg_tok_first); - print_all(in_tok_first, trim_empty); + print_all(outfile, in_tok_first, trim_empty); free_all(&in_tok_first); /* Print the lines after the trailers as is */ - print_lines(lines, trailer_end, INT_MAX); + print_lines(outfile, lines, trailer_end, INT_MAX); + + if (in_place) + if (rename_tempfile(&trailers_tempfile, file)) + die_errno(_("could not rename temporary file to %s"), file); strbuf_list_free(lines); } @@ -1,6 +1,7 @@ #ifndef TRAILER_H #define TRAILER_H -void process_trailers(const char *file, int trim_empty, struct string_list *trailers); +void process_trailers(const char *file, int in_place, int trim_empty, + struct string_list *trailers); #endif /* TRAILER_H */ |