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;