builtin-am: support and auto-detect mercurial patches

Since 0cfd112 (am: preliminary support for hg patches, 2011-08-29), git-am.sh could convert mercurial patches to an RFC2822 mail patch suitable for parsing with git-mailinfo, and queue them in the state directory for application. Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh was able to auto-detect mercurial patches by checking if the file begins with the line: # HG changeset patch Re-implement the above in builtin/am.c. Helped-by: Stefan Beller <sbeller@google.com> Signed-off-by: Paul Tan <pyokagan@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul Tan committed Aug 4, 2015 at 21:52 UTC 94cd175cff196d2571bc2e17ad8e9f47ef4b3911
1 file changed +73 -1
builtin/am.c
+73 -1
@@ -81,7 +81,8 @@ enum patch_format {
81 PATCH_FORMAT_UNKNOWN = 0,
82 PATCH_FORMAT_MBOX,
83 PATCH_FORMAT_STGIT,
84 - PATCH_FORMAT_STGIT_SERIES
84 + PATCH_FORMAT_STGIT_SERIES,
85 + PATCH_FORMAT_HG
86 };
87
88 enum keep_type {
@@ -656,6 +657,11 @@ static int detect_patch_format(const char **paths)
657 goto done;
658 }
659
660 + if (!strcmp(l1.buf, "# HG changeset patch")) {
661 + ret = PATCH_FORMAT_HG;
662 + goto done;
663 + }
664 +
665 strbuf_reset(&l2);
666 strbuf_getline_crlf(&l2, fp);
667 strbuf_reset(&l3);
@@ -853,6 +859,68 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths,
859 return ret;
860 }
861
862 +/**
863 + * A split_patches_conv() callback that converts a mercurial patch to a RFC2822
864 + * message suitable for parsing with git-mailinfo.
865 + */
866 +static int hg_patch_to_mail(FILE *out, FILE *in, int keep_cr)
867 +{
868 + struct strbuf sb = STRBUF_INIT;
869 +
870 + while (!strbuf_getline(&sb, in, '\n')) {
871 + const char *str;
872 +
873 + if (skip_prefix(sb.buf, "# User ", &str))
874 + fprintf(out, "From: %s\n", str);
875 + else if (skip_prefix(sb.buf, "# Date ", &str)) {
876 + unsigned long timestamp;
877 + long tz, tz2;
878 + char *end;
879 +
880 + errno = 0;
881 + timestamp = strtoul(str, &end, 10);
882 + if (errno)
883 + return error(_("invalid timestamp"));
884 +
885 + if (!skip_prefix(end, " ", &str))
886 + return error(_("invalid Date line"));
887 +
888 + errno = 0;
889 + tz = strtol(str, &end, 10);
890 + if (errno)
891 + return error(_("invalid timezone offset"));
892 +
893 + if (*end)
894 + return error(_("invalid Date line"));
895 +
896 + /*
897 + * mercurial's timezone is in seconds west of UTC,
898 + * however git's timezone is in hours + minutes east of
899 + * UTC. Convert it.
900 + */
901 + tz2 = labs(tz) / 3600 * 100 + labs(tz) % 3600 / 60;
902 + if (tz > 0)
903 + tz2 = -tz2;
904 +
905 + fprintf(out, "Date: %s\n", show_date(timestamp, tz2, DATE_MODE(RFC2822)));
906 + } else if (starts_with(sb.buf, "# ")) {
907 + continue;
908 + } else {
909 + fprintf(out, "\n%s\n", sb.buf);
910 + break;
911 + }
912 + }
913 +
914 + strbuf_reset(&sb);
915 + while (strbuf_fread(&sb, 8192, in) > 0) {
916 + fwrite(sb.buf, 1, sb.len, out);
917 + strbuf_reset(&sb);
918 + }
919 +
920 + strbuf_release(&sb);
921 + return 0;
922 +}
923 +
924 /**
925 * Splits a list of files/directories into individual email patches. Each path
926 * in `paths` must be a file/directory that is formatted according to
@@ -885,6 +953,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
953 return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
954 case PATCH_FORMAT_STGIT_SERIES:
955 return split_mail_stgit_series(state, paths, keep_cr);
956 + case PATCH_FORMAT_HG:
957 + return split_mail_conv(hg_patch_to_mail, state, paths, keep_cr);
958 default:
959 die("BUG: invalid patch_format");
960 }
@@ -1937,6 +2007,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
2007 *opt_value = PATCH_FORMAT_STGIT;
2008 else if (!strcmp(arg, "stgit-series"))
2009 *opt_value = PATCH_FORMAT_STGIT_SERIES;
2010 + else if (!strcmp(arg, "hg"))
2011 + *opt_value = PATCH_FORMAT_HG;
2012 else
2013 return error(_("Invalid value for --patch-format: %s"), arg);
2014 return 0;