Skip to content

Commit 0799f93

Browse files
committed
format-patch: --no-clobber refrains from overwriting output files
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 overwriting existing files also helps people. Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2fe95f4 commit 0799f93

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

Documentation/git-format-patch.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ SYNOPSIS
2525
[--[no-]cover-letter] [--quiet] [--notes[=<ref>]]
2626
[--interdiff=<previous>]
2727
[--range-diff=<previous> [--creation-factor=<percent>]]
28-
[--progress]
28+
[--progress] [--[no-]clobber]
2929
[<common diff options>]
3030
[ <since> | <revision range> ]
3131

@@ -93,6 +93,12 @@ include::diff-options.txt[]
9393
Use <dir> to store the resulting files, instead of the
9494
current working directory.
9595

96+
--clobber::
97+
--no-clobber::
98+
(experimental)
99+
Allow overwriting existing files, which is the default. To
100+
make the command refrain from overwriting, use `--no-clobber`.
101+
96102
-n::
97103
--numbered::
98104
Name output in '[PATCH n/m]' format, even with a single patch.

builtin/log.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,16 @@ static int git_format_config(const char *var, const char *value, void *cb)
867867
static const char *output_directory = NULL;
868868
static int outdir_offset;
869869

870+
static FILE *fopen_excl(const char *filename)
871+
{
872+
int fd = open(filename, O_CREAT | O_EXCL | O_WRONLY, 0666);
873+
if (fd < 0)
874+
return NULL;
875+
return fdopen(fd, "w");
876+
}
877+
870878
static int open_next_file(struct commit *commit, const char *subject,
871-
struct rev_info *rev, int quiet)
879+
struct rev_info *rev, int quiet, int clobber)
872880
{
873881
struct strbuf filename = STRBUF_INIT;
874882
int suffix_len = strlen(rev->patch_suffix) + 1;
@@ -893,7 +901,12 @@ static int open_next_file(struct commit *commit, const char *subject,
893901
if (!quiet)
894902
printf("%s\n", filename.buf + outdir_offset);
895903

896-
if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) {
904+
if (clobber)
905+
rev->diffopt.file = fopen(filename.buf, "w");
906+
else
907+
rev->diffopt.file = fopen_excl(filename.buf);
908+
909+
if (!rev->diffopt.file) {
897910
error_errno(_("cannot open patch file %s"), filename.buf);
898911
strbuf_release(&filename);
899912
return -1;
@@ -1030,7 +1043,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
10301043
struct commit *origin,
10311044
int nr, struct commit **list,
10321045
const char *branch_name,
1033-
int quiet)
1046+
int quiet,
1047+
int clobber)
10341048
{
10351049
const char *committer;
10361050
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,
10491063
committer = git_committer_info(0);
10501064

10511065
if (!use_stdout &&
1052-
open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet))
1066+
open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter",
1067+
rev, quiet, clobber))
10531068
die(_("failed to create cover-letter file"));
10541069

10551070
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)
15091524
struct strbuf buf = STRBUF_INIT;
15101525
int use_patch_format = 0;
15111526
int quiet = 0;
1527+
int clobber = 1;
15121528
int reroll_count = -1;
15131529
char *branch_name = NULL;
15141530
char *base_commit = NULL;
@@ -1595,6 +1611,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
15951611
OPT__QUIET(&quiet, N_("don't print the patch filenames")),
15961612
OPT_BOOL(0, "progress", &show_progress,
15971613
N_("show progress while generating patches")),
1614+
OPT_BOOL(0, "clobber", &clobber,
1615+
N_("allow overwriting output files")),
15981616
OPT_CALLBACK(0, "interdiff", &idiff_prev, N_("rev"),
15991617
N_("show changes against <rev> in cover letter or single patch"),
16001618
parse_opt_object_name),
@@ -1885,7 +1903,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
18851903
if (thread)
18861904
gen_message_id(&rev, "cover");
18871905
make_cover_letter(&rev, use_stdout,
1888-
origin, nr, list, branch_name, quiet);
1906+
origin, nr, list, branch_name,
1907+
quiet, clobber);
18891908
print_bases(&bases, rev.diffopt.file);
18901909
print_signature(rev.diffopt.file);
18911910
total++;
@@ -1940,7 +1959,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
19401959
}
19411960

19421961
if (!use_stdout &&
1943-
open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet))
1962+
open_next_file(rev.numbered_files ? NULL : commit, NULL,
1963+
&rev, quiet, clobber))
19441964
die(_("failed to create output files"));
19451965
shown = log_tree_commit(&rev, commit);
19461966
free_commit_buffer(the_repository->parsed_objects,

t/t4014-format-patch.sh

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,22 @@ test_expect_success 'failure to write cover-letter aborts gracefully' '
595595
test_must_fail git format-patch --no-renames --cover-letter -1
596596
'
597597

598+
test_expect_success 'refrain from overwriting a patch with --no-clobber' '
599+
rm -f 000[01]-*.patch &&
600+
git format-patch --no-clobber --no-renames --cover-letter -1 >filelist &&
601+
# empty the files output by the command ...
602+
for f in $(cat filelist)
603+
do
604+
: >"$f" || return 1
605+
done &&
606+
test_must_fail git format-patch --no-clobber --cover-letter --no-renames -1 &&
607+
# ... and make sure they stay empty
608+
for f in $(cat filelist)
609+
do
610+
! test -s "$f" || return 1
611+
done
612+
'
613+
598614
test_expect_success 'cover-letter inherits diff options' '
599615
git mv file foo &&
600616
git commit -m foo &&

0 commit comments

Comments
 (0)