email.c 7.3 KB
Newer Older
1 2 3 4 5 6 7
/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

8 9
#include "email.h"

10
#include "common.h"
11
#include "buf.h"
12
#include "diff_generate.h"
13 14
#include "diff_stats.h"
#include "patch.h"
15
#include "date.h"
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37

#include "git2/email.h"
#include "git2/patch.h"
#include "git2/version.h"

/*
 * Git uses a "magic" timestamp to indicate that an email message
 * is from `git format-patch` (or our equivalent).
 */
#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001"

GIT_INLINE(int) include_prefix(
	size_t patch_count,
	git_email_create_options *opts)
{
	return ((!opts->subject_prefix || *opts->subject_prefix) ||
	        (opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
	        opts->reroll_number ||
		(patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS)));
}

static int append_prefix(
38
	git_str *out,
39 40 41 42 43 44 45
	size_t patch_idx,
	size_t patch_count,
	git_email_create_options *opts)
{
	const char *subject_prefix = opts->subject_prefix ?
		opts->subject_prefix : "PATCH";

46
	git_str_putc(out, '[');
47 48

	if (*subject_prefix)
49
		git_str_puts(out, subject_prefix);
50 51 52

	if (opts->reroll_number) {
		if (*subject_prefix)
53
			git_str_putc(out, ' ');
54

55
		git_str_printf(out, "v%" PRIuZ, opts->reroll_number);
56 57 58 59 60 61 62 63
	}

	if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
	    (patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) {
		size_t start_number = opts->start_number ?
			opts->start_number : 1;

		if (*subject_prefix || opts->reroll_number)
64
			git_str_putc(out, ' ');
65

66
		git_str_printf(out, "%" PRIuZ "/%" PRIuZ,
67 68 69 70
		               patch_idx + (start_number - 1),
		               patch_count + (start_number - 1));
	}

71
	git_str_puts(out, "]");
72

73
	return git_str_oom(out) ? -1 : 0;
74 75
}

76 77 78 79 80 81 82 83 84 85 86 87 88
static int append_date(
	git_str *out,
	const git_time *date)
{
	int error;

	if ((error = git_str_printf(out, "Date: ")) == 0 &&
	    (error = git_date_rfc2822_fmt(out, date->time, date->offset)) == 0)
	    error = git_str_putc(out, '\n');

	return error;
}

89
static int append_subject(
90
	git_str *out,
91 92
	size_t patch_idx,
	size_t patch_count,
93
	const char *summary,
94 95
	git_email_create_options *opts)
{
96 97
	bool prefix = include_prefix(patch_count, opts);
	size_t summary_len = summary ? strlen(summary) : 0;
98 99
	int error;

100 101 102 103 104 105 106
	if (summary_len) {
		const char *nl = strchr(summary, '\n');

		if (nl)
			summary_len = (nl - summary);
	}

107
	if ((error = git_str_puts(out, "Subject: ")) < 0)
108 109
		return error;

110 111 112 113
	if (prefix &&
	    (error = append_prefix(out, patch_idx, patch_count, opts)) < 0)
		return error;

114
	if (prefix && summary_len && (error = git_str_putc(out, ' ')) < 0)
115 116 117
		return error;

	if (summary_len &&
118
	    (error = git_str_put(out, summary, summary_len)) < 0)
119 120
		return error;

121
	return git_str_putc(out, '\n');
122 123 124
}

