git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 0/2] format-patch: learn --[no-]range-diff-notes

From
Kkristofferhaugsbakk@fastmail.com <kristofferhaugsbakk@fastmail.com>
Date
Sep 26, 2026, 18:27 UTC
Message-ID
<V2_CV_format-patch_learn_--range-diff-notes.cdb@m5gid.xyz>
In-Reply-To
<CV_format-patch_learn_--range-diff-notes.c57@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Topic name (applied): kh/format-patch-range-diff-notes

Topic summary: Teach 'format-patch' options to tweak notes output in the range diff independent of what notes are output in the patches.

Hey, sorry if someone got duplicate emails right now! I tried to send out about ten minutes ago but it didn’t hit the list. It turned out that there was no `To` header.

Well I don’t know if emails without `To` are sent to the `Cc` addresses.
***
See patch 2/2 for details.

This is motivated by wanting to turn off range diff notes, but the goal here is to implement it in full generality.

(How many of us `git format-patch --notes` users are there out there? More than a dozen? Maybe just D. Ben Knoble and me?)

I have implemented this behavior for myself and used it for many months. But that was hacky and only suitable for one person’s use. So this is a completely new implementation. In other words: this is new code, *not* tested for months.

§ Changes in v2

This version drops the whole functionality around being able to *go back* (and forth) to using `--notes` for the range diff.[1] The behavior was too complex to explain and motivate compared to the utility (little).

🔗 1: https://lore.kernel.org/git/8f0a076b-4822-44e2-a842-cc1e39ae1c1d@app.fastmail.com/#t

This also means that the implementation is quite different. Now it just uses a parse-options callback instead of adding if/else to `revision.c:handle_revision_opt`.

See patch 2/2 for details.

Version 1 patch 2/3 is dropped. It was a rename motivated by the changes to `struct rev_info` in version 1 patch 3/3, which is now gone. The v1 3/3 change needed the struct member to stay notes-only, but that is no longer required.

[1/2] format-patch: simplify get_notes_arg parameters [2/2] format-patch: learn --[no-]range-diff-notes

 Documentation/git-format-patch.adoc | 15 +++++
 builtin/log.c                       | 62 ++++++++++++++++++--
 t/t3206-range-diff.sh               | 87 +++++++++++++++++++++++++++++
 3 files changed, 158 insertions(+), 6 deletions(-)
Interdiff against v1:
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc
index e0ba435dfcf..5907f299a8d 100644
--- a/Documentation/git-format-patch.adoc
+++ b/Documentation/git-format-patch.adoc
@@ -378,22 +378,20 @@ case is to show comparison with an older iteration of the same
 topic and the tool should find more correspondence between the two
 sets of patches.
 
-`--range-diff-notes[=<ref>]`::
+`--range-diff-notes=<ref>`::
 `--no-range-diff-notes`::
 	Used with `--range-diff`, tweak what notes to display in the
-	range diff. For example, you can use `--no-range-diff-notes` to
-	turn off all notes in the range diff. The default behavior is
-	to display the same notes in the range diff as on the patches
-	(see `--notes`).
+	range diff.
 +
-You may want to turn off this notes override after it has been
-activated. Use this sequence to do that:
-+
-----
---no-range-diff-notes --range-diff-notes
-----
-+
-Now the range diff is back to displaying the same notes as the patches.
+The default behavior is to display the same notes in the range diff as
+on the patches; see `--notes`. But you can use these options to use a
+different list of notes. For example, say you have given three notes
+refs to `--notes`. At this point those same three notes will be
+displayed in the range diff. But then you pass
+`--range-diff-notes=<ref>`. Now the range diff will only display
+_<ref>_. You can of course pass more refs to this option, just like
+`--notes`. And you can also turn off all range diff notes with
+`--no-range-diff-notes`.
 
 `--notes[=<ref>]`::
 `--no-notes`::
diff --git a/builtin/log.c b/builtin/log.c
index de997bc9ab0..d70101f0755 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -1327,27 +1327,65 @@ static void prepare_cover_text(struct pretty_print_context *pp,
 	strbuf_release(&subject_sb);
 }
 
+struct rdiff_notes {
+	/*
+	 * True if we want to override the notes behavior
+	 * of 'format-patch'
+	 */
+	bool override;
+	struct string_list notes;
+};
+
+static int rdiff_notes_cb(const struct option *option,
+		       const char *arg,
+		       int unset)
+{
+	struct rdiff_notes *rdiff_notes = option->value;
+
+	rdiff_notes->override = 1;
+
+	/*
+	 * The rest is the same as
+	 * parse-options-cb.c:parse_opt_string_list
+	 */
+	if (unset) {
+		string_list_clear(&rdiff_notes->notes, 0);
+		return 0;
+	}
+
+	if (!arg)
+		return -1;
+
+	string_list_append(&rdiff_notes->notes, arg);
+	return 0;
+}
+
 static int get_notes_refs(struct string_list_item *item, void *arg)
 {
 	strvec_pushf(arg, "--notes=%s", item->string);
 	return 0;
 }
 
