thunderbird-patch-review
Easy-to-use patch review interface for Thunderbird
Contribute: ~marcc/thunderbird-review-plugin@lists.sr.ht
git clone git://mccd.space/thunderbird-patch-review
| Log | Files | Refs | README | LICENSE |
patch-detect.js (2858B)
1 // Heuristics for recognizing git patches sent by email (git send-email /
2 // git format-patch conventions).
3
4 // Matches subject tags like [PATCH], [PATCH 3/7], [RFC PATCH v2 0/5],
5 // [PATCH net-next v3 1/2]. Reply prefixes ("Re: ") may precede the tag.
6 const SUBJECT_TAG_RE = /^(?:\s*(?:re|aw|fwd?)\s*:\s*)*\[(?<tag>[^\]]*\bPATCH\b[^\]]*)\]\s*(?<title>.*)$/i;
7
8 /**
9 * Parse a patch subject line.
10 * @returns {null | {
11 * tag: string, // full text inside the brackets
12 * title: string, // subject after the tag
13 * prefix: string, // tag minus version and n/m, normalized ("PATCH", "RFC PATCH net-next", ...)
14 * version: number, // 1 when no vN marker present
15 * n: number|null, // index within the series, 0 = cover letter
16 * m: number|null // series size, null for a bare [PATCH]
17 * }}
18 */
19 export function parseSubject(subject) {
20 const match = SUBJECT_TAG_RE.exec(subject || "");
21 if (!match) {
22 return null;
23 }
24 const tag = match.groups.tag.trim();
25
26 let n = null;
27 let m = null;
28 const nm = /\b(\d+)\s*\/\s*(\d+)\b/.exec(tag);
29 if (nm) {
30 n = parseInt(nm[1], 10);
31 m = parseInt(nm[2], 10);
32 }
33
34 let version = 1;
35 const ver = /\bv(\d+)\b/i.exec(tag);
36 if (ver) {
37 version = parseInt(ver[1], 10);
38 }
39
40 const prefix = tag
41 .replace(nm ? nm[0] : "", "")
42 .replace(ver ? ver[0] : "", "")
43 .split(/\s+/)
44 .filter(Boolean)
45 .join(" ")
46 .toUpperCase();
47
48 return { tag, title: match.groups.title.trim(), prefix, version, n, m };
49 }
50
51 const REPLY_PREFIX_RE = /^(?:\s*(?:re|aw|fwd?)\s*:\s*)+/i;
52
53 /** True if the subject carries a leading Re:/Aw:/Fwd: (etc.) reply prefix. */
54 export function isReplySubject(subject) {
55 return REPLY_PREFIX_RE.test(subject || "");
56 }
57
58 /** True if the text contains a unified diff. */
59 export function bodyLooksLikeDiff(text) {
60 if (!text) {
61 return false;
62 }
63 if (/^diff --git /m.test(text)) {
64 return true;
65 }
66 if (/^--- (?:a\/|\/dev\/null)/m.test(text) && /^\+\+\+ (?:b\/|\/dev\/null)/m.test(text)) {
67 return true;
68 }
69 return /^@@ -\d+(?:,\d+)? \+\d+(?:,\d+)? @@/m.test(text);
70 }
71
72 /**
73 * Decide whether a message is a reviewable patch. The subject tag is the
74 * primary signal; a diff in the body confirms it (and catches patches sent
75 * without the conventional tag). A cover letter (0/m) has no diff but is
76 * still part of a series.
77 */
78 export function isPatchMessage(subject, body) {
79 const info = parseSubject(subject);
80 if (info) {
81 return info.n === 0 || bodyLooksLikeDiff(body) || info.m !== null;
82 }
83 return bodyLooksLikeDiff(body);
84 }
85
86 /**
87 * True when two parsed subjects belong to the same patch series:
88 * same normalized prefix, same version, same series size.
89 */
90 export function sameSeries(a, b) {
91 if (!a || !b) {
92 return false;
93 }
94 return a.prefix === b.prefix && a.version === b.version && a.m === b.m;
95 }