···55 "errors"
66 "fmt"
77 "io"
88+ "mime/quotedprintable"
89 "net/mail"
910 "strconv"
1011 "strings"
···457458 break
458459 }
459460460460- return s[:at], s[at:]
461461+ return s[:at], decodeSubject(s[at:])
462462+}
463463+464464+// Decodes a subject line. Currently only supports quoted-printable UTF-8. This format is the result
465465+// of a `git format-patch` when the commit title has a non-ASCII character (i.e. an emoji).
466466+// See for reference: https://stackoverflow.com/questions/27695749/gmail-api-not-respecting-utf-encoding-in-subject
467467+func decodeSubject(encoded string) string {
468468+ if !strings.HasPrefix(encoded, "=?UTF-8?q?") {
469469+ // not UTF-8 encoded
470470+ return encoded
471471+ }
472472+473473+ // If the subject is too long, `git format-patch` may produce a subject line across
474474+ // multiple lines. When parsed, this can look like the following:
475475+ // <UTF8-prefix><first-line> <UTF8-prefix><second-line>
476476+ payload := " " + encoded
477477+ payload = strings.ReplaceAll(payload, " =?UTF-8?q?", "")
478478+ payload = strings.ReplaceAll(payload, "?=", "")
479479+480480+ decoded, err := io.ReadAll(quotedprintable.NewReader(strings.NewReader(payload)))
481481+ if err != nil {
482482+ // if err, abort decoding and return original subject
483483+ return encoded
484484+ }
485485+486486+ return string(decoded)
461487}
+41
gitdiff/patch_header_test.go
···138138 }
139139 expectedDate := time.Date(2020, 04, 11, 15, 21, 23, 0, time.FixedZone("PDT", -7*60*60))
140140 expectedTitle := "A sample commit to test header parsing"
141141+ expectedEmojiOneLineTitle := "🤖 Enabling auto-merging"
142142+ expectedEmojiMultiLineTitle := "[IA64] Put ia64 config files on the Uwe Kleine-König diet"
141143 expectedBody := "The medium format shows the body, which\nmay wrap on to multiple lines.\n\nAnother body line."
142144 expectedBodyAppendix := "CC: Joe Smith <joe.smith@company.com>"
143145···264266 Author: expectedIdentity,
265267 AuthorDate: expectedDate,
266268 Title: expectedTitle,
269269+ Body: expectedBody,
270270+ },
271271+ },
272272+ "mailboxEmojiOneLine": {
273273+ Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
274274+From: Morton Haypenny <mhaypenny@example.com>
275275+Date: Sat, 11 Apr 2020 15:21:23 -0700
276276+Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Enabling=20auto-merging?=
277277+278278+The medium format shows the body, which
279279+may wrap on to multiple lines.
280280+281281+Another body line.
282282+`,
283283+ Header: PatchHeader{
284284+ SHA: expectedSHA,
285285+ Author: expectedIdentity,
286286+ AuthorDate: expectedDate,
287287+ Title: expectedEmojiOneLineTitle,
288288+ Body: expectedBody,
289289+ },
290290+ },
291291+ "mailboxEmojiMultiLine": {
292292+ Input: `From 61f5cd90bed4d204ee3feb3aa41ee91d4734855b Mon Sep 17 00:00:00 2001
293293+From: Morton Haypenny <mhaypenny@example.com>
294294+Date: Sat, 11 Apr 2020 15:21:23 -0700
295295+Subject: [PATCH] =?UTF-8?q?[IA64]=20Put=20ia64=20config=20files=20on=20the=20?=
296296+ =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20diet?=
297297+298298+The medium format shows the body, which
299299+may wrap on to multiple lines.
300300+301301+Another body line.
302302+`,
303303+ Header: PatchHeader{
304304+ SHA: expectedSHA,
305305+ Author: expectedIdentity,
306306+ AuthorDate: expectedDate,
307307+ Title: expectedEmojiMultiLineTitle,
267308 Body: expectedBody,
268309 },
269310 },