-static void get_notes_args(struct rev_info *rev)
+static void get_notes_args(struct rdiff_notes *rdiff_notes,
+			   struct rev_info *rev)
 {
-	if (rev->rdiff_override_notes) {
-		if (!rev->rdiff_notes_arg.nr)
-			strvec_push(&rev->rdiff_notes_arg, "--no-notes");
+	if (rdiff_notes->override) {
+		if (rdiff_notes->notes.nr)
+			for_each_string_list(&rdiff_notes->notes,
+					     get_notes_refs,
+					     &rev->rdiff_log_arg);
+		else
+			strvec_push(&rev->rdiff_log_arg, "--no-notes");
 	} else if (!rev->show_notes) {
-		strvec_push(&rev->rdiff_notes_arg, "--no-notes");
+		strvec_push(&rev->rdiff_log_arg, "--no-notes");
 	} else if (rev->notes_opt.use_default_notes > 0 ||
 		   (rev->notes_opt.use_default_notes == -1 &&
 		    !rev->notes_opt.extra_notes_refs.nr)) {
-		strvec_push(&rev->rdiff_notes_arg, "--notes");
+		strvec_push(&rev->rdiff_log_arg, "--notes");
 	} else {
 		for_each_string_list(&rev->notes_opt.extra_notes_refs,
 				     get_notes_refs,
-				     &rev->rdiff_notes_arg);
+				     &rev->rdiff_log_arg);
 	}
 }
 
@@ -1478,7 +1516,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file,
 			.dual_color = 1,
 			.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
 			.diffopt = &opts,
-			.log_arg = &rev->rdiff_notes_arg
+			.log_arg = &rev->rdiff_log_arg
 		};
 
 		repo_diff_setup(the_repository, &opts);
