From: Junio C Hamano <gitster@pobox.com>
To: git@vger.kernel.org
Cc: Sergey Organov <sorganov@gmail.com>
Subject: [PATCH] diff: fix behaviour of the "-s" option
Date: Thu, 04 May 2023 16:10:02 -0700 [thread overview]
Message-ID: <xmqqfs8bith1.fsf_-_@gitster.g> (raw)
In-Reply-To: <xmqqjzxnixqr.fsf_-_@gitster.g> (Junio C. Hamano's message of "Thu, 04 May 2023 14:37:48 -0700")
Junio C Hamano <gitster@pobox.com> writes:
> I haven't run any tests (not just your new one, but existing ones)
> but ...
And of course, not writing tests fails to even realize that the bug
has two components, "-s" failing to clear the bits previously set,
and other options not clearing the bit set by "-s".
This version may still be rough, but at least the full test suite
has been run with it, so I have a bit more confidence than the
earlier one (which may not mean much).
------- >8 ------------- >8 ------------- >8 -------------
Sergey Organov noticed and reported "--patch --no-patch --raw"
behaves differently from "--raw". It turns out there are a few
interesting bugs in the implementation and documentation.
* First, the documentation for "--no-patch" was unclear that it
could be read to mean "--no-patch" countermands an earlier
"--patch" but not other things. The intention of "--no-patch"
ever since it was introduced at d09cd15d (diff: allow --no-patch
as synonym for -s, 2013-07-16) was to serve as a synonym for
"-s", so "--raw --patch --no-patch" should have produced no
output, but it can be (mis)read to allow showing only "--raw"
output.
* Then the interaction between "-s" and other format options were
poorly implemented. Modern versions of Git uses one bit each to
represent formatting options like "--patch", "--stat" in a single
output_format word, but for historical reasons, "-s" also is
represented as another bit in the same word. This allows two
interesting bugs to happen, and we have both.
(1) After setting a format bit, then setting NO_OUTPUT with "-s",
the code to process another "--<format>" option drops the
NO_OUTPUT bit to allow output to be shown again. However,
the code to handle "-s" only set NO_OUTPUT without unsetting
format bits set earlier, so the earlier format bit got
revealed upon seeing the second "--<format>" option. THis is
the problem Sergey observed.
(2) After setting NO_OUTPUT with "-s", code to process
"--<format>" option can forget to unset NO_OUTPUT, leaving
the command still silent.
It is tempting to change the meaning of "--no-patch" to mean
"disable only the patch format output" and reimplement "-s" as "not
showing anything", but it would be an end-user visible change in
behaviour. Let's fix the interactions of these bits to first make
"-s" work as intended.
The fix is conceptually very simple.
* Whenever we set DIFF_FORMAT_FOO becasuse we saw the "--foo"
option (e.g. DIFF_FORMAT_RAW is set when the "--raw" option is
given), we make sure we drop DIFF_FORMAT_NO_OUTPUT. We forgot to
do so in some of the options and caused (2) above.
* When processing "-s" option, we should not just set
DIFF_FORMAT_NO_OUTPUT bit, but clear other DIFF_FORMAT_* bits.
We didn't do so and retained format bits set by options
previously seen, causing (1) above.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
Documentation/diff-options.txt | 7 +++++--
diff.c | 24 +++++++++++++-----------
t/t4000-diff-format.sh | 32 +++++++++++++++++++++++++++++++-
3 files changed, 49 insertions(+), 14 deletions(-)
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 3674ac48e9..7d5bb65a49 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -29,8 +29,11 @@ endif::git-diff[]
-s::
--no-patch::
- Suppress diff output. Useful for commands like `git show` that
- show the patch by default, or to cancel the effect of `--patch`.
+ Suppress all output from the diff machinery. Useful for
+ commands like `git show` that show the patch by default to
+ squelch their output, or to cancel the effect of options like
+ `--patch`, `--stat` earlier on the command line in an alias.
+
endif::git-format-patch[]
ifdef::git-log[]
diff --git a/diff.c b/diff.c
index 648f6717a5..5a2f096683 100644
--- a/diff.c
+++ b/diff.c
@@ -4868,6 +4868,7 @@ static int diff_opt_stat(const struct option *opt, const char *value, int unset)
} else
BUG("%s should not get here", opt->long_name);
+ options->output_format &= ~DIFF_FORMAT_NO_OUTPUT;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
options->stat_name_width = name_width;
options->stat_graph_width = graph_width;
@@ -4887,6 +4888,7 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
* The caller knows a dirstat-related option is given from the command
* line; allow it to say "return this_function();"
*/
+ options->output_format &= ~DIFF_FORMAT_NO_OUTPUT;
options->output_format |= DIFF_FORMAT_DIRSTAT;
return 1;
}
@@ -5086,6 +5088,7 @@ static int diff_opt_compact_summary(const struct option *opt,
options->flags.stat_with_summary = 0;
} else {
options->flags.stat_with_summary = 1;
+ options->output_format &= ~DIFF_FORMAT_NO_OUTPUT;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
}
return 0;
@@ -5404,9 +5407,8 @@ static void prep_parse_options(struct diff_options *options)
OPT_BITOP('p', "patch", &options->output_format,
N_("generate patch"),
DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
- OPT_BIT_F('s', "no-patch", &options->output_format,
- N_("suppress diff output"),
- DIFF_FORMAT_NO_OUTPUT, PARSE_OPT_NONEG),
+ OPT_SET_INT('s', "no-patch", &options->output_format,
+ N_("suppress diff output"), DIFF_FORMAT_NO_OUTPUT),
OPT_BITOP('u', NULL, &options->output_format,
N_("generate patch"),
DIFF_FORMAT_PATCH, DIFF_FORMAT_NO_OUTPUT),
@@ -5415,9 +5417,9 @@ static void prep_parse_options(struct diff_options *options)
PARSE_OPT_NONEG | PARSE_OPT_OPTARG, diff_opt_unified),
OPT_BOOL('W', "function-context", &options->flags.funccontext,
N_("generate diffs with <n> lines context")),
- OPT_BIT_F(0, "raw", &options->output_format,
+ OPT_BITOP(0, "raw", &options->output_format,
N_("generate the diff in raw format"),
- DIFF_FORMAT_RAW, PARSE_OPT_NONEG),
+ DIFF_FORMAT_RAW, DIFF_FORMAT_NO_OUTPUT),
OPT_BITOP(0, "patch-with-raw", &options->output_format,
N_("synonym for '-p --raw'"),
DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW,
@@ -5426,12 +5428,12 @@ static void prep_parse_options(struct diff_options *options)
N_("synonym for '-p --stat'"),
DIFF_FORMAT_PATCH | DIFF_FORMAT_DIFFSTAT,
DIFF_FORMAT_NO_OUTPUT),
- OPT_BIT_F(0, "numstat", &options->output_format,
+ OPT_BITOP(0, "numstat", &options->output_format,
N_("machine friendly --stat"),
- DIFF_FORMAT_NUMSTAT, PARSE_OPT_NONEG),
- OPT_BIT_F(0, "shortstat", &options->output_format,
+ DIFF_FORMAT_NUMSTAT, DIFF_FORMAT_NO_OUTPUT),
+ OPT_BITOP(0, "shortstat", &options->output_format,
N_("output only the last line of --stat"),
- DIFF_FORMAT_SHORTSTAT, PARSE_OPT_NONEG),
+ DIFF_FORMAT_SHORTSTAT, DIFF_FORMAT_NO_OUTPUT),
OPT_CALLBACK_F('X', "dirstat", options, N_("<param1,param2>..."),
N_("output the distribution of relative amount of changes for each sub-directory"),
PARSE_OPT_NONEG | PARSE_OPT_OPTARG,
@@ -5447,9 +5449,9 @@ static void prep_parse_options(struct diff_options *options)
OPT_BIT_F(0, "check", &options->output_format,
N_("warn if changes introduce conflict markers or whitespace errors"),
DIFF_FORMAT_CHECKDIFF, PARSE_OPT_NONEG),
- OPT_BIT_F(0, "summary", &options->output_format,
+ OPT_BITOP(0, "summary", &options->output_format,
N_("condensed summary such as creations, renames and mode changes"),
- DIFF_FORMAT_SUMMARY, PARSE_OPT_NONEG),
+ DIFF_FORMAT_SUMMARY, DIFF_FORMAT_NO_OUTPUT),
OPT_BIT_F(0, "name-only", &options->output_format,
N_("show only names of changed files"),
DIFF_FORMAT_NAME, PARSE_OPT_NONEG),
diff --git a/t/t4000-diff-format.sh b/t/t4000-diff-format.sh
index bfcaae390f..762b9d4c60 100755
--- a/t/t4000-diff-format.sh
+++ b/t/t4000-diff-format.sh
@@ -5,6 +5,9 @@
test_description='Test built-in diff output engine.
+We happen to know that all diff plumbing and diff Porcelain share the
+same command line parser, so testing one should be sufficient; pick
+diff-files as a representative.
'
TEST_PASSES_SANITIZE_LEAK=true
@@ -16,9 +19,11 @@ Line 2
line 3'
cat path0 >path1
chmod +x path1
+mkdir path2
+>path2/path3
test_expect_success 'update-index --add two files with and without +x.' '
- git update-index --add path0 path1
+ git update-index --add path0 path1 path2/path3
'
mv path0 path0-
@@ -91,4 +96,29 @@ test_expect_success 'git diff-files --patch --no-patch does not show the patch'
test_must_be_empty err
'
+
+# Smudge path2/path3 so that dirstat has something to show
+date >path2/path3
+
+for format in stat raw numstat shortstat dirstat
+do
+ test_expect_success "--no-patch in 'git diff-files --no-patch --$format' is a no-op" '
+ git diff-files --no-patch "--$format" >actual &&
+ git diff-files "--$format" >expect &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "--no-patch clears all previous ones" '
+ git diff-files --$format -s -p >actual &&
+ git diff-files -p >expect &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "--no-patch in 'git diff --no-patch --$format' is a no-op" '
+ git diff --no-patch "--$format" >actual &&
+ git diff "--$format" >expect &&
+ test_cmp expect actual
+ '
+done
+
test_done
--
2.40.1-476-g69c786637d
next prev parent reply other threads:[~2023-05-04 23:10 UTC|newest]
Thread overview: 42+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-05-03 13:41 [PATCH] t4013: add expected failure for "log --patch --no-patch" Sergey Organov
2023-05-03 16:57 ` Junio C Hamano
2023-05-03 17:31 ` Sergey Organov
2023-05-03 18:07 ` Junio C Hamano
2023-05-03 18:32 ` Felipe Contreras
2023-05-03 19:49 ` Sergey Organov
2023-05-04 15:50 ` Junio C Hamano
2023-05-04 18:24 ` Sergey Organov
2023-05-04 20:53 ` Junio C Hamano
2023-05-04 21:37 ` Re* " Junio C Hamano
2023-05-04 23:10 ` Junio C Hamano [this message]
2023-05-05 5:28 ` [PATCH] diff: fix behaviour of the "-s" option Junio C Hamano
2023-05-05 16:51 ` Junio C Hamano
2023-05-09 1:16 ` Felipe Contreras
2023-05-05 8:32 ` Sergey Organov
2023-05-05 16:31 ` Junio C Hamano
2023-05-05 17:07 ` Sergey Organov
2023-05-05 16:59 ` [PATCH v2] diff: fix interaction between the "-s" option and other options Junio C Hamano
2023-05-05 17:41 ` Eric Sunshine
2023-05-05 19:01 ` Junio C Hamano
2023-05-05 21:19 ` [PATCH 0/2] dirstat: leakfix Junio C Hamano
2023-05-05 21:19 ` [PATCH 1/2] diff: refactor common tail part of dirstat computation Junio C Hamano
2023-05-05 21:19 ` [PATCH 2/2] diff: plug leaks in dirstat Junio C Hamano
2023-05-09 0:38 ` [PATCH v2] diff: fix interaction between the "-s" option and other options Felipe Contreras
2023-05-09 1:22 ` Junio C Hamano
2023-05-09 3:50 ` Felipe Contreras
2023-05-10 4:26 ` Junio C Hamano
2023-05-10 23:16 ` Felipe Contreras
2023-05-10 23:41 ` Felipe Contreras
2023-05-11 1:25 ` Jeff King
2023-05-13 3:07 ` Felipe Contreras
2023-05-11 1:50 ` Junio C Hamano
2023-05-13 5:32 ` Felipe Contreras
2023-05-09 1:34 ` [PATCH] t4013: add expected failure for "log --patch --no-patch" Felipe Contreras
2023-05-10 13:54 ` Sergey Organov
2023-05-10 21:54 ` Felipe Contreras
2023-05-09 1:03 ` Felipe Contreras
2023-05-04 18:07 ` Junio C Hamano
2023-05-04 18:26 ` Sergey Organov
2023-05-09 1:07 ` Felipe Contreras
2023-05-10 13:40 ` Sergey Organov
2023-05-10 21:39 ` Felipe Contreras
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=xmqqfs8bith1.fsf_-_@gitster.g \
--to=gitster@pobox.com \
--cc=git@vger.kernel.org \
--cc=sorganov@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.