interpret-trailers: add option for in-place editing

Add a command line option --in-place to support in-place editing akin to sed -i. This allows to write commands like the following: git interpret-trailers --trailer "X: Y" a.txt > b.txt && mv b.txt a.txt in a more concise way: git interpret-trailers --trailer "X: Y" --in-place a.txt Signed-off-by: Tobias Klauser <tklauser@distanz.ch> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tobias Klauser committed Jan 14, 2016 at 17:57 UTC e1f898639e906158fec26bdf3111d6f623288fa1
5 files changed +114 -7
Documentation/git-interpret-trailers.txt
+23 -1
@@ -8,7 +8,7 @@ git-interpret-trailers - help add structured information into commit messages
8 SYNOPSIS
9 --------
10 [verse]
11 -'git interpret-trailers' [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]
11 +'git interpret-trailers' [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]
12
13 DESCRIPTION
14 -----------
@@ -64,6 +64,9 @@ folding rules, the encoding rules and probably many other rules.
64
65 OPTIONS
66 -------
67 +--in-place::
68 + Edit the files in place.
69 +
70 --trim-empty::
71 If the <value> part of any trailer contains only whitespace,
72 the whole trailer will be removed from the resulting message.
@@ -216,6 +219,25 @@ Signed-off-by: Alice <alice@example.com>
219 Signed-off-by: Bob <bob@example.com>
220 ------------
221
222 +* Use the '--in-place' option to edit a message file in place:
223 ++
224 +------------
225 +$ cat msg.txt
226 +subject
227 +
228 +message
229 +
230 +Signed-off-by: Bob <bob@example.com>
231 +$ git interpret-trailers --trailer 'Acked-by: Alice <alice@example.com>' --in-place msg.txt
232 +$ cat msg.txt
233 +subject
234 +
235 +message
236 +
237 +Signed-off-by: Bob <bob@example.com>
238 +Acked-by: Alice <alice@example.com>
239 +------------
240 +
241 * Extract the last commit as a patch, and add a 'Cc' and a
242 'Reviewed-by' trailer to it:
243 +
builtin/interpret-trailers.c
+9 -4
@@ -12,16 +12,18 @@
12 #include "trailer.h"
13
14 static const char * const git_interpret_trailers_usage[] = {
15 - N_("git interpret-trailers [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
15 + N_("git interpret-trailers [--in-place] [--trim-empty] [(--trailer <token>[(=|:)<value>])...] [<file>...]"),
16 NULL
17 };
18
19 int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
20 {
21 + int in_place = 0;
22 int trim_empty = 0;
23 struct string_list trailers = STRING_LIST_INIT_DUP;
24
25 struct option options[] = {
26 + OPT_BOOL(0, "in-place", &in_place, N_("edit files in place")),
27 OPT_BOOL(0, "trim-empty", &trim_empty, N_("trim empty trailers")),
28 OPT_STRING_LIST(0, "trailer", &trailers, N_("trailer"),
29 N_("trailer(s) to add")),
@@ -34,9 +36,12 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix)
36 if (argc) {
37 int i;
38 for (i = 0; i < argc; i++)
37 - process_trailers(argv[i], trim_empty, &trailers);
38 - } else
39 - process_trailers(NULL, trim_empty, &trailers);
39 + process_trailers(argv[i], in_place, trim_empty, &trailers);
40 + } else {
41 + if (in_place)
42 + die(_("no input file given for in-place editing"));
43 + process_trailers(NULL, in_place, trim_empty, &trailers);
44 + }
45
46 string_list_clear(&trailers, 0);
47
t/t7513-interpret-trailers.sh
+40
@@ -326,6 +326,46 @@ test_expect_success 'with complex patch, args and --trim-empty' '
326 test_cmp expected actual
327 '
328
329 +test_expect_success 'in-place editing with basic patch' '
330 + cat basic_message >message &&
331 + cat basic_patch >>message &&
332 + cat basic_message >expected &&
333 + echo >>expected &&
334 + cat basic_patch >>expected &&
335 + git interpret-trailers --in-place message &&
336 + test_cmp expected message
337 +'
338 +
339 +test_expect_success 'in-place editing with additional trailer' '
340 + cat basic_message >message &&
341 + cat basic_patch >>message &&
342 + cat basic_message >expected &&
343 + echo >>expected &&
344 + cat >>expected <<-\EOF &&
345 + Reviewed-by: Alice
346 + EOF
347 + cat basic_patch >>expected &&
348 + git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message &&
349 + test_cmp expected message
350 +'
351 +
352 +test_expect_success 'in-place editing on stdin disallowed' '
353 + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place < basic_message
354 +'
355 +
356 +test_expect_success 'in-place editing on non-existing file' '
357 + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place nonexisting &&
358 + test_path_is_missing nonexisting
359 +'
360 +
361 +test_expect_success POSIXPERM,SANITY "in-place editing doesn't clobber original file on error" '
362 + cat basic_message >message &&
363 + chmod -r message &&
364 + test_must_fail git interpret-trailers --trailer "Reviewed-by: Alice" --in-place message &&
365 + chmod +r message &&
366 + test_cmp message basic_message
367 +'
368 +
369 test_expect_success 'using "where = before"' '
370 git config trailer.bug.where "before" &&
371 cat complex_message_body >expected &&
trailer.c
+40 -1
@@ -2,6 +2,7 @@
2 #include "string-list.h"
3 #include "run-command.h"
4 #include "commit.h"
5 +#include "tempfile.h"
6 #include "trailer.h"
7 /*
8 * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
@@ -843,7 +844,38 @@ static void free_all(struct trailer_item **first)
844 }
845 }
846
846 -void process_trailers(const char *file, int trim_empty, struct string_list *trailers)
847 +static struct tempfile trailers_tempfile;
848 +
849 +static FILE *create_in_place_tempfile(const char *file)
850 +{
851 + struct stat st;
852 + struct strbuf template = STRBUF_INIT;
853 + const char *tail;
854 + FILE *outfile;
855 +
856 + if (stat(file, &st))
857 + die_errno(_("could not stat %s"), file);
858 + if (!S_ISREG(st.st_mode))
859 + die(_("file %s is not a regular file"), file);
860 + if (!(st.st_mode & S_IWUSR))
861 + die(_("file %s is not writable by user"), file);
862 +
863 + /* Create temporary file in the same directory as the original */
864 + tail = strrchr(file, '/');
865 + if (tail != NULL)
866 + strbuf_add(&template, file, tail - file + 1);
867 + strbuf_addstr(&template, "git-interpret-trailers-XXXXXX");
868 +
869 + xmks_tempfile_m(&trailers_tempfile, template.buf, st.st_mode);
870 + strbuf_release(&template);
871 + outfile = fdopen_tempfile(&trailers_tempfile, "w");
872 + if (!outfile)
873 + die_errno(_("could not open temporary file"));
874 +
875 + return outfile;
876 +}
877 +
878 +void process_trailers(const char *file, int in_place, int trim_empty, struct string_list *trailers)
879 {
880 struct trailer_item *in_tok_first = NULL;
881 struct trailer_item *in_tok_last = NULL;
@@ -858,6 +890,9 @@ void process_trailers(const char *file, int trim_empty, struct string_list *trai
890
891 lines = read_input_file(file);
892
893 + if (in_place)
894 + outfile = create_in_place_tempfile(file);
895 +
896 /* Print the lines before the trailers */
897 trailer_end = process_input_file(outfile, lines, &in_tok_first, &in_tok_last);
898
@@ -872,5 +907,9 @@ void process_trailers(const char *file, int trim_empty, struct string_list *trai
907 /* Print the lines after the trailers as is */
908 print_lines(outfile, lines, trailer_end, INT_MAX);
909
910 + if (in_place)
911 + if (rename_tempfile(&trailers_tempfile, file))
912 + die_errno(_("could not rename temporary file to %s"), file);
913 +
914 strbuf_list_free(lines);
915 }
trailer.h
+2 -1
@@ -1,6 +1,7 @@
1 #ifndef TRAILER_H
2 #define TRAILER_H
3
4 -void process_trailers(const char *file, int trim_empty, struct string_list *trailers);
4 +void process_trailers(const char *file, int in_place, int trim_empty,
5 + struct string_list *trailers);
6
7 #endif /* TRAILER_H */