@@ -1998,6 +2036,9 @@ int cmd_format_patch(int argc,
 	struct strbuf rdiff1 = STRBUF_INIT;
 	struct strbuf rdiff2 = STRBUF_INIT;
 	struct strbuf rdiff_title = STRBUF_INIT;
+	struct rdiff_notes rdiff_notes = {
+		.notes = STRING_LIST_INIT_NODUP,
+	};
 	const char *rfc = NULL;
 	int creation_factor = -1;
 	const char *signature = git_version_string;
@@ -2094,6 +2135,9 @@ int cmd_format_patch(int argc,
 			     parse_opt_object_name),
 		OPT_STRING(0, "range-diff", &rdiff_prev, N_("refspec"),
 			   N_("show changes against <refspec> in cover letter or single patch")),
+		OPT_CALLBACK_F(0, "range-diff-notes", &rdiff_notes, N_("note"),
+			       N_("override notes behavior for the range diff"),
+			       0, rdiff_notes_cb),
 		OPT_INTEGER(0, "creation-factor", &creation_factor,
 			    N_("percentage by which creation is weighted")),
 		OPT_BOOL(0, "force-in-body-from", &force_in_body_from,
@@ -2409,7 +2453,7 @@ int cmd_format_patch(int argc,
 		rev.rdiff_title = diff_title(&rdiff_title, reroll_count,
 					     _("Range-diff:"),
 					     _("Range-diff against v%d:"));
-		get_notes_args(&rev);
+		get_notes_args(&rdiff_notes, &rev);
 	}
 
 	/*
@@ -2572,7 +2616,8 @@ int cmd_format_patch(int argc,
 	rev.diffopt.no_free = 0;
 	release_revisions(&rev);
 	format_config_release(&cfg);
-	strvec_clear(&rev.rdiff_notes_arg);
+	strvec_clear(&rev.rdiff_log_arg);
+	string_list_clear(&rdiff_notes.notes, 0);
 	return 0;
 }
 
diff --git a/log-tree.c b/log-tree.c
index fd6ddf32af4..83a3c4bf9b1 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -718,7 +718,7 @@ static void show_diff_of_diff(struct rev_info *opt)
 			.dual_color = 1,
 			.max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT,
 			.diffopt = &opts,
-			.log_arg = &opt->rdiff_notes_arg
+			.log_arg = &opt->rdiff_log_arg
 		};
 
 		memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff));
diff --git a/revision.c b/revision.c
index 1e21f2861cc..50dc8b19913 100644
--- a/revision.c
+++ b/revision.c
@@ -2625,19 +2625,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
 		revs->notes_opt.use_default_notes = 1;
 	} else if (!strcmp(arg, "--no-standard-notes")) {
 		revs->notes_opt.use_default_notes = 0;
-	} else if (!strcmp(arg, "--no-range-diff-notes")) {
-		strvec_clear(&revs->rdiff_notes_arg);
-		revs->rdiff_override_notes = 1;
-	} else if (!strcmp(arg, "--range-diff-notes")) {
-		/*
-		 * Allow the user to use '--no-range-diff-notes
-		 * --range-diff-notes' in order to go back to
-		 * using the 'format-patch' notes behavior
-		 */
-		revs->rdiff_override_notes = revs->rdiff_notes_arg.nr;
-	} else if (skip_prefix(arg, "--range-diff-notes=", &optarg)) {
-		strvec_pushf(&revs->rdiff_notes_arg, "--notes=%s", optarg);
-		revs->rdiff_override_notes = 1;
 	} else if (!strcmp(arg, "--oneline")) {
 		revs->verbose_header = 1;
 		get_commit_format("oneline", revs);
diff --git a/revision.h b/revision.h
index e8dbf774b00..acf6d06b241 100644
--- a/revision.h
+++ b/revision.h
@@ -351,12 +351,7 @@ struct rev_info {
 	/* range-diff */
 	const char *rdiff1;
 	const char *rdiff2;
-	/*
-	 * whether to use 'rdiff_notes_arg' or inherited
-	 * notes behavior
-	 */
-	bool rdiff_override_notes;
-	struct strvec rdiff_notes_arg;
+	struct strvec rdiff_log_arg;
 	int creation_factor;
 	const char *rdiff_title;
 
@@ -437,7 +432,7 @@ struct rev_info {
 	.expand_tabs_in_log = -1, \
 	.commit_format = CMIT_FMT_DEFAULT, \
 	.expand_tabs_in_log_default = 8, \
-	.rdiff_notes_arg = STRVEC_INIT, \
+	.rdiff_log_arg = STRVEC_INIT, \
 }
 
 /**
diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh
index db238d0a5a1..640c5dec52e 100755
--- a/t/t3206-range-diff.sh
+++ b/t/t3206-range-diff.sh
@@ -845,28 +845,49 @@ test_expect_success 'format-patch --range-diff with multiple notes' '
 	test_cmp expect actual
 '
 
+# Unlike '--notes', '--range-diff-notes' requires a value
+test_expect_success 'format-patch --range-diff-notes requires a value' '
+	cat >expect <<-EOF &&
+	error: option \`range-diff-notes${SQ} requires a value
+	EOF
+	test_must_fail git format-patch --range-diff=main..topic \
+		--cover-letter --range-diff-notes 2>actual &&
+	test_cmp expect actual
+'
+
+# The '--range-diff-notes' has no effect but is allowed
+test_expect_success 'format-patch --range-diff-notes=not-a-note (no --range-diff)' '
+	test_when_finished "rm -f 000?-*" &&
+	git format-patch --range-diff-notes=not-a-note --cover-letter \
+		main..unmodified &&
+	test_when_finished "rm -f 000?-*" &&
+	test_file_not_empty 0000-cover-letter* &&
+	test_grep ! "^Range-diff:" 0000-cover-letter* &&
+	test_grep ! "## Notes " 0000-cover-letter*
+'
+
 test_expect_success 'format-patch --range-diff --notes=custom --no-range-diff-notes' '
 	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
 	git notes --ref=custom add -m "topic note1" topic &&
 	git notes --ref=custom add -m "unmodified note1" unmodified &&
 	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev --notes=custom \
+	git format-patch --range-diff=main..topic --notes=custom \
 		--no-range-diff-notes --cover-letter \
-		main..unmodified >actual &&
+		main..unmodified &&
 	test_grep "^Notes (custom):" 0004-* &&
 	test_grep "^Range-diff:" 0000-cover-letter* &&
 	test_grep ! "## Notes (custom) ##" 0000-cover-letter*
 '
 
-test_expect_success 'format-patch --range-diff --range-diff-notes uses --notes behavior' '
+test_expect_success 'format-patch --range-diff --no-notes --range-diff-notes=custom' '
 	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
 	git notes --ref=custom add -m "topic note1" topic &&
 	git notes --ref=custom add -m "unmodified note1" unmodified &&
 	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev --notes=custom \
-		--range-diff-notes --cover-letter \
-		main..unmodified >actual &&
-	test_grep "^Notes (custom):" 0004-* &&
+	git format-patch --range-diff=main..topic --no-notes \
+		--range-diff-notes=custom --cover-letter \
+		main..unmodified &&
+	test_grep ! "^Notes (custom):" 0004-* &&
 	test_grep "^Range-diff:" 0000-cover-letter* &&
 	test_grep "## Notes (custom) ##" 0000-cover-letter*
 '
@@ -879,9 +900,9 @@ test_expect_success 'format-patch --range-diff --notes=patch --range-diff-notes=
 	git notes --ref=rdiff add -m "only for range diff 1" topic &&
 	git notes --ref=rdiff add -m "only for range diff 2" unmodified &&
 	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev --notes=patch \
+	git format-patch --range-diff=main..topic --notes=patch \
 		--range-diff-notes=rdiff --cover-letter \
-		main..unmodified >actual &&
+		main..unmodified &&
 	test_grep "^Notes (patch):" 0004-* &&
 	test_grep ! "^Notes (rdiff):" 0004-* &&
 	test_grep "^Range-diff:" 0000-cover-letter* &&
@@ -889,50 +910,11 @@ test_expect_success 'format-patch --range-diff --notes=patch --range-diff-notes=
 	test_grep ! "## Notes (patch) ##" 0000-cover-letter*
 '
 
-test_expect_success 'format-patch --range-diff --no-range-diff-notes --range-diff-notes uses --notes behavior' '
-	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
-	git notes --ref=custom add -m "topic note1" topic &&
-	git notes --ref=custom add -m "unmodified note1" unmodified &&
-	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev --notes=custom \
-		--no-range-diff-notes --range-diff-notes --cover-letter \
-		main..unmodified >actual &&
-	test_grep "^Notes (custom):" 0004-* &&
-	test_grep "^Range-diff:" 0000-cover-letter* &&
-	test_grep "## Notes (custom) ##" 0000-cover-letter*
-'
-
-test_expect_success 'format-patch --range-diff --range-diff-notes uses --notes behavior' '
-	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
-	git notes --ref=custom add -m "topic note1" topic &&
-	git notes --ref=custom add -m "unmodified note1" unmodified &&
-	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev --notes=custom \
-		--range-diff-notes --cover-letter \
-		main..unmodified >actual &&
-	test_grep "^Notes (custom):" 0004-* &&
-	test_grep "^Range-diff:" 0000-cover-letter* &&
-	test_grep "## Notes (custom) ##" 0000-cover-letter*
-'
-
-test_expect_success 'format-patch --range-diff --no-range-diff-notes does not use default notes' '
-	test_when_finished "git notes remove topic unmodified || :" &&
-	git notes add -m "topic note1" topic &&
-	git notes add -m "unmodified note1" unmodified &&
-	test_when_finished "rm -f 000?-*" &&
-	git format-patch --range-diff=$prev \
-		--no-range-diff-notes --cover-letter \
-		main..unmodified >actual &&
-	test_grep ! "^Notes:" 0004-* &&
-	test_grep "^Range-diff:" 0000-cover-letter* &&
-	test_grep ! "## Notes ##" 0000-cover-letter*
-'
-
 test_expect_success 'format-patch --range-diff --no-range-diff-notes on single patch' '
 	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
 	git notes --ref=custom add -m "topic note (custom)" HEAD &&
 	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
-	git format-patch --notes=custom --range-diff=$prev \
+	git format-patch --notes=custom --range-diff=main..topic \
 		--no-range-diff-notes -1 --stdout >actual &&
 	test_grep "Notes (custom):" actual &&
 	test_grep "^Range-diff:" actual &&
@@ -943,7 +925,7 @@ test_expect_success 'format-patch --range-diff --range-diff-notes=custom on sing
 	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
 	git notes --ref=custom add -m "topic note (custom)" HEAD &&
 	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
-	git format-patch --no-notes --range-diff=$prev \
+	git format-patch --range-diff=main..topic \
 		--range-diff-notes=custom -1 --stdout >actual &&
 	test_grep ! "Notes (custom):" actual &&
 	test_grep "^Range-diff:" actual &&
Range-diff against v1:
1:  977f9c2e97a = 1:  977f9c2e97a format-patch: simplify get_notes_arg parameters
2:  2a555d40ced < -:  ----------- revision.h: rename struct member to reflect notes role
3:  058f5fdc8da ! 2:  bf66e94e376 format-patch: learn --[no-]range-diff-notes
    @@ Commit message
         • No such options given
         • `--no-range-diff-notes`
     
    -    Well, we can’t. Therefore we need `rdiff_override_notes` to set whenever
    +    Well, we can’t. Therefore we need `rdiff_notes.override` to set whenever
         any of these options are given.
     
    -    However, we may also want to turn *off* this override. Just like how we
    -    can countermand any notes ref we pass in:
    -
    -        --notes=custom --no-notes
    -
    -    To that end, let’s make `--range-diff-notes` when the list of options is
    -    empty special. Then it means: go back to using whatever git-format-
    -    patch(1) wants to use.
    -
    -    Now, `--notes` is a bit special in that it has an optional
    -    argument. Implementing this with a parse-options callback is not
    -    user-friendly; the following does *not* mean what it looks like:
    -
    -        --parse-option --another-option
    -
    -    Namely, it is not a bare `--parse-option` followed by another
    -    option. Rather, it’s one option:
    -
    -        --parse-option=--another-option
    -
    -    And we need the bare `--range-diff-notes` form in order to turn off
    -    notes overriding. For that reason, let’s implement these new options in
    -    `revision.c:handle_revision_opt`, just like the `--notes` options are.
    -
         † 1: For example, let say we have two notes ref that are used for a
              patch series:
     
    @@ Commit message
     
         Note that using `--creation-factor` without `--range-diff` will cause
         the command to die. But this is not the case for `--[no-]range-diff-
    -    notes`. Yes, we could introduce struct member `rdiff_notes_arg_used` or
    -    something in order to detect the same condition. Or turn `rdiff_notes_
    -    override` into a tri-state `int`. But the extra code is not worth that
    -    in my opinion.
    +    notes`; we would have to check `rdiff_notes.override`, which is a sticky
    +    value (cannot be turned off). The reason is that it is potentially
    +    inconvenient to error out since it would not let you turn off
    +    `--range-diff` in, say, some alias that uses `--no-range-diff-
    +    notes`. Granted, it is difficult for me to come up with a concrete use
    +    case since `--range-diff` requires a value, specifically a value which
    +    is probably not that reusable (revision range), and yet you have
    +    something like an alias set up with it. But why spend code closing
    +    that door? There is no usability upside to erroring out.
    +
    +    ***
    +
    +    Add two tests here for the single-patch case, i.e. the case where the
    +    range diff is on the patch and not in the cover letter. These are meant
    +    as regression tests based on my encounter with single-patch range diff
    +    notes handling bug.[2]
    +
    +    † 2: 155986b4 (format-patch: handle range-diff on notes correctly for
    +         single patches, 2025-09-25)
     
         Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
     
     
      ## Notes (testing) ##
    -    CI: https://github.com/LemmingAvalanche/git/actions/runs/32762207178
    +    CI: https://github.com/LemmingAvalanche/git/actions/runs/36231842902
    +
    +    This run is on a previous iteration where v1 patch/commit 2/3 was still
    +    there. But that is just a rename. So I compiled and tested
    +    `t/t3206-range-diff.sh` and took that as proof that the full CI/build run
    +    is still valid.
     
      ## Documentation/git-format-patch.adoc ##
     @@ Documentation/git-format-patch.adoc: case is to show comparison with an older iteration of the same
      topic and the tool should find more correspondence between the two
      sets of patches.
      
    -+`--range-diff-notes[=<ref>]`::
    ++`--range-diff-notes=<ref>`::
     +`--no-range-diff-notes`::
     +	Used with `--range-diff`, tweak what notes to display in the
    -+	range diff. For example, you can use `--no-range-diff-notes` to
    -+	turn off all notes in the range diff. The default behavior is
    -+	to display the same notes in the range diff as on the patches
    -+	(see `--notes`).
    -++
    -+You may want to turn off this notes override after it has been
    -+activated. Use this sequence to do that:
    ++	range diff.
     ++
    -+----
    -+--no-range-diff-notes --range-diff-notes
    -+----
    -++
    -+Now the range diff is back to displaying the same notes as the patches.
    ++The default behavior is to display the same notes in the range diff as
    ++on the patches; see `--notes`. But you can use these options to use a
    ++different list of notes. For example, say you have given three notes
    ++refs to `--notes`. At this point those same three notes will be
    ++displayed in the range diff. But then you pass
    ++`--range-diff-notes=<ref>`. Now the range diff will only display
    ++_<ref>_. You can of course pass more refs to this option, just like
    ++`--notes`. And you can also turn off all range diff notes with
    ++`--no-range-diff-notes`.
     +
      `--notes[=<ref>]`::
      `--no-notes`::
      	Append the notes (see linkgit:git-notes[1]) for the commit
     
      ## builtin/log.c ##
    -@@ builtin/log.c: static int get_notes_refs(struct string_list_item *item, void *arg)
    +@@ builtin/log.c: static void prepare_cover_text(struct pretty_print_context *pp,
    + 	strbuf_release(&subject_sb);
    + }
    + 
    ++struct rdiff_notes {
    ++	/*
    ++	 * True if we want to override the notes behavior
    ++	 * of 'format-patch'
    ++	 */
    ++	bool override;
    ++	struct string_list notes;
    ++};
    ++
    ++static int rdiff_notes_cb(const struct option *option,
    ++		       const char *arg,
    ++		       int unset)
    ++{
    ++	struct rdiff_notes *rdiff_notes = option->value;
    ++
    ++	rdiff_notes->override = 1;
    ++
    ++	/*
    ++	 * The rest is the same as
    ++	 * parse-options-cb.c:parse_opt_string_list
    ++	 */
    ++	if (unset) {
    ++		string_list_clear(&rdiff_notes->notes, 0);
    ++		return 0;
    ++	}
    ++
    ++	if (!arg)
    ++		return -1;
    ++
    ++	string_list_append(&rdiff_notes->notes, arg);
    ++	return 0;
    ++}
    ++
    + static int get_notes_refs(struct string_list_item *item, void *arg)
    + {
    + 	strvec_pushf(arg, "--notes=%s", item->string);
    + 	return 0;
    + }
      
    - static void get_notes_args(struct rev_info *rev)
    +-static void get_notes_args(struct rev_info *rev)
    ++static void get_notes_args(struct rdiff_notes *rdiff_notes,
    ++			   struct rev_info *rev)
      {
     -	if (!rev->show_notes) {
    -+	if (rev->rdiff_override_notes) {
    -+		if (!rev->rdiff_notes_arg.nr)
    -+			strvec_push(&rev->rdiff_notes_arg, "--no-notes");
    ++	if (rdiff_notes->override) {
    ++		if (rdiff_notes->notes.nr)
    ++			for_each_string_list(&rdiff_notes->notes,
    ++					     get_notes_refs,
    ++					     &rev->rdiff_log_arg);
    ++		else
    ++			strvec_push(&rev->rdiff_log_arg, "--no-notes");
     +	} else if (!rev->show_notes) {
    - 		strvec_push(&rev->rdiff_notes_arg, "--no-notes");
    + 		strvec_push(&rev->rdiff_log_arg, "--no-notes");
      	} else if (rev->notes_opt.use_default_notes > 0 ||
      		   (rev->notes_opt.use_default_notes == -1 &&
    -
    - ## revision.c ##
    -@@ revision.c: static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
    - 		revs->notes_opt.use_default_notes = 1;
    - 	} else if (!strcmp(arg, "--no-standard-notes")) {
    - 		revs->notes_opt.use_default_notes = 0;
    -+	} else if (!strcmp(arg, "--no-range-diff-notes")) {
    -+		strvec_clear(&revs->rdiff_notes_arg);
    -+		revs->rdiff_override_notes = 1;
    -+	} else if (!strcmp(arg, "--range-diff-notes")) {
    -+		/*
    -+		 * Allow the user to use '--no-range-diff-notes
    -+		 * --range-diff-notes' in order to go back to
    -+		 * using the 'format-patch' notes behavior
    -+		 */
    -+		revs->rdiff_override_notes = revs->rdiff_notes_arg.nr;
    -+	} else if (skip_prefix(arg, "--range-diff-notes=", &optarg)) {
    -+		strvec_pushf(&revs->rdiff_notes_arg, "--notes=%s", optarg);
    -+		revs->rdiff_override_notes = 1;
    - 	} else if (!strcmp(arg, "--oneline")) {
    - 		revs->verbose_header = 1;
    - 		get_commit_format("oneline", revs);
    -
    - ## revision.h ##
    -@@ revision.h: struct rev_info {
    - 	/* range-diff */
    - 	const char *rdiff1;
    - 	const char *rdiff2;
    -+	/*
    -+	 * whether to use 'rdiff_notes_arg' or inherited
    -+	 * notes behavior
    -+	 */
    -+	bool rdiff_override_notes;
    - 	struct strvec rdiff_notes_arg;
    - 	int creation_factor;
    - 	const char *rdiff_title;
    +@@ builtin/log.c: int cmd_format_patch(int argc,
    + 	struct strbuf rdiff1 = STRBUF_INIT;
    + 	struct strbuf rdiff2 = STRBUF_INIT;
    + 	struct strbuf rdiff_title = STRBUF_INIT;
    ++	struct rdiff_notes rdiff_notes = {
    ++		.notes = STRING_LIST_INIT_NODUP,
    ++	};
    + 	const char *rfc = NULL;
    + 	int creation_factor = -1;
    + 	const char *signature = git_version_string;
    +@@ builtin/log.c: int cmd_format_patch(int argc,
    + 			     parse_opt_object_name),
    + 		OPT_STRING(0, "range-diff", &rdiff_prev, N_("refspec"),
    + 			   N_("show changes against <refspec> in cover letter or single patch")),
    ++		OPT_CALLBACK_F(0, "range-diff-notes", &rdiff_notes, N_("note"),
    ++			       N_("override notes behavior for the range diff"),
    ++			       0, rdiff_notes_cb),
    + 		OPT_INTEGER(0, "creation-factor", &creation_factor,
    + 			    N_("percentage by which creation is weighted")),
    + 		OPT_BOOL(0, "force-in-body-from", &force_in_body_from,
    +@@ builtin/log.c: int cmd_format_patch(int argc,
    + 		rev.rdiff_title = diff_title(&rdiff_title, reroll_count,
    + 					     _("Range-diff:"),
    + 					     _("Range-diff against v%d:"));
    +-		get_notes_args(&rev);
    ++		get_notes_args(&rdiff_notes, &rev);
    + 	}
    + 
    + 	/*
    +@@ builtin/log.c: int cmd_format_patch(int argc,
    + 	release_revisions(&rev);
    + 	format_config_release(&cfg);
    + 	strvec_clear(&rev.rdiff_log_arg);
    ++	string_list_clear(&rdiff_notes.notes, 0);
    + 	return 0;
    + }
    + 
     
      ## t/t3206-range-diff.sh ##
     @@ t/t3206-range-diff.sh: test_expect_success 'format-patch --range-diff with multiple notes' '
      	test_cmp expect actual
      '
      
    ++# Unlike '--notes', '--range-diff-notes' requires a value
    ++test_expect_success 'format-patch --range-diff-notes requires a value' '
    ++	cat >expect <<-EOF &&
    ++	error: option \`range-diff-notes${SQ} requires a value
    ++	EOF
    ++	test_must_fail git format-patch --range-diff=main..topic \
    ++		--cover-letter --range-diff-notes 2>actual &&
    ++	test_cmp expect actual
    ++'
    ++
    ++# The '--range-diff-notes' has no effect but is allowed
    ++test_expect_success 'format-patch --range-diff-notes=not-a-note (no --range-diff)' '
    ++	test_when_finished "rm -f 000?-*" &&
    ++	git format-patch --range-diff-notes=not-a-note --cover-letter \
    ++		main..unmodified &&
    ++	test_when_finished "rm -f 000?-*" &&
    ++	test_file_not_empty 0000-cover-letter* &&
    ++	test_grep ! "^Range-diff:" 0000-cover-letter* &&
    ++	test_grep ! "## Notes " 0000-cover-letter*
    ++'
    ++
     +test_expect_success 'format-patch --range-diff --notes=custom --no-range-diff-notes' '
     +	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
     +	git notes --ref=custom add -m "topic note1" topic &&
     +	git notes --ref=custom add -m "unmodified note1" unmodified &&
     +	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev --notes=custom \
    ++	git format-patch --range-diff=main..topic --notes=custom \
     +		--no-range-diff-notes --cover-letter \
    -+		main..unmodified >actual &&
    ++		main..unmodified &&
     +	test_grep "^Notes (custom):" 0004-* &&
     +	test_grep "^Range-diff:" 0000-cover-letter* &&
     +	test_grep ! "## Notes (custom) ##" 0000-cover-letter*
     +'
     +
    -+test_expect_success 'format-patch --range-diff --range-diff-notes uses --notes behavior' '
    ++test_expect_success 'format-patch --range-diff --no-notes --range-diff-notes=custom' '
     +	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
     +	git notes --ref=custom add -m "topic note1" topic &&
     +	git notes --ref=custom add -m "unmodified note1" unmodified &&
     +	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev --notes=custom \
    -+		--range-diff-notes --cover-letter \
    -+		main..unmodified >actual &&
    -+	test_grep "^Notes (custom):" 0004-* &&
    ++	git format-patch --range-diff=main..topic --no-notes \
    ++		--range-diff-notes=custom --cover-letter \
    ++		main..unmodified &&
    ++	test_grep ! "^Notes (custom):" 0004-* &&
     +	test_grep "^Range-diff:" 0000-cover-letter* &&
     +	test_grep "## Notes (custom) ##" 0000-cover-letter*
     +'
    @@ t/t3206-range-diff.sh: test_expect_success 'format-patch --range-diff with multi
     +	git notes --ref=rdiff add -m "only for range diff 1" topic &&
     +	git notes --ref=rdiff add -m "only for range diff 2" unmodified &&
     +	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev --notes=patch \
    ++	git format-patch --range-diff=main..topic --notes=patch \
     +		--range-diff-notes=rdiff --cover-letter \
    -+		main..unmodified >actual &&
    ++		main..unmodified &&
     +	test_grep "^Notes (patch):" 0004-* &&
     +	test_grep ! "^Notes (rdiff):" 0004-* &&
     +	test_grep "^Range-diff:" 0000-cover-letter* &&
    @@ t/t3206-range-diff.sh: test_expect_success 'format-patch --range-diff with multi
     +	test_grep ! "## Notes (patch) ##" 0000-cover-letter*
     +'
     +
    -+test_expect_success 'format-patch --range-diff --no-range-diff-notes --range-diff-notes uses --notes behavior' '
    -+	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
    -+	git notes --ref=custom add -m "topic note1" topic &&
    -+	git notes --ref=custom add -m "unmodified note1" unmodified &&
    -+	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev --notes=custom \
    -+		--no-range-diff-notes --range-diff-notes --cover-letter \
    -+		main..unmodified >actual &&
    -+	test_grep "^Notes (custom):" 0004-* &&
    -+	test_grep "^Range-diff:" 0000-cover-letter* &&
    -+	test_grep "## Notes (custom) ##" 0000-cover-letter*
    -+'
    -+
    -+test_expect_success 'format-patch --range-diff --range-diff-notes uses --notes behavior' '
    -+	test_when_finished "git notes --ref=custom remove topic unmodified || :" &&
    -+	git notes --ref=custom add -m "topic note1" topic &&
    -+	git notes --ref=custom add -m "unmodified note1" unmodified &&
    -+	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev --notes=custom \
    -+		--range-diff-notes --cover-letter \
    -+		main..unmodified >actual &&
    -+	test_grep "^Notes (custom):" 0004-* &&
    -+	test_grep "^Range-diff:" 0000-cover-letter* &&
    -+	test_grep "## Notes (custom) ##" 0000-cover-letter*
    -+'
    -+
    -+test_expect_success 'format-patch --range-diff --no-range-diff-notes does not use default notes' '
    -+	test_when_finished "git notes remove topic unmodified || :" &&
    -+	git notes add -m "topic note1" topic &&
    -+	git notes add -m "unmodified note1" unmodified &&
    -+	test_when_finished "rm -f 000?-*" &&
    -+	git format-patch --range-diff=$prev \
    -+		--no-range-diff-notes --cover-letter \
    -+		main..unmodified >actual &&
    -+	test_grep ! "^Notes:" 0004-* &&
    -+	test_grep "^Range-diff:" 0000-cover-letter* &&
    -+	test_grep ! "## Notes ##" 0000-cover-letter*
    -+'
    -+
     +test_expect_success 'format-patch --range-diff --no-range-diff-notes on single patch' '
     +	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
     +	git notes --ref=custom add -m "topic note (custom)" HEAD &&
     +	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
    -+	git format-patch --notes=custom --range-diff=$prev \
    ++	git format-patch --notes=custom --range-diff=main..topic \
     +		--no-range-diff-notes -1 --stdout >actual &&
     +	test_grep "Notes (custom):" actual &&
     +	test_grep "^Range-diff:" actual &&
    @@ t/t3206-range-diff.sh: test_expect_success 'format-patch --range-diff with multi
     +	test_when_finished "git notes --ref=custom remove HEAD unmodified || :" &&
     +	git notes --ref=custom add -m "topic note (custom)" HEAD &&
     +	git notes --ref=custom add -m "unmodified note (custom)" unmodified &&
    -+	git format-patch --no-notes --range-diff=$prev \
    ++	git format-patch --range-diff=main..topic \
     +		--range-diff-notes=custom -1 --stdout >actual &&
     +	test_grep ! "Notes (custom):" actual &&
     +	test_grep "^Range-diff:" actual &&

base-commit: 1a3e64c6c4a623626ff0687008732a8e007e2a1c
-- 
2.55.0.793.gc667de3f2c5
Previous: Junio C HamanoNext: kristofferhaugsbakk@fastmail.com
Message 18 of 41 in “format-patch: learn --[no-]range-diff-notes”
  1. 0/3 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Aug 24, 2026
  2. 1/3 format-patch: simplify get_notes_arg parameterskristofferhaugsbakk@fastmail.com, Aug 24, 2026
  3. 2/3 revision.h: rename struct member to reflect notes rolekristofferhaugsbakk@fastmail.com, Aug 24, 2026
  4. 3/3 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Aug 24, 2026
  5. Junio C HamanoAug 24, 2026
  6. Kristoffer HaugsbakkAug 25, 2026
  7. Junio C HamanoAug 28, 2026
  8. Kristoffer HaugsbakkAug 28, 2026
  9. Junio C HamanoAug 28, 2026
  10. Kristoffer HaugsbakkSep 2, 2026
  11. Kristoffer HaugsbakkSep 6, 2026
  12. D. Ben KnobleSep 6, 2026
  13. Kristoffer HaugsbakkSep 6, 2026
  14. D. Ben KnobleSep 6, 2026
  15. Junio C HamanoSep 6, 2026
  16. Kristoffer HaugsbakkSep 9, 2026
  17. Junio C HamanoSep 9, 2026
  18. 0/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Sep 26, 2026
  19. 1/2 format-patch: simplify get_notes_arg parameterskristofferhaugsbakk@fastmail.com, Sep 26, 2026
  20. 2/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Sep 26, 2026
  21. Junio C HamanoSep 27, 2026
  22. Kristoffer HaugsbakkSep 27, 2026
  23. Junio C HamanoSep 28, 2026
  24. Kristoffer HaugsbakkSep 28, 2026
  25. 0/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 2, 2026
  26. 1/2 format-patch: simplify get_notes_arg parameterskristofferhaugsbakk@fastmail.com, Oct 2, 2026
  27. Junio C HamanoOct 2, 2026
  28. Kristoffer HaugsbakkOct 2, 2026
  29. Kristoffer HaugsbakkOct 2, 2026
  30. Kristoffer HaugsbakkOct 2, 2026
  31. 2/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 2, 2026
  32. Junio C HamanoOct 2, 2026
  33. Kristoffer HaugsbakkOct 2, 2026
  34. 0/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 4, 2026
  35. 1/2 format-patch: simplify get_notes_arg parameterskristofferhaugsbakk@fastmail.com, Oct 4, 2026
  36. 2/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 4, 2026
  37. Junio C HamanoOct 4, 2026
  38. Kristoffer HaugsbakkOct 4, 2026
  39. 0/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 4, 2026
  40. 1/2 format-patch: simplify get_notes_arg parameterskristofferhaugsbakk@fastmail.com, Oct 4, 2026
  41. 2/2 format-patch: learn --[no-]range-diff-noteskristofferhaugsbakk@fastmail.com, Oct 4, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.