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