format-patch: support deep threading

For deep threading mode, i.e., the mode that gives a thread structured
like

  + [PATCH 0/n] Cover letter
   `-+ [PATCH 1/n] First patch
      `-+ [PATCH 2/n] Second patch
         `-+ ...

we currently have to use 'git send-email --thread' (the default).  On
the other hand, format-patch also has a --thread option which gives
shallow mode, i.e.,

  + [PATCH 0/n] Cover letter
  |-+ [PATCH 1/n] First patch
  |-+ [PATCH 2/n] Second patch
  ...

To reduce the confusion resulting from having two indentically named
features in different tools giving different results, let format-patch
take an optional argument '--thread=deep' that gives the same output
as 'send-mail --thread'.  With no argument, or 'shallow', behave as
before.  Also add a configuration variable format.thread with the same
semantics.

Signed-off-by: Thomas Rast <trast@student.ethz.ch>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Thomas Rast 2009-02-19 22:26:33 +01:00 committed by Junio C Hamano
parent 2175c10d5a
commit 30984ed2e9
4 changed files with 170 additions and 5 deletions

View File

@ -677,6 +677,16 @@ format.pretty::
See linkgit:git-log[1], linkgit:git-show[1], See linkgit:git-log[1], linkgit:git-show[1],
linkgit:git-whatchanged[1]. linkgit:git-whatchanged[1].
format.thread::
The default threading style for 'git-format-patch'. Can be
either a boolean value, `shallow` or `deep`. 'Shallow'
threading makes every mail a reply to the head of the series,
where the head is chosen from the cover letter, the
`\--in-reply-to`, and the first patch mail, in this order.
'Deep' threading makes every mail a reply to the previous one.
A true boolean value is the same as `shallow`, and a false
value disables threading.
gc.aggressiveWindow:: gc.aggressiveWindow::
The window size parameter used in the delta compression The window size parameter used in the delta compression
algorithm used by 'git-gc --aggressive'. This defaults algorithm used by 'git-gc --aggressive'. This defaults

View File

@ -122,10 +122,18 @@ include::diff-options.txt[]
which is the commit message and the patch itself in the which is the commit message and the patch itself in the
second part, with "Content-Disposition: inline". second part, with "Content-Disposition: inline".
--thread:: --thread[=<style>]::
Add In-Reply-To and References headers to make the second and Add In-Reply-To and References headers to make the second and
subsequent mails appear as replies to the first. Also generates subsequent mails appear as replies to the first. Also generates
the Message-Id header to reference. the Message-Id header to reference.
+
The optional <style> argument can be either `shallow` or `deep`.
'Shallow' threading makes every mail a reply to the head of the
series, where the head is chosen from the cover letter, the
`\--in-reply-to`, and the first patch mail, in this order. 'Deep'
threading makes every mail a reply to the previous one. If not
specified, defaults to the 'format.thread' configuration, or `shallow`
if that is not set.
--in-reply-to=Message-Id:: --in-reply-to=Message-Id::
Make the first mail (or all the mails with --no-thread) appear as a Make the first mail (or all the mails with --no-thread) appear as a

View File

@ -460,6 +460,10 @@ static void add_header(const char *value)
extra_hdr[extra_hdr_nr++] = xstrndup(value, len); extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
} }
#define THREAD_SHALLOW 1
#define THREAD_DEEP 2
static int thread = 0;
static int git_format_config(const char *var, const char *value, void *cb) static int git_format_config(const char *var, const char *value, void *cb)
{ {
if (!strcmp(var, "format.headers")) { if (!strcmp(var, "format.headers")) {
@ -489,6 +493,18 @@ static int git_format_config(const char *var, const char *value, void *cb)
auto_number = auto_number && numbered; auto_number = auto_number && numbered;
return 0; return 0;
} }
if (!strcmp(var, "format.thread")) {
if (value && !strcasecmp(value, "deep")) {
thread = THREAD_DEEP;
return 0;
}
if (value && !strcasecmp(value, "shallow")) {
thread = THREAD_SHALLOW;
return 0;
}
thread = git_config_bool(var, value) && THREAD_SHALLOW;
return 0;
}
return git_log_config(var, value, cb); return git_log_config(var, value, cb);
} }
@ -767,7 +783,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int numbered_files = 0; /* _just_ numbers */ int numbered_files = 0; /* _just_ numbers */
int subject_prefix = 0; int subject_prefix = 0;
int ignore_if_in_upstream = 0; int ignore_if_in_upstream = 0;
int thread = 0;
int cover_letter = 0; int cover_letter = 0;
int boundary_count = 0; int boundary_count = 0;
int no_binary_diff = 0; int no_binary_diff = 0;
@ -860,8 +875,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
} }
else if (!strcmp(argv[i], "--ignore-if-in-upstream")) else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
ignore_if_in_upstream = 1; ignore_if_in_upstream = 1;
else if (!strcmp(argv[i], "--thread")) else if (!strcmp(argv[i], "--thread")
thread = 1; || !strcmp(argv[i], "--thread=shallow"))
thread = THREAD_SHALLOW;
else if (!strcmp(argv[i], "--thread=deep"))
thread = THREAD_DEEP;
else if (!strcmp(argv[i], "--no-thread"))
thread = 0;
else if (!prefixcmp(argv[i], "--in-reply-to=")) else if (!prefixcmp(argv[i], "--in-reply-to="))
in_reply_to = argv[i] + 14; in_reply_to = argv[i] + 14;
else if (!strcmp(argv[i], "--in-reply-to")) { else if (!strcmp(argv[i], "--in-reply-to")) {
@ -1036,6 +1056,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
/* Have we already had a message ID? */ /* Have we already had a message ID? */
if (rev.message_id) { if (rev.message_id) {
/* /*
* For deep threading: make every mail
* a reply to the previous one, no
* matter what other options are set.
*
* For shallow threading:
*
* Without --cover-letter and * Without --cover-letter and
* --in-reply-to, make every mail a * --in-reply-to, make every mail a
* reply to the one before. * reply to the one before.
@ -1050,7 +1076,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* letter is a reply to the * letter is a reply to the
* --in-reply-to, if specified. * --in-reply-to, if specified.
*/ */
if (rev.ref_message_ids->nr > 0 if (thread == THREAD_SHALLOW
&& rev.ref_message_ids->nr > 0
&& (!cover_letter || rev.nr > 1)) && (!cover_letter || rev.nr > 1))
free(rev.message_id); free(rev.message_id);
else else

View File

@ -257,6 +257,126 @@ test_expect_success 'thread cover-letter in-reply-to' '
--in-reply-to="<test.message>" --thread master --in-reply-to="<test.message>" --thread master
' '
test_expect_success 'thread explicit shallow' '
check_threading expect.cl-irt --cover-letter \
--in-reply-to="<test.message>" --thread=shallow master
'
cat > expect.deep <<EOF
---
Message-Id: <0>
---
Message-Id: <1>
In-Reply-To: <0>
References: <0>
---
Message-Id: <2>
In-Reply-To: <1>
References: <0>
<1>
EOF
test_expect_success 'thread deep' '
check_threading expect.deep --thread=deep master
'
cat > expect.deep-irt <<EOF
---
Message-Id: <0>
In-Reply-To: <1>
References: <1>
---
Message-Id: <2>
In-Reply-To: <0>
References: <1>
<0>
---
Message-Id: <3>
In-Reply-To: <2>
References: <1>
<0>
<2>
EOF
test_expect_success 'thread deep in-reply-to' '
check_threading expect.deep-irt --thread=deep \
--in-reply-to="<test.message>" master
'
cat > expect.deep-cl <<EOF
---
Message-Id: <0>
---
Message-Id: <1>
In-Reply-To: <0>
References: <0>
---
Message-Id: <2>
In-Reply-To: <1>
References: <0>
<1>
---
Message-Id: <3>
In-Reply-To: <2>
References: <0>
<1>
<2>
EOF
test_expect_success 'thread deep cover-letter' '
check_threading expect.deep-cl --cover-letter --thread=deep master
'
cat > expect.deep-cl-irt <<EOF
---
Message-Id: <0>
In-Reply-To: <1>
References: <1>
---
Message-Id: <2>
In-Reply-To: <0>
References: <1>
<0>
---
Message-Id: <3>
In-Reply-To: <2>
References: <1>
<0>
<2>
---
Message-Id: <4>
In-Reply-To: <3>
References: <1>
<0>
<2>
<3>
EOF
test_expect_success 'thread deep cover-letter in-reply-to' '
check_threading expect.deep-cl-irt --cover-letter \
--in-reply-to="<test.message>" --thread=deep master
'
test_expect_success 'thread via config' '
git config format.thread true &&
check_threading expect.thread master
'
test_expect_success 'thread deep via config' '
git config format.thread deep &&
check_threading expect.deep master
'
test_expect_success 'thread config + override' '
git config format.thread deep &&
check_threading expect.thread --thread master
'
test_expect_success 'thread config + --no-thread' '
git config format.thread deep &&
check_threading expect.no-threading --no-thread master
'
test_expect_success 'excessive subject' ' test_expect_success 'excessive subject' '
rm -rf patches/ && rm -rf patches/ &&