Skip to content

Commit dc1672d

Browse files
peffgitster
authored andcommitted
format-patch: support --output option
We've never intended to support diff's --output option in format-patch. And until baa4adc (parse-options: disable option abbreviation with PARSE_OPT_KEEP_UNKNOWN, 2019-01-27), it was impossible to trigger. We first parse the format-patch options before handing the remainder off to setup_revisions(). Before that commit, we'd accept "--output=foo" as an abbreviation for "--output-directory=foo". But afterwards, we don't check abbreviations, and --output gets passed to the diff code. This results in nonsense behavior and bugs. The diff code will have opened a filehandle at rev.diffopt.file, but we'll overwrite that with our own handles that we open for each individual patch file. So the --output file will always just be empty. But worse, the diff code also sets rev.diffopt.close_file, so log_tree_commit() will close the filehandle itself. And then the main loop in cmd_format_patch() will try to close it again, resulting in a double-free. The simplest solution would be to just disallow --output with format-patch, as nobody ever intended it to work. However, we have accidentally documented it (because format-patch includes diff-options). And it does work with "git log", which writes the whole output to the specified file. It's easy enough to make that work for format-patch, too: it's really the same as --stdout, but pointed at a specific file. We can detect the use of the --output option by the "close_file" flag (note that we can't use rev.diffopt.file, since the diff setup will otherwise set it to stdout). So we just need to unset that flag, but don't have to do anything else. Our situation is otherwise exactly like --stdout (note that we don't fclose() the file, but nor does the stdout case; exiting the program takes care of that for us). Reported-by: Johannes Postler <[email protected]> Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 1e1693b commit dc1672d

File tree

2 files changed

+32
-5
lines changed

2 files changed

+32
-5
lines changed

builtin/log.c

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1942,11 +1942,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
19421942
if (rev.show_notes)
19431943
load_display_notes(&rev.notes_opt);
19441944

1945-
if (use_stdout + !!output_directory > 1)
1946-
die(_("--stdout and --output-directory are mutually exclusive"));
1945+
if (use_stdout + rev.diffopt.close_file + !!output_directory > 1)
1946+
die(_("--stdout, --output, and --output-directory are mutually exclusive"));
19471947

19481948
if (use_stdout) {
19491949
setup_pager();
1950+
} else if (rev.diffopt.close_file) {
1951+
/*
1952+
* The diff code parsed --output; it has already opened the
1953+
* file, but but we must instruct it not to close after each
1954+
* diff.
1955+
*/
1956+
rev.diffopt.close_file = 0;
19501957
} else {
19511958
int saved;
19521959

t/t4014-format-patch.sh

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,18 +1920,38 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' '
19201920
'
19211921

19221922
test_expect_success 'format-patch forbids multiple outputs' '
1923-
rm -fr outdir &&
1923+
rm -fr outfile outdir &&
19241924
test_must_fail \
1925-
git format-patch --stdout --output-directory=outdir
1925+
git format-patch --stdout --output-directory=outdir &&
1926+
test_must_fail \
1927+
git format-patch --stdout --output=outfile &&
1928+
test_must_fail \
1929+
git format-patch --output=outfile --output-directory=outdir
19261930
'
19271931

19281932
test_expect_success 'configured outdir does not conflict with output options' '
1929-
rm -fr outdir &&
1933+
rm -fr outfile outdir &&
19301934
test_config format.outputDirectory outdir &&
19311935
git format-patch --stdout &&
1936+
test_path_is_missing outdir &&
1937+
git format-patch --output=outfile &&
19321938
test_path_is_missing outdir
19331939
'
19341940

1941+
test_expect_success 'format-patch --output' '
1942+
rm -fr outfile &&
1943+
git format-patch -3 --stdout HEAD >expect &&
1944+
git format-patch -3 --output=outfile HEAD &&
1945+
test_cmp expect outfile
1946+
'
1947+
1948+
test_expect_success 'format-patch --cover-letter --output' '
1949+
rm -fr outfile &&
1950+
git format-patch --cover-letter -3 --stdout HEAD >expect &&
1951+
git format-patch --cover-letter -3 --output=outfile HEAD &&
1952+
test_cmp expect outfile
1953+
'
1954+
19351955
test_expect_success 'format-patch --base' '
19361956
git checkout patchid &&
19371957

0 commit comments

Comments
 (0)