If you keep an output for an older iteration of the same topic in
the same directory around and use "git format-patch" to prepare a
newer iteration of the topic, those commits that happen to be at the
same position in the series that have not been retitled will get the
same filename---and the command opens them for writing without any
check.

Existing "-o outdir" and "-v number" options are both good ways to
avoid such name collisions, and in general helps to give good ways
to compare the latest iteration with older iteration(s), but let's
see if "--no-clobber" option that forbids overwrting existing files
would also help people.

Signed-off-by: Junio C Hamano <gits...@pobox.com>
---
 Documentation/git-format-patch.txt |  8 +++++++-
 builtin/log.c                      | 32 ++++++++++++++++++++++++------
 t/t4014-format-patch.sh            | 16 +++++++++++++++
 3 files changed, 49 insertions(+), 7 deletions(-)

diff --git a/Documentation/git-format-patch.txt 
b/Documentation/git-format-patch.txt
index 1af85d404f..540822b3b4 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -25,7 +25,7 @@ SYNOPSIS
                   [--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
                   [--interdiff=<previous>]
                   [--range-diff=<previous> [--creation-factor=<percent>]]
-                  [--progress]
+                  [--progress] [--[no-]clobber]
                   [<common diff options>]
                   [ <since> | <revision range> ]
 
@@ -93,6 +93,12 @@ include::diff-options.txt[]
        Use <dir> to store the resulting files, instead of the
        current working directory.
 
+--clobber::
+--no-clobber::
+       (experimental)
+       Allow overwriting existing files, which is the default.  To
+       make the command refrain from overwriting, use `--no-clobber`.
+
 -n::
 --numbered::
        Name output in '[PATCH n/m]' format, even with a single patch.
diff --git a/builtin/log.c b/builtin/log.c
index ca86611efe..7421f1cc93 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -867,8 +867,16 @@ static int git_format_config(const char *var, const char 
*value, void *cb)
 static const char *output_directory = NULL;
 static int outdir_offset;
 
+static FILE *fopen_excl(const char *filename)
+{
+       int fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0)
+               return NULL;
+       return fdopen(fd, "w");
+}
+
 static int open_next_file(struct commit *commit, const char *subject,
-                        struct rev_info *rev, int quiet)
+                         struct rev_info *rev, int quiet, int clobber)
 {
        struct strbuf filename = STRBUF_INIT;
        int suffix_len = strlen(rev->patch_suffix) + 1;
@@ -893,7 +901,12 @@ static int open_next_file(struct commit *commit, const 
char *subject,
        if (!quiet)
                printf("%s\n", filename.buf + outdir_offset);
 
-       if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) {
+       if (clobber)
+               rev->diffopt.file = fopen(filename.buf, "w");
+       else
+               rev->diffopt.file = fopen_excl(filename.buf);
+
+       if (!rev->diffopt.file) {
                error_errno(_("cannot open patch file %s"), filename.buf);
                strbuf_release(&filename);
                return -1;
@@ -1030,7 +1043,8 @@ static void make_cover_letter(struct rev_info *rev, int 
use_stdout,
                              struct commit *origin,
                              int nr, struct commit **list,
                              const char *branch_name,
-                             int quiet)
+                             int quiet,
+                             int clobber)
 {
        const char *committer;
        const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
@@ -1049,7 +1063,8 @@ static void make_cover_letter(struct rev_info *rev, int 
use_stdout,
        committer = git_committer_info(0);
 
        if (!use_stdout &&
-           open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", 
rev, quiet))
+           open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter",
+                          rev, quiet, clobber))
                die(_("failed to create cover-letter file"));
 
        log_write_email_headers(rev, head, &pp.after_subject, &need_8bit_cte, 
0);
@@ -1509,6 +1524,7 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
        struct strbuf buf = STRBUF_INIT;
        int use_patch_format = 0;
        int quiet = 0;
+       int clobber = 1;
        int reroll_count = -1;
        char *branch_name = NULL;
        char *base_commit = NULL;
@@ -1595,6 +1611,8 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
                OPT__QUIET(&quiet, N_("don't print the patch filenames")),
                OPT_BOOL(0, "progress", &show_progress,
                         N_("show progress while generating patches")),
+               OPT_BOOL(0, "clobber", &clobber,
+                        N_("allow overwriting output files")),
                OPT_CALLBACK(0, "interdiff", &idiff_prev, N_("rev"),
                             N_("show changes against <rev> in cover letter or 
single patch"),
                             parse_opt_object_name),
@@ -1885,7 +1903,8 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
                if (thread)
                        gen_message_id(&rev, "cover");
                make_cover_letter(&rev, use_stdout,
-                                 origin, nr, list, branch_name, quiet);
+                                 origin, nr, list, branch_name,
+                                 quiet, clobber);
                print_bases(&bases, rev.diffopt.file);
                print_signature(rev.diffopt.file);
                total++;
@@ -1940,7 +1959,8 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
                }
 
                if (!use_stdout &&
-                   open_next_file(rev.numbered_files ? NULL : commit, NULL, 
&rev, quiet))
+                   open_next_file(rev.numbered_files ? NULL : commit, NULL,
+                                  &rev, quiet, clobber))
                        die(_("failed to create output files"));
                shown = log_tree_commit(&rev, commit);
                free_commit_buffer(the_repository->parsed_objects,
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index b6e2fdbc44..384a1fd9e7 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -595,6 +595,22 @@ test_expect_success 'failure to write cover-letter aborts 
gracefully' '
        test_must_fail git format-patch --no-renames --cover-letter -1
 '
 
+test_expect_success 'refrain from overwriting a patch with --no-clobber' '
+       rm -f 000[01]-*.patch &&
+       git format-patch --no-clobber --no-renames --cover-letter -1 >filelist 
&&
+       # empty the files output by the command ...
+       for f in $(cat filelist)
+       do
+               : >"$f" || return 1
+       done &&
+       test_must_fail git format-patch --no-clobber --cover-letter 
--no-renames -1 &&
+       # ... and make sure they stay empty
+       for f in $(cat filelist)
+       do
+               ! test -s "$f" || return 1
+       done
+'
+
 test_expect_success 'cover-letter inherits diff options' '
        git mv file foo &&
        git commit -m foo &&
-- 
2.21.0-rc2

Reply via email to