git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH/WIP v3 06/31] am: detect mbox patches

From
Paul Tan <pyokagan@gmail.com>
Date
Jun 25, 2015, 13:40 UTC
Message-ID
<CACRoPnQAGt_mLbG-EtJ+qTpD2OvNgi-MZfU=NoKCoSmhCOnBEw@mail.gmail.com>
In-Reply-To
<334feea4cee88d06a10a7363956d2bfe@www.dscho.org>

On Wed, Jun 24, 2015 at 11:10 PM, Johannes Schindelin <johannes.schindelin@gmx.de> wrote:

Show 38 quoted lines
> Hi Paul,
>
> On 2015-06-18 13:25, Paul Tan wrote:
>
>> diff --git a/builtin/am.c b/builtin/am.c
>> index e9a3687..7b97ea8 100644
>> --- a/builtin/am.c
>> +++ b/builtin/am.c
>> @@ -121,6 +121,96 @@ static void am_destroy(const struct am_state *state)
>>       strbuf_release(&sb);
>>  }
>>
>> +/*
>> + * Returns 1 if the file looks like a piece of email a-la RFC2822, 0 otherwise.
>> + * We check this by grabbing all the non-indented lines and seeing if they look
>> + * like they begin with valid header field names.
>> + */
>> +static int is_email(const char *filename)
>> +{
>> +     struct strbuf sb = STRBUF_INIT;
>> +     FILE *fp = xfopen(filename, "r");
>> +     int ret = 1;
>> +
>> +     while (!strbuf_getline(&sb, fp, '\n')) {
>> +             const char *x;
>> +
>> +             strbuf_rtrim(&sb);
>> +
>> +             if (!sb.len)
>> +                     break; /* End of header */
>> +
>> +             /* Ignore indented folded lines */
>> +             if (*sb.buf == '\t' || *sb.buf == ' ')
>> +                     continue;
>> +
>> +             /* It's a header if it matches the regexp "^[!-9;-~]+:" */
>
> Why not just compile a regex and use it here? We use regexes elsewhere anyway...

Ah, you're right. A regular expression would definitely be clearer. I've fixed it on my end.

Show 35 quoted lines
>> +/**
>> + * Attempts to detect the patch_format of the patches contained in `paths`,
>> + * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
>> + * detection fails.
>> + */
>> +static int detect_patch_format(struct string_list *paths)
>> +{
>> +     enum patch_format ret = PATCH_FORMAT_UNKNOWN;
>> +     struct strbuf l1 = STRBUF_INIT;
>> +     struct strbuf l2 = STRBUF_INIT;
>> +     struct strbuf l3 = STRBUF_INIT;
>> +     FILE *fp;
>> +
>> +     /*
>> +      * We default to mbox format if input is from stdin and for directories
>> +      */
>> +     if (!paths->nr || !strcmp(paths->items->string, "-") ||
>> +         is_directory(paths->items->string)) {
>> +             ret = PATCH_FORMAT_MBOX;
>> +             goto done;
>> +     }
>> +
>> +     /*
>> +      * Otherwise, check the first 3 lines of the first patch, starting
>> +      * from the first non-blank line, to try to detect its format.
>> +      */
>> +     fp = xfopen(paths->items->string, "r");
>> +     while (!strbuf_getline(&l1, fp, '\n')) {
>> +             strbuf_trim(&l1);
>> +             if (l1.len)
>> +                     break;
>> +     }
>> +     strbuf_getline(&l2, fp, '\n');
>
> We should test the return value of `strbuf_getline()`; if EOF was reached already, `strbuf_getwholeline()` does not touch the strbuf. I know, the strbuf is still initialized empty here, but it is too easy to forget when changing this code.

Ah OK. I'll vote for doing a strbuf_reset() just before the strbuf_getline() though.

Show 9 quoted lines
>> +     strbuf_trim(&l2);
>> +     strbuf_getline(&l3, fp, '\n');
>> +     strbuf_trim(&l3);
>> +     fclose(fp);
>> +
>> +     if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: "))
>> +             ret = PATCH_FORMAT_MBOX;
>
> Hmm. We can test that earlier and return without reading from the file any further, I think.

The "reading 3 lines at the beginning" logic is meant to support a later patch where support for StGit and mercurial patches is added. That said, it's true that we don't need to read 3 lines in this patch, so I think I will remove it in this patch.

>> +     else if (l1.len && l2.len && l3.len && is_email(paths->items->string))
>> +             ret = PATCH_FORMAT_MBOX;
>
> Maybe we can do better than this by folding the `is_email() function into this here function, reusing the same strbuf to read the lines and keeping track of the email header lines we saw... I would really like to avoid opening the same file twice just to figure out whether it is in email format.

Okay, how about every time we call a strbuf_getline(), we save the line to a string_list as well? Like string_list_getline_crlf() below:

/**
 * Like strbuf_getline(), but supports both '\n' and "\r\n" as line
 * terminators.
 */
static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
{
    if (strbuf_getwholeline(sb, fp, '\n'))
        return EOF;
    if (sb->buf[sb->len - 1] == '\n') {
        strbuf_setlen(sb, sb->len - 1);
        if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
            strbuf_setlen(sb, sb->len - 1);
    }
    return 0;
}
/**
 * Like strbuf_getline_crlf(), but appends the line to a string_list and
 * returns it as a string. Returns NULL on EOF.
 */
static const char *string_list_getline_crlf(struct string_list *list, FILE *fp)
{
    struct strbuf sb = STRBUF_INIT;
    struct string_list_item *item;
    if (strbuf_getline_crlf(&sb, fp))
        return NULL;
    item = string_list_append_nodup(list, strbuf_detach(&sb, NULL));
    return item->string;
}

So now, is_email() can have access to previously-read lines, and if it needs some more, it can read more as well:

static int is_email(struct string_list *lines, FILE *fp)
{
    const char *header_regex = "^[!-9;-~]+:";
    regex_t regex;
    int ret = 1;
    size_t i;
    if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
        die("Invalid search pattern: %s", header_regex);
    for (i = 0; i < lines->nr || string_list_getline_crlf(lines, fp); i++) {
        const char *line = lines->items[i].string;
        if (!*line)
            break; /* End of header */
        /* Ignore indented folded lines */
        if (*line == '\t' || *line == ' ')
            continue;
        /* It's a header if it matches header_regex */
        if (regexec(&regex, line, 0, NULL, 0)) {
            ret = 0;
            goto done;
        }
    }
done:
    regfree(&regex);
    return ret;
}
Which solves the problem of opening the file 2 times. What do you think?

Regards, Paul

Previous: Johannes SchindelinNext: Paul Tan
Message 24 of 64 in “Make git-am a builtin”
  1. 00/31 Make git-am a builtinPaul Tan, Jun 18, 2015
  2. 01/31 wrapper: implement xopen()Paul Tan, Jun 18, 2015
  3. Johannes SchindelinJun 24, 2015
  4. Stefan BellerJun 24, 2015
  5. Johannes SchindelinJun 24, 2015
  6. Paul TanJul 1, 2015
  7. Paul TanJul 1, 2015
  8. 02/31 wrapper: implement xfopen()Paul Tan, Jun 18, 2015
  9. 03/31 am: implement skeletal builtin amPaul Tan, Jun 18, 2015
  10. Junio C HamanoJun 18, 2015
  11. Paul TanJun 19, 2015
  12. 04/31 am: implement patch queue mechanismPaul Tan, Jun 18, 2015
  13. Stefan BellerJun 18, 2015
  14. Junio C HamanoJun 18, 2015
  15. Paul TanJun 19, 2015
  16. Johannes SchindelinJun 24, 2015
  17. Paul TanJun 25, 2015
  18. 05/31 am: split out mbox/maildir patches with git-mailsplitPaul Tan, Jun 18, 2015
  19. Junio C HamanoJun 18, 2015
  20. 06/31 am: detect mbox patchesPaul Tan, Jun 18, 2015
  21. Junio C HamanoJun 18, 2015
  22. Paul TanJun 24, 2015
  23. Johannes SchindelinJun 24, 2015
  24. Paul TanJun 25, 2015
  25. Paul TanJun 26, 2015
  26. 07/31 am: extract patch, message and authorship with git-mailinfoPaul Tan, Jun 18, 2015
  27. Junio C HamanoJun 18, 2015
  28. Paul TanJun 19, 2015
  29. Junio C HamanoJun 19, 2015
  30. Paul TanJun 24, 2015
  31. Junio C HamanoJun 24, 2015
  32. Paul TanJun 25, 2015
  33. Johannes SchindelinJun 24, 2015
  34. Paul TanJun 26, 2015
  35. Junio C HamanoJun 26, 2015
  36. 08/31 am: apply patch with git-applyPaul Tan, Jun 18, 2015
  37. Junio C HamanoJun 18, 2015
  38. 09/31 am: commit applied patchPaul Tan, Jun 18, 2015
  39. 10/31 am: refresh the index at startPaul Tan, Jun 18, 2015
  40. Junio C HamanoJun 18, 2015
  41. Paul TanJun 19, 2015
  42. 11/31 am: refuse to apply patches if index is dirtyPaul Tan, Jun 18, 2015
  43. 12/31 am: implement --resolved/--continuePaul Tan, Jun 18, 2015
  44. 13/31 am: implement --skipPaul Tan, Jun 18, 2015
  45. 14/31 am: implement --abortPaul Tan, Jun 18, 2015
  46. 15/31 am: don't accept patches when there's a session in progressPaul Tan, Jun 18, 2015
  47. 16/31 am: implement quiet optionPaul Tan, Jun 18, 2015
  48. 17/31 am: exit with user friendly message on patch failurePaul Tan, Jun 18, 2015
  49. 18/31 am: implement am --signoffPaul Tan, Jun 18, 2015
  50. 19/31 cache-tree: introduce write_index_as_tree()Paul Tan, Jun 18, 2015
  51. 20/31 am: implement 3-way mergePaul Tan, Jun 18, 2015
  52. 21/31 am: --rebasingPaul Tan, Jun 18, 2015
  53. 22/31 am: don't use git-mailinfo if --rebasingPaul Tan, Jun 18, 2015
  54. 23/31 am: handle stray state directoryPaul Tan, Jun 18, 2015
  55. 24/31 am: implement -k/--keep, --keep-non-patchPaul Tan, Jun 18, 2015
  56. 25/31 am: implement --[no-]message-id, am.messageidPaul Tan, Jun 18, 2015
  57. 26/31 am: support --keep-cr, am.keepcrPaul Tan, Jun 18, 2015
  58. 27/31 am: implement --[no-]scissorsPaul Tan, Jun 18, 2015
  59. 28/31 am: pass git-apply's options to git-applyPaul Tan, Jun 18, 2015
  60. 29/31 am: implement --ignore-datePaul Tan, Jun 18, 2015
  61. 30/31 am: implement --committer-date-is-author-datePaul Tan, Jun 18, 2015
  62. 31/31 am: implement -S/--gpg-sign, commit.gpgsignPaul Tan, Jun 18, 2015
  63. Paul TanJun 19, 2015
  64. Johannes SchindelinJun 19, 2015

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.