Skip to content

Commit 75d4676

Browse files
committed
email: introduce git_email_create_from_commit
Create `git_email_*` which will encapsulate email creation and application, and `git_email_create_from_commit` in particular, which creates an email for a single commit.
1 parent aa993f7 commit 75d4676

File tree

4 files changed

+481
-0
lines changed

4 files changed

+481
-0
lines changed

include/git2.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "git2/deprecated.h"
2727
#include "git2/describe.h"
2828
#include "git2/diff.h"
29+
#include "git2/email.h"
2930
#include "git2/errors.h"
3031
#include "git2/filter.h"
3132
#include "git2/global.h"

include/git2/email.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
#ifndef INCLUDE_git_email_h__
8+
#define INCLUDE_git_email_h__
9+
10+
#include "common.h"
11+
12+
/**
13+
* @file git2/email.h
14+
* @brief Git email formatting and application routines.
15+
* @ingroup Git
16+
* @{
17+
*/
18+
GIT_BEGIN_DECL
19+
20+
/**
21+
* Formatting options for diff e-mail generation
22+
*/
23+
typedef enum {
24+
/** Normal patch, the default */
25+
GIT_EMAIL_CREATE_DEFAULT = 0,
26+
27+
/** Do not include patch numbers in the subject prefix. */
28+
GIT_EMAIL_CREATE_OMIT_NUMBERS = (1u << 0),
29+
30+
/**
31+
* Include numbers in the subject prefix even when the
32+
* patch is for a single commit (1/1).
33+
*/
34+
GIT_EMAIL_CREATE_ALWAYS_NUMBER = (1u << 1),
35+
} git_email_create_flags_t;
36+
37+
/**
38+
* Options for controlling the formatting of the generated e-mail.
39+
*/
40+
typedef struct {
41+
unsigned int version;
42+
43+
/** see `git_email_create_flags_t` above */
44+
uint32_t flags;
45+
46+
/** Options to use when creating diffs */
47+
git_diff_options diff_opts;
48+
49+
/**
50+
* The subject prefix, by default "PATCH". If set to an empty
51+
* string ("") then only the patch numbers will be shown in the
52+
* prefix. If the subject_prefix is empty and patch numbers
53+
* are not being shown, the prefix will be omitted entirely.
54+
*/
55+
const char *subject_prefix;
56+
57+
/**
58+
* The starting patch number; this cannot be 0. By default,
59+
* this is 1.
60+
*/
61+
size_t start_number;
62+
63+
/** The "re-roll" number. By default, there is no re-roll. */
64+
size_t reroll_number;
65+
} git_email_create_options;
66+
67+
#define GIT_EMAIL_CREATE_OPTIONS_VERSION 1
68+
#define GIT_EMAIL_CREATE_OPTIONS_INIT { \
69+
GIT_EMAIL_CREATE_OPTIONS_VERSION, \
70+
GIT_EMAIL_CREATE_DEFAULT, \
71+
GIT_DIFF_OPTIONS_INIT \
72+
}
73+
74+
/**
75+
* Create a diff for a commit in mbox format for sending via email.
76+
* The commit must not be a merge commit.
77+
*
78+
* @param out buffer to store the e-mail patch in
79+
* @param commit commit to create a patch for
80+
* @param opts email creation options
81+
*/
82+
GIT_EXTERN(int) git_email_create_from_commit(
83+
git_buf *out,
84+
git_commit *commit,
85+
const git_email_create_options *opts);
86+
87+
/**
88+
* Create an mbox format diff for the given commits in the revision
89+
* spec, excluding merge commits.
90+
*
91+
* @param out buffer to store the e-mail patches in
92+
* @param commits the array of commits to create patches for
93+
* @param len the length of the `commits` array
94+
* @param opts email creation options
95+
*/
96+
GIT_EXTERN(int) git_email_create_from_commits(
97+
git_strarray *out,
98+
git_commit **commits,
99+
size_t len,
100+
const git_email_create_options *opts);
101+
102+
GIT_END_DECL
103+
104+
/** @} */
105+
106+
#endif

