Skip to content

Commit 5ae41c7

Browse files
pyokagangitster
authored andcommitted
builtin-am: support and auto-detect StGit patches
Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27), git-am.sh supported converting StGit patches into RFC2822 mail patches that can be parsed with git-mailinfo. Implement this by introducing two functions in builtin/am.c: stgit_patch_to_mail() and split_mail_conv(). stgit_patch_to_mail() is a callback function for split_mail_conv(), and contains the logic for converting an StGit patch into an RFC2822 mail patch. split_mail_conv() implements the logic to go through each file in the `paths` list, reading from stdin where specified, and calls the callback function to write the converted patch to the corresponding output file in the state directory. This interface should be generic enough to support other foreign patch formats in the future. Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches. Re-implement this in builtin/am.c. Helped-by: Eric Sunshine <[email protected]> Signed-off-by: Paul Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent f1cb96d commit 5ae41c7

File tree

1 file changed

+131
-1
lines changed

1 file changed

+131
-1
lines changed

builtin/am.c

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,22 @@ static int linelen(const char *msg)
6565
return strchrnul(msg, '\n') - msg;
6666
}
6767

68+
/**
69+
* Returns true if `str` consists of only whitespace, false otherwise.
70+
*/
71+
static int str_isspace(const char *str)
72+
{
73+
for (; *str; str++)
74+
if (!isspace(*str))
75+
return 0;
76+
77+
return 1;
78+
}
79+
6880
enum patch_format {
6981
PATCH_FORMAT_UNKNOWN = 0,
70-
PATCH_FORMAT_MBOX
82+
PATCH_FORMAT_MBOX,
83+
PATCH_FORMAT_STGIT
7184
};
7285

7386
enum keep_type {
@@ -610,6 +623,8 @@ static int detect_patch_format(const char **paths)
610623
{
611624
enum patch_format ret = PATCH_FORMAT_UNKNOWN;
612625
struct strbuf l1 = STRBUF_INIT;
626+
struct strbuf l2 = STRBUF_INIT;
627+
struct strbuf l3 = STRBUF_INIT;
613628
FILE *fp;
614629

615630
/*
@@ -635,6 +650,23 @@ static int detect_patch_format(const char **paths)
635650
goto done;
636651
}
637652

653+
strbuf_reset(&l2);
654+
strbuf_getline_crlf(&l2, fp);
655+
strbuf_reset(&l3);
656+
strbuf_getline_crlf(&l3, fp);
657+
658+
/*
659+
* If the second line is empty and the third is a From, Author or Date
660+
* entry, this is likely an StGit patch.
661+
*/
662+
if (l1.len && !l2.len &&
663+
(starts_with(l3.buf, "From:") ||
664+
starts_with(l3.buf, "Author:") ||
665+
starts_with(l3.buf, "Date:"))) {
666+
ret = PATCH_FORMAT_STGIT;
667+
goto done;
668+
}
669+
638670
if (l1.len && is_mail(fp)) {
639671
ret = PATCH_FORMAT_MBOX;
640672
goto done;
@@ -674,6 +706,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_
674706
return 0;
675707
}
676708

709+
/**
710+
* Callback signature for split_mail_conv(). The foreign patch should be
711+
* read from `in`, and the converted patch (in RFC2822 mail format) should be
712+
* written to `out`. Return 0 on success, or -1 on failure.
713+
*/
714+
typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
715+
716+
/**
717+
* Calls `fn` for each file in `paths` to convert the foreign patch to the
718+
* RFC2822 mail format suitable for parsing with git-mailinfo.
719+
*
720+
* Returns 0 on success, -1 on failure.
721+
*/
722+
static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
723+
const char **paths, int keep_cr)
724+
{
725+
static const char *stdin_only[] = {"-", NULL};
726+
int i;
727+
728+
if (!*paths)
729+
paths = stdin_only;
730+
731+
for (i = 0; *paths; paths++, i++) {
732+
FILE *in, *out;
733+
const char *mail;
734+
int ret;
735+
736+
if (!strcmp(*paths, "-"))
737+
in = stdin;
738+
else
739+
in = fopen(*paths, "r");
740+
741+
if (!in)
742+
return error(_("could not open '%s' for reading: %s"),
743+
*paths, strerror(errno));
744+
745+
mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
746+
747+
out = fopen(mail, "w");
748+
if (!out)
749+
return error(_("could not open '%s' for writing: %s"),
750+
mail, strerror(errno));
751+
752+
ret = fn(out, in, keep_cr);
753+
754+
fclose(out);
755+
fclose(in);
756+
757+
if (ret)
758+
return error(_("could not parse patch '%s'"), *paths);
759+
}
760+
761+
state->cur = 1;
762+
state->last = i;
763+
return 0;
764+
}
765+
766+
/**
767+
* A split_mail_conv() callback that converts an StGit patch to an RFC2822
768+
* message suitable for parsing with git-mailinfo.
769+
*/
770+
static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
771+
{
772+
struct strbuf sb = STRBUF_INIT;
773+
int subject_printed = 0;
774+
775+
while (!strbuf_getline(&sb, in, '\n')) {
776+
const char *str;
777+
778+
if (str_isspace(sb.buf))
779+
continue;
780+
else if (skip_prefix(sb.buf, "Author:", &str))
781+
fprintf(out, "From:%s\n", str);
782+
else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
783+
fprintf(out, "%s\n", sb.buf);
784+
else if (!subject_printed) {
785+
fprintf(out, "Subject: %s\n", sb.buf);
786+
subject_printed = 1;
787+
} else {
788+
fprintf(out, "\n%s\n", sb.buf);
789+
break;
790+
}
791+
}
792+
793+
strbuf_reset(&sb);
794+
while (strbuf_fread(&sb, 8192, in) > 0) {
795+
fwrite(sb.buf, 1, sb.len, out);
796+
strbuf_reset(&sb);
797+
}
798+
799+
strbuf_release(&sb);
800+
return 0;
801+
}
802+
677803
/**
678804
* Splits a list of files/directories into individual email patches. Each path
679805
* in `paths` must be a file/directory that is formatted according to
@@ -702,6 +828,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
702828
switch (patch_format) {
703829
case PATCH_FORMAT_MBOX:
704830
return split_mail_mbox(state, paths, keep_cr);
831+
case PATCH_FORMAT_STGIT:
832+
return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
705833
default:
706834
die("BUG: invalid patch_format");
707835
}
@@ -1750,6 +1878,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
17501878

17511879
if (!strcmp(arg, "mbox"))
17521880
*opt_value = PATCH_FORMAT_MBOX;
1881+
else if (!strcmp(arg, "stgit"))
1882+
*opt_value = PATCH_FORMAT_STGIT;
17531883
else
17541884
return error(_("Invalid value for --patch-format: %s"), arg);
17551885
return 0;

0 commit comments

Comments
 (0)