static int append_header(
125
	git_str *out,
126 127
	size_t patch_idx,
	size_t patch_count,
128 129 130
	const git_oid *commit_id,
	const char *summary,
	const git_signature *author,
131 132 133 134 135
	git_email_create_options *opts)
{
	char id[GIT_OID_HEXSZ];
	int error;

136
	if ((error = git_oid_fmt(id, commit_id)) < 0 ||
137 138
	    (error = git_str_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 ||
	    (error = git_str_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 ||
139
	    (error = append_date(out, &author->when)) < 0 ||
140
	    (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0)
141 142
		return error;

143
	if ((error = git_str_putc(out, '\n')) < 0)
144 145 146 147 148
		return error;

	return 0;
}

149
static int append_body(git_str *out, const char *body)
150 151 152 153 154 155 156 157 158
{
	size_t body_len;
	int error;

	if (!body)
		return 0;

	body_len = strlen(body);

159
	if ((error = git_str_puts(out, body)) < 0)
160 161 162
		return error;

	if (body_len && body[body_len - 1] != '\n')
163
		error = git_str_putc(out, '\n');
164 165 166 167

	return error;
}

168
static int append_diffstat(git_str *out, git_diff *diff)
169 170 171 172 173 174 175 176
{
	git_diff_stats *stats = NULL;
	unsigned int format_flags;
	int error;

	format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;

	if ((error = git_diff_get_stats(&stats, diff)) == 0 &&
177 178
	    (error = git_diff__stats_to_buf(out, stats, format_flags, 0)) == 0)
		error = git_str_putc(out, '\n');
179 180 181 182 183

	git_diff_stats_free(stats);
	return error;
}

184
static int append_patches(git_str *out, git_diff *diff)
185 186 187 188 189 190 191 192 193 194
{
	size_t i, deltas;
	int error = 0;

	deltas = git_diff_num_deltas(diff);

	for (i = 0; i < deltas; ++i) {
		git_patch *patch = NULL;

		if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
195
			error = git_patch__to_buf(out, patch);
196 197 198 199 200 201 202 203 204 205

		git_patch_free(patch);

		if (error < 0)
			break;
	}

	return error;
}

206
int git_email__append_from_diff(
207
	git_str *out,
208 209 210 211 212 213 214
	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,
215 216 217
	const git_email_create_options *given_opts)
{
	git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
218
	int error;
219 220

	GIT_ASSERT_ARG(out);
221 222 223 224
	GIT_ASSERT_ARG(diff);
	GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count);
	GIT_ASSERT_ARG(commit_id);
	GIT_ASSERT_ARG(author);
225 226 227 228 229 230 231 232

	GIT_ERROR_CHECK_VERSION(given_opts,
		GIT_EMAIL_CREATE_OPTIONS_VERSION,
		"git_email_create_options");

	if (given_opts)
		memcpy(&opts, given_opts, sizeof(git_email_create_options));

233 234
	if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 &&
	    (error = append_body(out, body)) == 0 &&
235
	    (error = git_str_puts(out, "---\n")) == 0 &&
236 237
	    (error = append_diffstat(out, diff)) == 0 &&
	    (error = append_patches(out, diff)) == 0)
238
		error = git_str_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
239

240 241 242
	return error;
}

243 244 245 246 247 248 249 250 251 252 253
int 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)
{
254
	git_str email = GIT_STR_INIT;
255 256
	int error;

257
	git_buf_tostr(&email, out);
258

259
	error = git_email__append_from_diff(&email, diff, patch_idx,
260 261 262
		patch_count, commit_id, summary, body, author,
		given_opts);

263 264 265 266
	if (error == 0)
		error = git_buf_fromstr(out, &email);

	git_str_dispose(&email);
267 268 269
	return error;
}

270 271 272
int git_email_create_from_commit(
	git_buf *out,
	git_commit *commit,
273
	const git_email_create_options *given_opts)
274
{
275
	git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
276 277
	git_diff *diff = NULL;
	git_repository *repo;
278 279
	git_diff_options *diff_opts;
	git_diff_find_options *find_opts;
280 281 282 283 284 285 286 287
	const git_signature *author;
	const char *summary, *body;
	const git_oid *commit_id;
	int error = -1;

	GIT_ASSERT_ARG(out);
	GIT_ASSERT_ARG(commit);

288
	GIT_ERROR_CHECK_VERSION(given_opts,
289 290 291
		GIT_EMAIL_CREATE_OPTIONS_VERSION,
		"git_email_create_options");

292 293 294
	if (given_opts)
		memcpy(&opts, given_opts, sizeof(git_email_create_options));

295 296 297 298 299
	repo = git_commit_owner(commit);
	author = git_commit_author(commit);
	summary = git_commit_summary(commit);
	body = git_commit_body(commit);
	commit_id = git_commit_id(commit);
300
	diff_opts = &opts.diff_opts;
301
	find_opts = &opts.diff_find_opts;
302 303 304 305

	if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
		goto done;

306 307 308 309
	if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 &&
	    (error = git_diff_find_similar(diff, find_opts)) < 0)
		goto done;

310
	error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts);
311 312

done:
313 314 315
	git_diff_free(diff);
	return error;
}