summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2021-09-20 21:45:10 -0400
committerGitHub <noreply@github.com>2021-09-20 21:45:10 -0400
commitba01547d4a8190d5e4df317d4b0d0767e160bc57 (patch)
treee2d4d5b54a684f0f5e3b3ae33cdcb3b5e0089cd1 /src
parent0a8728622ecf77ce4185139761b31f0c6f77f04e (diff)
parentba3595af0f3f788427868abb0b499da9f82dc9d1 (diff)
downloadlibgit2-ba01547d4a8190d5e4df317d4b0d0767e160bc57.tar.gz
Merge pull request #6061 from libgit2/ethomson/email
Introduce `git_email_create`; deprecate `git_diff_format_email`
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c7
-rw-r--r--src/buffer.h1
-rw-r--r--src/diff.c214
-rw-r--r--src/email.c300
-rw-r--r--src/email.h25
5 files changed, 374 insertions, 173 deletions
diff --git a/src/buffer.c b/src/buffer.c
index ab2a6139a..a57df1284 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -600,6 +600,13 @@ void git_buf_shorten(git_buf *buf, size_t amount)
git_buf_clear(buf);
}
+void git_buf_truncate_at_char(git_buf *buf, char separator)
+{
+ ssize_t idx = git_buf_find(buf, separator);
+ if (idx >= 0)
+ git_buf_truncate(buf, (size_t)idx);
+}
+
void git_buf_rtruncate_at_char(git_buf *buf, char separator)
{
ssize_t idx = git_buf_rfind_next(buf, separator);
diff --git a/src/buffer.h b/src/buffer.h
index 7faa9fdc8..a356bebea 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -171,6 +171,7 @@ void git_buf_consume_bytes(git_buf *buf, size_t len);
void git_buf_consume(git_buf *buf, const char *end);
void git_buf_truncate(git_buf *buf, size_t len);
void git_buf_shorten(git_buf *buf, size_t amount);
+void git_buf_truncate_at_char(git_buf *buf, char separator);
void git_buf_rtruncate_at_char(git_buf *path, char separator);
/** General join with separator */
diff --git a/src/diff.c b/src/diff.c
index 4085c5f78..30b9f647a 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -7,11 +7,15 @@
#include "diff.h"
-#include "git2/version.h"
-#include "diff_generate.h"
+#include "common.h"
#include "patch.h"
+#include "email.h"
#include "commit.h"
#include "index.h"
+#include "diff_generate.h"
+
+#include "git2/version.h"
+#include "git2/email.h"
struct patch_id_args {
git_hash_ctx ctx;
@@ -150,97 +154,14 @@ int git_diff_foreach(
return error;
}
-static int diff_format_email_append_header_tobuf(
- git_buf *out,
- const git_oid *id,
- const git_signature *author,
- const char *summary,
- const char *body,
- size_t patch_no,
- size_t total_patches,
- bool exclude_patchno_marker)
-{
- char idstr[GIT_OID_HEXSZ + 1];
- char date_str[GIT_DATE_RFC2822_SZ];
- int error = 0;
-
- git_oid_fmt(idstr, id);
- idstr[GIT_OID_HEXSZ] = '\0';
-
- if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
- &author->when)) < 0)
- return error;
-
- error = git_buf_printf(out,
- "From %s Mon Sep 17 00:00:00 2001\n" \
- "From: %s <%s>\n" \
- "Date: %s\n" \
- "Subject: ",
- idstr,
- author->name, author->email,
- date_str);
-
- if (error < 0)
- return error;
-
- if (!exclude_patchno_marker) {
- if (total_patches == 1) {
- error = git_buf_puts(out, "[PATCH] ");
- } else {
- error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
- patch_no, total_patches);
- }
-
- if (error < 0)
- return error;
- }
-
- error = git_buf_printf(out, "%s\n\n", summary);
-
- if (body) {
- git_buf_puts(out, body);
-
- if (out->ptr[out->size - 1] != '\n')
- git_buf_putc(out, '\n');
- }
-
- return error;
-}
-
-static int diff_format_email_append_patches_tobuf(
- git_buf *out,
- git_diff *diff)
-{
- 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)
- error = git_patch_to_buf(out, patch);
-
- git_patch_free(patch);
-
- if (error < 0)
- break;
- }
-
- return error;
-}
+#ifndef GIT_DEPRECATE_HARD
int git_diff_format_email(
git_buf *out,
git_diff *diff,
const git_diff_format_email_options *opts)
{
- git_diff_stats *stats = NULL;
- char *summary = NULL, *loc = NULL;
- bool ignore_marker;
- unsigned int format_flags = 0;
- size_t allocsize;
+ git_email_create_options email_create_opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
int error;
GIT_ASSERT_ARG(out);
@@ -251,64 +172,13 @@ int git_diff_format_email(
GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
"git_format_email_options");
- ignore_marker = (opts->flags &
- GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
-
- if (!ignore_marker) {
- if (opts->patch_no > opts->total_patches) {
- git_error_set(GIT_ERROR_INVALID,
- "patch %"PRIuZ" out of range. max %"PRIuZ,
- opts->patch_no, opts->total_patches);
- return -1;
- }
+ if ((opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0)
+ email_create_opts.subject_prefix = "";
- if (opts->patch_no == 0) {
- git_error_set(GIT_ERROR_INVALID,
- "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
- return -1;
- }
- }
- /* the summary we receive may not be clean.
- * it could potentially contain new line characters
- * or not be set, sanitize, */
- if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
- size_t offset = 0;
-
- if ((offset = (loc - opts->summary)) == 0) {
- git_error_set(GIT_ERROR_INVALID, "summary is empty");
- error = -1;
- goto on_error;
- }
-
- GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
- summary = git__calloc(allocsize, sizeof(char));
- GIT_ERROR_CHECK_ALLOC(summary);
-
- strncpy(summary, opts->summary, offset);
- }
-
- error = diff_format_email_append_header_tobuf(out,
- opts->id, opts->author, summary == NULL ? opts->summary : summary,
- opts->body, opts->patch_no, opts->total_patches, ignore_marker);
-
- if (error < 0)
- goto on_error;
-
- format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
-
- if ((error = git_buf_puts(out, "---\n")) < 0 ||
- (error = git_diff_get_stats(&stats, diff)) < 0 ||
- (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
- (error = git_buf_putc(out, '\n')) < 0 ||
- (error = diff_format_email_append_patches_tobuf(out, diff)) < 0)
- goto on_error;
-
- error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
-
-on_error:
- git__free(summary);
- git_diff_stats_free(stats);
+ error = git_email__append_from_diff(out, diff, opts->patch_no,
+ opts->total_patches, opts->id, opts->summary, opts->body,
+ opts->author, &email_create_opts);
return error;
}
@@ -323,60 +193,43 @@ int git_diff_commit_as_email(
const git_diff_options *diff_opts)
{
git_diff *diff = NULL;
- git_diff_format_email_options opts =
- GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
+ const git_oid *commit_id;
+ const char *summary, *body;
+ const git_signature *author;
int error;
GIT_ASSERT_ARG(out);
GIT_ASSERT_ARG(repo);
GIT_ASSERT_ARG(commit);
- opts.flags = flags;
- opts.patch_no = patch_no;
- opts.total_patches = total_patches;
- opts.id = git_commit_id(commit);
- opts.summary = git_commit_summary(commit);
- opts.body = git_commit_body(commit);
- opts.author = git_commit_author(commit);
+ commit_id = git_commit_id(commit);
+ summary = git_commit_summary(commit);
+ body = git_commit_body(commit);
+ author = git_commit_author(commit);
+
+ if ((flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0)
+ opts.subject_prefix = "";
if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
return error;
- error = git_diff_format_email(out, diff, &opts);
+ error = git_email_create_from_diff(out, diff, patch_no, total_patches, commit_id, summary, body, author, &opts);
git_diff_free(diff);
return error;
}
-int git_diff_options_init(git_diff_options *opts, unsigned int version)
-{
- GIT_INIT_STRUCTURE_FROM_TEMPLATE(
- opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
- return 0;
-}
-
-#ifndef GIT_DEPRECATE_HARD
int git_diff_init_options(git_diff_options *opts, unsigned int version)
{
return git_diff_options_init(opts, version);
}
-#endif
-int git_diff_find_options_init(
- git_diff_find_options *opts, unsigned int version)
-{
- GIT_INIT_STRUCTURE_FROM_TEMPLATE(
- opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
- return 0;
-}
-
-#ifndef GIT_DEPRECATE_HARD
int git_diff_find_init_options(
git_diff_find_options *opts, unsigned int version)
{
return git_diff_find_options_init(opts, version);
}
-#endif
int git_diff_format_email_options_init(
git_diff_format_email_options *opts, unsigned int version)
@@ -387,14 +240,29 @@ int git_diff_format_email_options_init(
return 0;
}
-#ifndef GIT_DEPRECATE_HARD
int git_diff_format_email_init_options(
git_diff_format_email_options *opts, unsigned int version)
{
return git_diff_format_email_options_init(opts, version);
}
+
#endif
+int git_diff_options_init(git_diff_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
+ return 0;
+}
+
+int git_diff_find_options_init(
+ git_diff_find_options *opts, unsigned int version)
+{
+ GIT_INIT_STRUCTURE_FROM_TEMPLATE(
+ opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
+ return 0;
+}
+
static int flush_hunk(git_oid *result, git_hash_ctx *ctx)
{
git_oid hash;
diff --git a/src/email.c b/src/email.c
new file mode 100644
index 000000000..8957d9a38
--- /dev/null
+++ b/src/email.c
@@ -0,0 +1,300 @@
+/*
+ * 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.
+ */
+
+#include "email.h"
+
+#include "buffer.h"
+#include "common.h"
+#include "diff_generate.h"
+
+#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(
+ git_buf *out,
+ size_t patch_idx,
+ size_t patch_count,
+ git_email_create_options *opts)
+{
+ const char *subject_prefix = opts->subject_prefix ?
+ opts->subject_prefix : "PATCH";
+
+ git_buf_putc(out, '[');
+
+ if (*subject_prefix)
+ git_buf_puts(out, subject_prefix);
+
+ if (opts->reroll_number) {
+ if (*subject_prefix)
+ git_buf_putc(out, ' ');
+
+ git_buf_printf(out, "v%" PRIuZ, opts->reroll_number);
+ }
+
+ 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)
+ git_buf_putc(out, ' ');
+
+ git_buf_printf(out, "%" PRIuZ "/%" PRIuZ,
+ patch_idx + (start_number - 1),
+ patch_count + (start_number - 1));
+ }
+
+ git_buf_puts(out, "]");
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
+static int append_subject(
+ git_buf *out,
+ size_t patch_idx,
+ size_t patch_count,
+ const char *summary,
+ git_email_create_options *opts)
+{
+ bool prefix = include_prefix(patch_count, opts);
+ size_t summary_len = summary ? strlen(summary) : 0;
+ int error;
+
+ if (summary_len) {
+ const char *nl = strchr(summary, '\n');
+
+ if (nl)
+ summary_len = (nl - summary);
+ }
+
+ if ((error = git_buf_puts(out, "Subject: ")) < 0)
+ return error;
+
+ if (prefix &&
+ (error = append_prefix(out, patch_idx, patch_count, opts)) < 0)
+ return error;
+
+ if (prefix && summary_len && (error = git_buf_putc(out, ' ')) < 0)
+ return error;
+
+ if (summary_len &&
+ (error = git_buf_put(out, summary, summary_len)) < 0)
+ return error;
+
+ return git_buf_putc(out, '\n');
+}
+
+static int 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)
+{
+ char id[GIT_OID_HEXSZ];
+ char date[GIT_DATE_RFC2822_SZ];
+ int error;
+
+ if ((error = git_oid_fmt(id, commit_id)) < 0 ||
+ (error = git_buf_printf(out, "From %.*s %s\n", GIT_OID_HEXSZ, id, EMAIL_TIMESTAMP)) < 0 ||
+ (error = git_buf_printf(out, "From: %s <%s>\n", author->name, author->email)) < 0 ||
+ (error = git__date_rfc2822_fmt(date, sizeof(date), &author->when)) < 0 ||
+ (error = git_buf_printf(out, "Date: %s\n", date)) < 0 ||
+ (error = append_subject(out, patch_idx, patch_count, summary, opts)) < 0)
+ return error;
+
+ if ((error = git_buf_putc(out, '\n')) < 0)
+ return error;
+
+ return 0;
+}
+
+static int append_body(git_buf *out, const char *body)
+{
+ size_t body_len;
+ int error;
+
+ if (!body)
+ return 0;
+
+ body_len = strlen(body);
+
+ if ((error = git_buf_puts(out, body)) < 0)
+ return error;
+
+ if (body_len && body[body_len - 1] != '\n')
+ error = git_buf_putc(out, '\n');
+
+ return error;
+}
+
+static int append_diffstat(git_buf *out, git_diff *diff)
+{
+ 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 &&
+ (error = git_diff_stats_to_buf(out, stats, format_flags, 0)) == 0)
+ error = git_buf_putc(out, '\n');
+
+ git_diff_stats_free(stats);
+ return error;
+}
+
+static int append_patches(git_buf *out, git_diff *diff)
+{
+ 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)
+ error = git_patch_to_buf(out, patch);
+
+ git_patch_free(patch);
+
+ if (error < 0)
+ break;
+ }
+
+ return error;
+}
+
+int 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)
+{
+ git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
+ int error;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(diff);
+ GIT_ASSERT_ARG(!patch_idx || patch_idx <= patch_count);
+ GIT_ASSERT_ARG(commit_id);
+ GIT_ASSERT_ARG(author);
+
+ 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));
+
+ git_buf_sanitize(out);
+ git_buf_clear(out);
+
+ if ((error = append_header(out, patch_idx, patch_count, commit_id, summary, author, &opts)) == 0 &&
+ (error = append_body(out, body)) == 0 &&
+ (error = git_buf_puts(out, "---\n")) == 0 &&
+ (error = append_diffstat(out, diff)) == 0 &&
+ (error = append_patches(out, diff)) == 0)
+ error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
+
+ return error;
+}
+
+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)
+{
+ int error;
+
+ git_buf_sanitize(out);
+ git_buf_clear(out);
+
+ error = git_email__append_from_diff(out, diff, patch_idx,
+ patch_count, commit_id, summary, body, author,
+ given_opts);
+
+ return error;
+}
+
+int git_email_create_from_commit(
+ git_buf *out,
+ git_commit *commit,
+ const git_email_create_options *given_opts)
+{
+ git_email_create_options opts = GIT_EMAIL_CREATE_OPTIONS_INIT;
+ git_diff *diff = NULL;
+ git_repository *repo;
+ git_diff_options *diff_opts;
+ git_diff_find_options *find_opts;
+ const git_signature *author;
+ const char *summary, *body;
+ const git_oid *commit_id;
+ int error = -1;
+
+ GIT_ASSERT_ARG(out);
+ GIT_ASSERT_ARG(commit);
+
+ 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));
+
+ 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);
+ diff_opts = &opts.diff_opts;
+ find_opts = &opts.diff_find_opts;
+
+ if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
+ goto done;
+
+ if ((opts.flags & GIT_EMAIL_CREATE_NO_RENAMES) == 0 &&
+ (error = git_diff_find_similar(diff, find_opts)) < 0)
+ goto done;
+
+ error = git_email_create_from_diff(out, diff, 1, 1, commit_id, summary, body, author, &opts);
+
+done:
+ git_diff_free(diff);
+ return error;
+}
diff --git a/src/email.h b/src/email.h
new file mode 100644
index 000000000..7aeb462ab
--- /dev/null
+++ b/src/email.h
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+#ifndef INCLUDE_email_h__
+#define INCLUDE_email_h__
+
+#include "common.h"
+
+#include "git2/email.h"
+
+extern int 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);
+
+#endif