On Sat, Mar 25, 2017 at 05:56:55PM +0100, René Scharfe wrote:

> Am 25.03.2017 um 17:17 schrieb Jeff King:
> > On Sat, Mar 25, 2017 at 01:16:42PM +0100, René Scharfe wrote:
> > > @@ -374,26 +372,9 @@ void log_write_email_headers(struct rev_info *opt, 
> > > struct commit *commit,
> > >           graph_show_oneline(opt->graph);
> > >   }
> > >   if (opt->mime_boundary) {
> > > -         static char subject_buffer[1024];
> > >           static char buffer[1024];
> > 
> > We still have this other buffer, which ends up in stat_sep. It should
> > probably get the same treatment, though I think the module boundaries
> > make it a little more awkward. We look at it in diff_flush(), which
> > otherwise doesn't need to know much about the pretty-printing.
> > 
> > Perhaps stat_sep should be a callback?
> 
> Yes, it would be nice to avoid it, but I haven't found a clean way, yet.  In
> diff.c, where it's used, we don't have commit and rev_info available (which
> we'd have to pass to a callback, or consume right there), and that's
> probably how it should be.  Perhaps preparing the filename in advance and
> passing that as a string together with mime_boundary and no_inline might be
> the way to go.

I think the no-allocation way with a callback would be something like
the patch at the end of this email. Having to stuff the commit into
rev_info is horrible, because rev_info shouldn't be carrying data
specific to individual commits. But it's really no different than what's
there now, which is stuffing a filled-out commit-specific buffer into
the same struct.

Doing it with a single allocated buffer would involve less callback
rigmarole, but it doesn't change the fact that we are stuffing
commit-specific data into the rev_info (via the diff_options member).
And then you add on top the question of who is supposed to free it
(which is punted on in the original by using a static; yech).

The most correct way is that the caller of log_write_email_headers() and
diff_flush() should have a function-local strbuf which holds the data,
gets passed to diff_flush() as some kind opaque context, and then is
freed afterwards. We don't have such a context, but if we were to abuse
diff_options.stat_sep _temporarily_, that would still be a lot cleaner.
I.e., something like this:

  struct strbuf stat_sep = STRBUF_INIT;

  /* may write into stat_sep, depending on options */
  log_write_email_headers(..., &stat_sep);
  opt->diffopt.stat_sep = stat_sep.buf;

  diff_flush(&opt->diffopt);
  opt->diffopt.stat_sep = NULL;
  strbuf_release(&stat_sep);

But it's a bit tricky because those two hunks happen in separate
functions, which means passing the strbuf around.

> > > + if (pp->print_email_subject && pp->rev && pp->rev->mime_boundary) {
> > > +         strbuf_addf(sb,
> > > +                     "MIME-Version: 1.0\n"
> > 
> > In the original, this would have been in "after_subject". Which means we
> > would print it even if print_email_subject is not true. Why do we need
> > to check it in the new conditional?
> 
> No, we only would have printed it if log_write_email_headers() was called to
> append it to the static buffer.  print_email_subject is only set when we
> call log_write_email_headers(), so checking it makes sure that we get the
> same behavior as before.

OK. So your logic is "you would always set print_email_subject alongside
log_write_email_headers()", and mine is "you would always call
log_write_email_headers() when opt->mime_boundary is set". Both are true
today, and I do not see a big chance of them becoming untrue.

So I'm OK with it either way.

> > Not that I expect the behavior to be wrong either way; why would we have
> > a mime boundary without setting print_email_subject? But I would think
> > that "do we have a mime boundary" would be the right conditional to
> > trigger printing it.
> 
> FWIW, the test suite still passes with the print_email_subject check
> removed.  And currently only cmd_format_patch() sets mime_boundary, so we
> don't need the check indeed.

Yeah, while working on the other patch I looked at all the callers
(there are only 2), and I think it's fine. I thought at first there
might be problems with:

  git format-patch --attach --format=not-email:%s

but we skip this code when it's a non-email format (and anyway,
format-patch always sets print_email_subject whether the format is email
or not).  That command _does_ generate nonsense, because
cmd_format_patch() insists on showing the trailing mime boundary even if
the format is not email. I'm not sure there's a good reason to use a
non-email format with format-patch in the first place, let alone with
--attach. Arguably it should bail as soon as it sees the incompatible
options.

Anyway. Here's my attempt at the callback version of stat_sep.

---
diff --git a/diff.c b/diff.c
index a628ac3a9..d061f9e18 100644
--- a/diff.c
+++ b/diff.c
@@ -4819,10 +4819,9 @@ void diff_flush(struct diff_options *options)
                        fprintf(options->file, "%s%c",
                                diff_line_prefix(options),
                                options->line_termination);
-                       if (options->stat_sep) {
-                               /* attach patch instead of inline */
-                               fputs(options->stat_sep, options->file);
-                       }
+                       if (options->stat_sep)
+                               options->stat_sep(options->file,
+                                                 options->stat_sep_data);
                }
 
                for (i = 0; i < q->nr; i++) {
diff --git a/diff.h b/diff.h
index e9ccb38c2..4785f3b23 100644
--- a/diff.h
+++ b/diff.h
@@ -154,9 +154,11 @@ struct diff_options {
        unsigned ws_error_highlight;
        const char *prefix;
        int prefix_length;
-       const char *stat_sep;
        long xdl_opts;
 
+       void (*stat_sep)(FILE *, void *);
+       void *stat_sep_data;
+
        int stat_width;
        int stat_name_width;
        int stat_graph_width;
diff --git a/log-tree.c b/log-tree.c
index 7049a1778..5cf825c41 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -348,6 +348,31 @@ void fmt_output_email_subject(struct strbuf *sb, struct 
rev_info *opt)
        }
 }
 
+static void show_mime_attachment(FILE *out, void *data)
+{
+       struct rev_info *opt = data;
+       struct strbuf filename = STRBUF_INIT;
+
+       if (opt->numbered_files)
+               strbuf_addf(&filename, "%d", opt->nr);
+       else
+               fmt_output_commit(&filename, opt->commit_for_mime, opt);
+
+       fprintf(out,
+               "\n--%s%s\n"
+               "Content-Type: text/x-patch;"
+               " name=\"%s\"\n"
+               "Content-Transfer-Encoding: 8bit\n"
+               "Content-Disposition: %s;"
+               " filename=\"%s\"\n\n",
+               mime_boundary_leader, opt->mime_boundary,
+               filename.buf,
+               opt->no_inline ? "attachment" : "inline",
+               filename.buf);
+
+       strbuf_release(&filename);
+}
+
 void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             int *need_8bit_cte_p)
 {
@@ -372,27 +397,10 @@ void log_write_email_headers(struct rev_info *opt, struct 
commit *commit,
                graph_show_oneline(opt->graph);
        }
        if (opt->mime_boundary) {
-               static char buffer[1024];
-               struct strbuf filename =  STRBUF_INIT;
                *need_8bit_cte_p = -1; /* NEVER */
-
-               if (opt->numbered_files)
-                       strbuf_addf(&filename, "%d", opt->nr);
-               else
-                       fmt_output_commit(&filename, commit, opt);
-               snprintf(buffer, sizeof(buffer) - 1,
-                        "\n--%s%s\n"
-                        "Content-Type: text/x-patch;"
-                        " name=\"%s\"\n"
-                        "Content-Transfer-Encoding: 8bit\n"
-                        "Content-Disposition: %s;"
-                        " filename=\"%s\"\n\n",
-                        mime_boundary_leader, opt->mime_boundary,
-                        filename.buf,
-                        opt->no_inline ? "attachment" : "inline",
-                        filename.buf);
-               opt->diffopt.stat_sep = buffer;
-               strbuf_release(&filename);
+               opt->diffopt.stat_sep = show_mime_attachment;
+               opt->diffopt.stat_sep_data = opt;
+               opt->commit_for_mime = commit;
        }
 }
 
diff --git a/revision.h b/revision.h
index 14886ec92..46ca45d96 100644
--- a/revision.h
+++ b/revision.h
@@ -156,6 +156,7 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
+       struct commit *commit_for_mime;
        const char      *patch_suffix;
        int             numbered_files;
        int             reroll_count;

Reply via email to