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