1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "email.h"
9
10 #include "buffer.h"
11 #include "common.h"
12 #include "diff_generate.h"
13
14 #include "git2/email.h"
15 #include "git2/patch.h"
16 #include "git2/version.h"
17
18 /*
19 * Git uses a "magic" timestamp to indicate that an email message
20 * is from `git format-patch` (or our equivalent).
21 */
22 #define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001"
23
include_prefix(size_t patch_count,git_email_create_options * opts)24 GIT_INLINE(int) include_prefix(
25 size_t patch_count,
26 git_email_create_options *opts)
27 {
28 return ((!opts->subject_prefix || *opts->subject_prefix) ||
29 (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
30 opts->reroll_number ||
31 (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS)));
32 }
33
append_prefix(git_buf * out,size_t patch_idx,size_t patch_count,git_email_create_options * opts)34 static int append_prefix(
35 git_buf *out,
36 size_t patch_idx,
37 size_t patch_count,
38 git_email_create_options *opts)
39 {
40 const char *subject_prefix = opts->subject_prefix ?
41 opts->subject_prefix : "PATCH";
42
43 git_buf_putc(out, '[');
44
45 if (*subject_prefix)
46 git_buf_puts(out, subject_prefix);
47
48 if (opts->reroll_number) {
49 if (*subject_prefix)
50 git_buf_putc(out, ' ');
51
52 git_buf_printf(out, "v%" PRIuZ, opts->reroll_number);
53 }
54
55 if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
56 (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) {
57 size_t start_number = opts->start_number ?
58 opts->start_number : 1;
59
60 if (*subject_prefix || opts->reroll_number)
61 git_buf_putc(out, ' ');
62
63 git_buf_printf(out, "%" PRIuZ "/%" PRIuZ,
64 patch_idx + (start_number - 1),
65 patch_count + (start_number - 1));
66 }
67
68 git_buf_puts(out, "]");
69
70 return git_buf_oom(out) ? -1 : 0;
71 }
72
append_subject(git_buf * out,size_t patch_idx,size_t patch_count,const char * summary,git_email_create_options * opts)73 static int append_subject(
74 git_buf *out,
75 size_t patch_idx,
76 size_t patch_count,
77 const char *summary,
78 git_email_create_options *opts)
79 {
80 bool prefix = include_prefix(patch_count, opts);
81 size_t summary_len = summary ? strlen(summary) : 0;
82 int error;
83
84 if (summary_len) {
85 const char *nl = strchr(summary, '\n');
86
87 if (nl)
88 summary_len = (nl - summary);
89 }
90
91 if ((error = git_buf_puts(out, "Subject: ")) < 0)
92 return error;
93
94 if (prefix &&
95 (error = append_prefix(out, patch_idx, patch_count, opts)) < 0)
96 return error;
97
98 if (prefix && summary_len && (error = git_buf_putc(out, ' ')) < 0)
99 return error;
100
101 if (summary_len &&
102 (error = git_buf_put(out, summary, summary_len)) < 0)
103 return error;
104
105 return git_buf_putc(out, '\n');
106 }
107
append_header(git_buf * out,size_t patch_idx,size_t patch_count,const git_oid * commit_id,const char * summary,const git_signature * author,git_email_create_options * opts)108 static int append_header(
109 git_buf *out,
110 size_t patch_idx,
111 size_t patch_count,
112 const git_oid *commit_id,
113 const char *summary,
114 const git_signature *author,
115 git_email_create_options *opts)
116 {
117 char id[GIT_OID_HEXSZ];
118 char date[GIT_DATE_RFC2822_SZ];
119 int error;
120
121 if ((error = git_oid_fmt(id, commit_id)) < 0 ||
122 (error = git_buf_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 ||
123 (error = git_buf_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 ||
124 (error = git__date_rfc2822_fmt(date, sizeof(date), &author->when)) < 0 ||
125 (error = git_buf_printf(out, "Date: %s\n", date)) < 0 ||
126 (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0)
127 return error;
128
129 if ((error = git_buf_putc(out, '\n')) < 0)
130 return error;
131
132 return 0;
133 }
134
append_body(git_buf * out,const char * body)135 static int append_body(git_buf *out, const char *body)
136 {
137 size_t body_len;
138 int error;
139
140 if (!body)
141 return 0;
142
143 body_len = strlen(body);
144
145 if ((error = git_buf_puts(out, body)) < 0)
146 return error;
147
148 if (body_len && body[body_len - 1] != '\n')
149 error = git_buf_putc(out, '\n');
150
151 return error;
152 }
153
append_diffstat(git_buf * out,git_diff * diff)154 static int append_diffstat(git_buf *out, git_diff *diff)
155 {
156 git_diff_stats *stats = NULL;
157 unsigned int format_flags;
158 int error;
159
160 format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
161
162 if ((error = git_diff_get_stats(&stats, diff)) == 0 &&
163 (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) == 0)
164 error = git_buf_putc(out, '\n');
165
166 git_diff_stats_free(stats);
167 return error;
168 }
169
append_patches(git_buf * out,git_diff * diff)170 static int append_patches(git_buf *out, git_diff *diff)
171 {
172 size_t i, deltas;
173 int error = 0;
174
175 deltas = git_diff_num_deltas(diff);
176
177 for (i = 0; i < deltas; ++i) {
178 git_patch *patch = NULL;
179
180 if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
181 error = git_patch_to_buf(out, patch);
182
183 git_patch_free(patch);
184
185 if (error < 0)
186 break;
187 }
188
189 return error;
190 }
191
git_email__append_from_diff(git_buf * out,git_diff * diff,size_t patch_idx,size_t patch_count,const git_oid * commit_id,const char * summary,const char * body,const git_signature * author,const git_email_create_options * given_opts)192 int git_email__append_from_diff(
193 git_buf *out,
194 git_diff *diff,
195 size_t patch_idx,
196 size_t patch_count,
197 const git_oid *commit_id,
198 const char *summary,
199 const char *body,
200 const git_signature *author,
201 const git_email_create_options *given_opts)
202 {
203 git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
204 int error;
205
206 GIT_ASSERT_ARG(out);
207 GIT_ASSERT_ARG(diff);
208 GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count);
209 GIT_ASSERT_ARG(commit_id);
210 GIT_ASSERT_ARG(author);
211
212 GIT_ERROR_CHECK_VERSION(given_opts,
213 GIT_EMAIL_CREATE_OPTIONS_VERSION,
214 "git_email_create_options");
215
216 if (given_opts)
217 memcpy(&opts, given_opts, sizeof(git_email_create_options));
218
219 git_buf_sanitize(out);
220
221 if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 &&
222 (error = append_body(out, body)) == 0 &&
223 (error = git_buf_puts(out, "---\n")) == 0 &&
224 (error = append_diffstat(out, diff)) == 0 &&
225 (error = append_patches(out, diff)) == 0)
226 error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
227
228 return error;
229 }
230
git_email_create_from_diff(git_buf * out,git_diff * diff,size_t patch_idx,size_t patch_count,const git_oid * commit_id,const char * summary,const char * body,const git_signature * author,const git_email_create_options * given_opts)231 int git_email_create_from_diff(
232 git_buf *out,
233 git_diff *diff,
234 size_t patch_idx,
235 size_t patch_count,
236 const git_oid *commit_id,
237 const char *summary,
238 const char *body,
239 const git_signature *author,
240 const git_email_create_options *given_opts)
241 {
242 int error;
243
244 git_buf_sanitize(out);
245 git_buf_clear(out);
246
247 error = git_email__append_from_diff(out, diff, patch_idx,
248 patch_count, commit_id, summary, body, author,
249 given_opts);
250
251 return error;
252 }
253
git_email_create_from_commit(git_buf * out,git_commit * commit,const git_email_create_options * given_opts)254 int git_email_create_from_commit(
255 git_buf *out,
256 git_commit *commit,
257 const git_email_create_options *given_opts)
258 {
259 git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
260 git_diff *diff = NULL;
261 git_repository *repo;
262 git_diff_options *diff_opts;
263 git_diff_find_options *find_opts;
264 const git_signature *author;
265 const char *summary, *body;
266 const git_oid *commit_id;
267 int error = -1;
268
269 GIT_ASSERT_ARG(out);
270 GIT_ASSERT_ARG(commit);
271
272 GIT_ERROR_CHECK_VERSION(given_opts,
273 GIT_EMAIL_CREATE_OPTIONS_VERSION,
274 "git_email_create_options");
275
276 if (given_opts)
277 memcpy(&opts, given_opts, sizeof(git_email_create_options));
278
279 repo = git_commit_owner(commit);
280 author = git_commit_author(commit);
281 summary = git_commit_summary(commit);
282 body = git_commit_body(commit);
283 commit_id = git_commit_id(commit);
284 diff_opts = &opts.diff_opts;
285 find_opts = &opts.diff_find_opts;
286
287 if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
288 goto done;
289
290 if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 &&
291 (error = git_diff_find_similar(diff, find_opts)) < 0)
292 goto done;
293
294 error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts);
295
296 done:
297 git_diff_free(diff);
298 return error;
299 }
300