git-send-email: add --transfer-encoding option

The thread at http://thread.gmane.org/gmane.comp.version-control.git/257392 details problems when applying patches with "git am" in a repository with CRLF line endings. In the example in the thread, the repository originated from "git-svn" so it is not possible to use core.eol and friends on it. Right now, the best option is to use "git am --keep-cr". However, when a patch create new files, the patch application process will reject the new file because it finds a "/dev/null\r" string instead of "/dev/null". The problem is that SMTP transport is CRLF-unsafe. Sending a patch by email is the same as passing it through "dos2unix | unix2dos". The newly introduced CRLFs are normally transparent because git-am strips them. The keepcr=true setting preserves them, but it is mostly working by chance and it would be very problematic to have a "git am" workflow in a repository with mixed LF and CRLF line endings. The MIME solution to this is the quoted-printable transfer enconding. This is not something that we want to enable by default, since it makes received emails horrible to look at. However, it is a very good match for projects that store CRLF line endings in the repository. The only disadvantage of quoted-printable is that quoted-printable patches fail to apply if the maintainer uses "git am --keep-cr". This is because the decoded patch will have two carriage returns at the end of the line. Therefore, add support for base64 transfer encoding too, which makes received emails downright impossible to look at outside a MUA, but really just works. The patch covers all bases, including users that still live in the late 80s, by also providing a 7bit content transfer encoding that refuses to send emails with non-ASCII character in them. And finally, "8bit" will add a Content-Transfer-Encoding header but otherwise do nothing. Signed-off-by: Paolo Bonzini <pbonzini@redhat.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paolo Bonzini committed Nov 25, 2014 at 15:00 UTC 8d8140843501107c92e2f9a5acb60ee136352c1f
5 files changed +208
Documentation/config.txt
+1
@@ -2303,6 +2303,7 @@ sendemail.smtpserverport::
2303 sendemail.smtpserveroption::
2304 sendemail.smtpuser::
2305 sendemail.thread::
2306 +sendemail.transferencoding::
2307 sendemail.validate::
2308 See linkgit:git-send-email[1] for description.
2309
Documentation/git-send-email.txt
+10
@@ -131,6 +131,16 @@ Note that no attempts whatsoever are made to validate the encoding.
131 Specify encoding of compose message. Default is the value of the
132 'sendemail.composeencoding'; if that is unspecified, UTF-8 is assumed.
133
134 +--transfer-encoding=(7bit|8bit|quoted-printable|base64)::
135 + Specify the transfer encoding to be used to send the message over SMTP.
136 + 7bit will fail upon encountering a non-ASCII message. quoted-printable
137 + can be useful when the repository contains files that contain carriage
138 + returns, but makes the raw patch email file (as saved from a MUA) much
139 + harder to inspect manually. base64 is even more fool proof, but also
140 + even more opaque. Default is the value of the 'sendemail.transferEncoding'
141 + configuration value; if that is unspecified, git will use 8bit and not
142 + add a Content-Transfer-Encoding header.
143 +
144
145 Sending
146 ~~~~~~~
contrib/completion/git-completion.bash
+4
@@ -1875,6 +1875,10 @@ _git_config ()
1875 __gitcomp "$__git_send_email_suppresscc_options"
1876 return
1877 ;;
1878 + sendemail.transferencoding)
1879 + __gitcomp "7bit 8bit quoted-printable base64"
1880 + return
1881 + ;;
1882 --get|--get-all|--unset|--unset-all)
1883 __gitcomp_nl "$(__git_config_get_set_variables)"
1884 return
git-send-email.perl
+36
@@ -58,6 +58,7 @@ git send-email [options] <file | directory | rev-list options >
58 --compose * Open an editor for introduction.
59 --compose-encoding <str> * Encoding to assume for introduction.
60 --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared
61 + --transfer-encoding <str> * Transfer encoding to use (quoted-printable, 8bit, base64)
62
63 Sending:
64 --envelope-sender <str> * Email envelope sender.
@@ -206,6 +207,7 @@ my ($validate, $confirm);
207 my (@suppress_cc);
208 my ($auto_8bit_encoding);
209 my ($compose_encoding);
210 +my ($target_xfer_encoding);
211
212 my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
213
@@ -242,6 +244,7 @@ my %config_settings = (
244 "from" => \$sender,
245 "assume8bitencoding" => \$auto_8bit_encoding,
246 "composeencoding" => \$compose_encoding,
247 + "transferencoding" => \$target_xfer_encoding,
248 );
249
250 my %config_path_settings = (
@@ -314,6 +317,7 @@ my $rc = GetOptions("h" => \$help,
317 "envelope-sender=s" => \$envelope_sender,
318 "thread!" => \$thread,
319 "validate!" => \$validate,
320 + "transfer-encoding=s" => \$target_xfer_encoding,
321 "format-patch!" => \$format_patch,
322 "8bit-encoding=s" => \$auto_8bit_encoding,
323 "compose-encoding=s" => \$compose_encoding,
@@ -1482,6 +1486,12 @@ foreach my $t (@files) {
1486 }
1487 }
1488 }
1489 + if (defined $target_xfer_encoding) {
1490 + $xfer_encoding = '8bit' if not defined $xfer_encoding;
1491 + $message = apply_transfer_encoding(
1492 + $message, $xfer_encoding, $target_xfer_encoding);
1493 + $xfer_encoding = $target_xfer_encoding;
1494 + }
1495 if (defined $xfer_encoding) {
1496 push @xh, "Content-Transfer-Encoding: $xfer_encoding";
1497 }
@@ -1556,6 +1566,32 @@ sub cleanup_compose_files {
1566
1567 $smtp->quit if $smtp;
1568
1569 +sub apply_transfer_encoding {
1570 + my $message = shift;
1571 + my $from = shift;
1572 + my $to = shift;
1573 +
1574 + return $message if ($from eq $to and $from ne '7bit');
1575 +
1576 + require MIME::QuotedPrint;
1577 + require MIME::Base64;
1578 +
1579 + $message = MIME::QuotedPrint::decode($message)
1580 + if ($from eq 'quoted-printable');
1581 + $message = MIME::Base64::decode($message)
1582 + if ($from eq 'base64');
1583 +
1584 + die "cannot send message as 7bit"
1585 + if ($to eq '7bit' and $message =~ /[^[:ascii:]]/);
1586 + return $message
1587 + if ($to eq '7bit' or $to eq '8bit');
1588 + return MIME::QuotedPrint::encode($message, "\n", 0)
1589 + if ($to eq 'quoted-printable');
1590 + return MIME::Base64::encode($message, "\n")
1591 + if ($to eq 'base64');
1592 + die "invalid transfer encoding";
1593 +}
1594 +
1595 sub unique_email_list {
1596 my %seen;
1597 my @emails;
t/t9001-send-email.sh
+157
@@ -1298,6 +1298,163 @@ test_expect_success $PREREQ '--8bit-encoding also treats subject' '
1298 test_cmp expected actual
1299 '
1300
1301 +test_expect_success $PREREQ 'setup expect' '
1302 +cat >email-using-8bit <<EOF
1303 +From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
1304 +Message-Id: <bogus-message-id@example.com>
1305 +From: A U Thor <author@example.com>
1306 +Date: Sat, 12 Jun 2010 15:53:58 +0200
1307 +Content-Type: text/plain; charset=UTF-8
1308 +Subject: Nothing to see here.
1309 +
1310 +Dieser Betreff enthält auch einen Umlaut!
1311 +EOF
1312 +'
1313 +
1314 +test_expect_success $PREREQ 'sendemail.transferencoding=7bit fails on 8bit data' '
1315 + clean_fake_sendmail &&
1316 + git config sendemail.transferEncoding 7bit &&
1317 + test_must_fail git send-email \
1318 + --transfer-encoding=7bit \
1319 + --smtp-server="$(pwd)/fake.sendmail" \
1320 + email-using-8bit \
1321 + 2>errors >out &&
1322 + grep "cannot send message as 7bit" errors &&
1323 + test -z "$(ls msgtxt*)"
1324 +'
1325 +
1326 +test_expect_success $PREREQ '--transfer-encoding overrides sendemail.transferEncoding' '
1327 + clean_fake_sendmail &&
1328 + git config sendemail.transferEncoding 8bit
1329 + test_must_fail git send-email \
1330 + --transfer-encoding=7bit \
1331 + --smtp-server="$(pwd)/fake.sendmail" \
1332 + email-using-8bit \
1333 + 2>errors >out &&
1334 + grep "cannot send message as 7bit" errors &&
1335 + test -z "$(ls msgtxt*)"
1336 +'
1337 +
1338 +test_expect_success $PREREQ 'sendemail.transferencoding=8bit' '
1339 + clean_fake_sendmail &&
1340 + git send-email \
1341 + --transfer-encoding=8bit \
1342 + --smtp-server="$(pwd)/fake.sendmail" \
1343 + email-using-8bit \
1344 + 2>errors >out &&
1345 + sed '1,/^$/d' msgtxt1 >actual &&
1346 + sed '1,/^$/d' email-using-8bit >expected &&
1347 + test_cmp expected actual
1348 +'
1349 +
1350 +test_expect_success $PREREQ 'setup expect' '
1351 +cat >expected <<EOF
1352 +Dieser Betreff enth=C3=A4lt auch einen Umlaut!
1353 +EOF
1354 +'
1355 +
1356 +test_expect_success $PREREQ '8-bit and sendemail.transferencoding=quoted-printable' '
1357 + clean_fake_sendmail &&
1358 + git send-email \
1359 + --transfer-encoding=quoted-printable \
1360 + --smtp-server="$(pwd)/fake.sendmail" \
1361 + email-using-8bit \
1362 + 2>errors >out &&
1363 + sed '1,/^$/d' msgtxt1 >actual &&
1364 + test_cmp expected actual
1365 +'
1366 +
1367 +test_expect_success $PREREQ 'setup expect' '
1368 +cat >expected <<EOF
1369 +RGllc2VyIEJldHJlZmYgZW50aMOkbHQgYXVjaCBlaW5lbiBVbWxhdXQhCg==
1370 +EOF
1371 +'
1372 +
1373 +test_expect_success $PREREQ '8-bit and sendemail.transferencoding=base64' '
1374 + clean_fake_sendmail &&
1375 + git send-email \
1376 + --transfer-encoding=base64 \
1377 + --smtp-server="$(pwd)/fake.sendmail" \
1378 + email-using-8bit \
1379 + 2>errors >out &&
1380 + sed '1,/^$/d' msgtxt1 >actual &&
1381 + test_cmp expected actual
1382 +'
1383 +
1384 +test_expect_success $PREREQ 'setup expect' '
1385 +cat >email-using-qp <<EOF
1386 +From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
1387 +Message-Id: <bogus-message-id@example.com>
1388 +From: A U Thor <author@example.com>
1389 +Date: Sat, 12 Jun 2010 15:53:58 +0200
1390 +MIME-Version: 1.0
1391 +Content-Transfer-Encoding: quoted-printable
1392 +Content-Type: text/plain; charset=UTF-8
1393 +Subject: Nothing to see here.
1394 +
1395 +Dieser Betreff enth=C3=A4lt auch einen Umlaut!
1396 +EOF
1397 +'
1398 +
1399 +test_expect_success $PREREQ 'convert from quoted-printable to base64' '
1400 + clean_fake_sendmail &&
1401 + git send-email \
1402 + --transfer-encoding=base64 \
1403 + --smtp-server="$(pwd)/fake.sendmail" \
1404 + email-using-qp \
1405 + 2>errors >out &&
1406 + sed '1,/^$/d' msgtxt1 >actual &&
1407 + test_cmp expected actual
1408 +'
1409 +
1410 +test_expect_success $PREREQ 'setup expect' "
1411 +tr -d '\\015' | tr '%' '\\015' > email-using-crlf <<EOF
1412 +From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001
1413 +Message-Id: <bogus-message-id@example.com>
1414 +From: A U Thor <author@example.com>
1415 +Date: Sat, 12 Jun 2010 15:53:58 +0200
1416 +Content-Type: text/plain; charset=UTF-8
1417 +Subject: Nothing to see here.
1418 +
1419 +Look, I have a CRLF and an = sign!%
1420 +EOF
1421 +"
1422 +
1423 +test_expect_success $PREREQ 'setup expect' '
1424 +cat >expected <<EOF
1425 +Look, I have a CRLF and an =3D sign!=0D
1426 +EOF
1427 +'
1428 +
1429 +test_expect_success $PREREQ 'CRLF and sendemail.transferencoding=quoted-printable' '
1430 + clean_fake_sendmail &&
1431 + git send-email \
1432 + --transfer-encoding=quoted-printable \
1433 + --smtp-server="$(pwd)/fake.sendmail" \
1434 + email-using-crlf \
1435 + 2>errors >out &&
1436 + sed '1,/^$/d' msgtxt1 >actual &&
1437 + test_cmp expected actual
1438 +'
1439 +
1440 +test_expect_success $PREREQ 'setup expect' '
1441 +cat >expected <<EOF
1442 +TG9vaywgSSBoYXZlIGEgQ1JMRiBhbmQgYW4gPSBzaWduIQ0K
1443 +EOF
1444 +'
1445 +
1446 +test_expect_success $PREREQ 'CRLF and sendemail.transferencoding=base64' '
1447 + clean_fake_sendmail &&
1448 + git send-email \
1449 + --transfer-encoding=base64 \
1450 + --smtp-server="$(pwd)/fake.sendmail" \
1451 + email-using-crlf \
1452 + 2>errors >out &&
1453 + sed '1,/^$/d' msgtxt1 >actual &&
1454 + test_cmp expected actual
1455 +'
1456 +
1457 +
1458 # Note that the patches in this test are deliberately out of order; we
1459 # want to make sure it works even if the cover-letter is not in the
1460 # first mail.