interpret-trailers: add option for in-place editing
authorTobias Klauser <tklauser@distanz.ch>
Thu, 14 Jan 2016 16:57:55 +0000 (17:57 +0100)
committerJunio C Hamano <gitster@pobox.com>
Thu, 14 Jan 2016 20:22:17 +0000 (12:22 -0800)
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>
Documentation/git-interpret-trailers.txt
builtin/interpret-trailers.c
t/t7513-interpret-trailers.sh
trailer.c
trailer.h
index 0ecd497c4de77ea2ed7afc5603b4bcbd8558d174..a77b901f1d7b5e75c66b4c1e032f35c989614cc7 100644 (file)
@@ -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:
 +
index 46838d24a90a7dff11624902281fd72ce6409756..b99ae4be8875ad8fb41fd8be2a84b6191681a4e4 100644 (file)
 #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);
 
index 322c436a494c14a7350d6fdd85efbfbc5a7a8d28..aee785cffa8d656fa52e6b893921153faf748fe9 100755 (executable)
@@ -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 &&
index 176fac2134503c3bc80994d4466d3c320c2f9404..94b387b499714911c9dfb6dd0940cb3f25a2397e 100644 (file)
--- a/trailer.c
+++ b/trailer.c
@@ -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>
@@ -843,7 +844,38 @@ 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;
@@ -858,6 +890,9 @@ 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(outfile, lines, &in_tok_first, &in_tok_last);
 
@@ -872,5 +907,9 @@ void process_trailers(const char *file, int trim_empty, struct string_list *trai
        /* Print the lines after the trailers as is */
        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);
 }
index 8eb25d565e284f54d32b1840ceeb45872dc6efb9..36b40b81761f95742c9c6fe7e94ba89f19aabfde 100644 (file)
--- a/trailer.h
+++ b/trailer.h
@@ -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 */