src/email.c

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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 "buffer.h"
9+
#include "common.h"
10+
#include "diff_generate.h"
11+
12+
#include "git2/email.h"
13+
#include "git2/patch.h"
14+
#include "git2/version.h"
15+
16+
/*
17+
* Git uses a "magic" timestamp to indicate that an email message
18+
* is from `git format-patch` (or our equivalent).
19+
*/
20+
#define EMAIL_TIMESTAMP "Mon Sep 17 00:00:00 2001"
21+
22+
GIT_INLINE(int) include_prefix(
23+
size_t patch_count,
24+
git_email_create_options *opts)
25+
{
26+
return ((!opts->subject_prefix || *opts->subject_prefix) ||
27+
(opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
28+
opts->reroll_number ||
29+
(patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS)));
30+
}
31+
32+
static int append_prefix(
33+
git_buf *out,
34+
size_t patch_idx,
35+
size_t patch_count,
36+
git_email_create_options *opts)
37+
{
38+
const char *subject_prefix = opts->subject_prefix ?
39+
opts->subject_prefix : "PATCH";
40+
41+
if (!include_prefix(patch_count, opts))
42+
return 0;
43+
44+
git_buf_putc(out, '[');
45+
46+
if (*subject_prefix)
47+
git_buf_puts(out, subject_prefix);
48+
49+
if (opts->reroll_number) {
50+
if (*subject_prefix)
51+
git_buf_putc(out, ' ');
52+
53+
git_buf_printf(out, "v%" PRIuZ, opts->reroll_number);
54+
}
55+
56+
if ((opts->flags & GIT_EMAIL_CREATE_ALWAYS_NUMBER) != 0 ||
57+
(patch_count > 1 && !(opts->flags & GIT_EMAIL_CREATE_OMIT_NUMBERS))) {
58+
size_t start_number = opts->start_number ?
59+
opts->start_number : 1;
60+
61+
if (*subject_prefix || opts->reroll_number)
62+
git_buf_putc(out, ' ');
63+
64+
git_buf_printf(out, "%" PRIuZ "/%" PRIuZ,
65+
patch_idx + (start_number - 1),
66+
patch_count + (start_number - 1));
67+
}
68+
69+
git_buf_puts(out, "] ");
70+
71+
return git_buf_oom(out) ? -1 : 0;
72+
}
73+
74+
static int append_subject(
75+
git_buf *out,
76+
git_commit *commit,
77+
size_t patch_idx,
78+
size_t patch_count,
79+
git_email_create_options *opts)
80+
{
81+
int error;
82+
83+
if ((error = git_buf_puts(out, "Subject: ")) < 0 ||
84+
(error = append_prefix(out, patch_idx, patch_count, opts)) < 0 ||
85+
(error = git_buf_puts(out, git_commit_summary(commit))) < 0 ||
86+
(error = git_buf_putc(out, '\n')) < 0)
87+
return error;
88+
89+
return 0;
90+
}
91+
92+
static int append_header(
93+
git_buf *out,
94+
git_commit *commit,
95+
size_t patch_idx,
96+
size_t patch_count,
97+
git_email_create_options *opts)
98+
{
99+
const git_signature *author = git_commit_author(commit);
100+
char id[GIT_OID_HEXSZ];
101+
char date[GIT_DATE_RFC2822_SZ];
102+
int error;
103+
104+
if ((error = git_oid_fmt(id, git_commit_id(commit))) < 0 ||
105+
(error = git_buf_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 ||
106+
(error = git_buf_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 ||
107+
(error = git__date_rfc2822_fmt(date, sizeof(date), &author->when)) < 0 ||
108+
(error = git_buf_printf(out, "Date: %s\n", date)) < 0 ||
109+
(error = append_subject(out, commit, patch_idx, patch_count, opts)) < 0)
110+
return error;
111+
112+
if ((error = git_buf_putc(out, '\n')) < 0)
113+
return error;
114+
115+
return 0;
116+
}
117+
118+
static int append_body(git_buf *out, git_commit *commit)
119+
{
120+
const char *body = git_commit_body(commit);
121+
size_t body_len;
122+
int error;
123+
124+
if (!body)
125+
return 0;
126+
127+
body_len = strlen(body);
128+
129+
if ((error = git_buf_puts(out, body)) < 0)
130+
return error;
131+
132+
if (body_len && body[body_len - 1] != '\n')
133+
error = git_buf_putc(out, '\n');
134+
135+
return error;
136+
}
137+
138+
static int append_diffstat(git_buf *out, git_diff *diff)
139+
{
140+
git_diff_stats *stats = NULL;
141+
unsigned int format_flags;
142+
int error;
143+
144+
format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
145+
146+
if ((error = git_diff_get_stats(&stats, diff)) == 0 &&
147+
(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) == 0)
148+
error = git_buf_putc(out, '\n');
149+
150+
git_diff_stats_free(stats);
151+
return error;
152+
}
153+
154+
static int append_patches(git_buf *out, git_diff *diff)
155+
{
156+
size_t i, deltas;
157+
int error = 0;
158+
159+
deltas = git_diff_num_deltas(diff);
160+
161+
for (i = 0; i < deltas; ++i) {
162+
git_patch *patch = NULL;
163+
164+
if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
165+
error = git_patch_to_buf(out, patch);
166+
167+
git_patch_free(patch);
168+
169+
if (error < 0)
170+
break;
171+
}
172+
173+
return error;
174+
}
175+
176+
int git_email_create_from_commit(
177+
git_buf *out,
178+
git_commit *commit,
179+
const git_email_create_options *given_opts)
180+
{
181+
git_diff *diff = NULL;
182+
git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
183+
git_repository *repo;
184+
int error = 0;
185+
186+
GIT_ASSERT_ARG(out);
187+
GIT_ASSERT_ARG(commit);
188+
189+
GIT_ERROR_CHECK_VERSION(given_opts,
190+
GIT_EMAIL_CREATE_OPTIONS_VERSION,
191+
"git_email_create_options");
192+
193+
if (given_opts)
194+
memcpy(&opts, given_opts, sizeof(git_email_create_options));
195+
196+
git_buf_sanitize(out);
197+
git_buf_clear(out);
198+
199+
repo = git_commit_owner(commit);
200+
201+
if ((error = git_diff__commit(&diff, repo, commit, &opts.diff_opts)) == 0 &&
202+
(error = append_header(out, commit, 1, 1, &opts)) == 0 &&
203+
(error = append_body(out, commit)) == 0 &&
204+
(error = git_buf_puts(out, "---\n")) == 0 &&
205+
(error = append_diffstat(out, diff)) == 0 &&
206+
(error = append_patches(out, diff)) == 0)
207+
error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
208+
209+
git_diff_free(diff);
210+
return error;
211+
}

0 commit comments

Comments
 (0)