Commit c5cacc4e by Vicent Marti

Merge pull request #2261 from jacquesg/format-patch

Support for format-patch
parents 3b2d14a7 39206ca2
...@@ -1072,6 +1072,179 @@ GIT_EXTERN(int) git_diff_buffers( ...@@ -1072,6 +1072,179 @@ GIT_EXTERN(int) git_diff_buffers(
git_diff_line_cb line_cb, git_diff_line_cb line_cb,
void *payload); void *payload);
/**
* This is an opaque structure which is allocated by `git_diff_get_stats`.
* You are responsible for releasing the object memory when done, using the
* `git_diff_stats_free()` function.
*/
typedef struct git_diff_stats git_diff_stats;
/**
* Formatting options for diff stats
*/
typedef enum {
/** No stats*/
GIT_DIFF_STATS_NONE = 0,
/** Full statistics, equivalent of `--stat` */
GIT_DIFF_STATS_FULL = (1u << 0),
/** Short statistics, equivalent of `--shortstat` */
GIT_DIFF_STATS_SHORT = (1u << 1),
/** Number statistics, equivalent of `--numstat` */
GIT_DIFF_STATS_NUMBER = (1u << 2),
/** Extended header information such as creations, renames and mode changes, equivalent of `--summary` */
GIT_DIFF_STATS_INCLUDE_SUMMARY = (1u << 3),
} git_diff_stats_format_t;
/**
* Accumlate diff statistics for all patches.
*
* @param out Structure containg the diff statistics.
* @param diff A git_diff generated by one of the above functions.
* @return 0 on success; non-zero on error
*/
GIT_EXTERN(int) git_diff_get_stats(
git_diff_stats **out,
git_diff *diff);
/**
* Get the total number of files changed in a diff
*
* @param stats A `git_diff_stats` generated by one of the above functions.
* @return total number of files changed in the diff
*/
GIT_EXTERN(size_t) git_diff_stats_files_changed(
const git_diff_stats *stats);
/**
* Get the total number of insertions in a diff
*
* @param stats A `git_diff_stats` generated by one of the above functions.
* @return total number of insertions in the diff
*/
GIT_EXTERN(size_t) git_diff_stats_insertions(
const git_diff_stats *stats);
/**
* Get the total number of deletions in a diff
*
* @param stats A `git_diff_stats` generated by one of the above functions.
* @return total number of deletions in the diff
*/
GIT_EXTERN(size_t) git_diff_stats_deletions(
const git_diff_stats *stats);
/**
* Print diff statistics to a `git_buf`.
*
* @param out buffer to store the formatted diff statistics in.
* @param stats A `git_diff_stats` generated by one of the above functions.
* @param format Formatting option.
* @return 0 on success; non-zero on error
*/
GIT_EXTERN(int) git_diff_stats_to_buf(
git_buf *out,
const git_diff_stats *stats,
git_diff_stats_format_t format);
/**
* Deallocate a `git_diff_stats`.
*
* @param stats The previously created statistics object;
* cannot be used after free.
*/
GIT_EXTERN(void) git_diff_stats_free(git_diff_stats *stats);
/**
* Formatting options for diff e-mail generation
*/
typedef enum {
/** Normal patch, the default */
GIT_DIFF_FORMAT_EMAIL_NONE = 0,
/** Don't insert "[PATCH]" in the subject header*/
GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER = (1 << 0),
} git_diff_format_email_flags_t;
/**
* Options for controlling the formatting of the generated e-mail.
*/
typedef struct {
unsigned int version;
git_diff_format_email_flags_t flags;
/** This patch number */
size_t patch_no;
/** Total number of patches in this series */
size_t total_patches;
/** id to use for the commit */
const git_oid *id;
/** Summary of the change */
const char *summary;
/** Author of the change */
const git_signature *author;
} git_diff_format_email_options;
#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION 1
#define GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT {GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, 0, 1, 1, NULL, NULL, NULL}
/**
* Create an e-mail ready patch from a diff.
*
* @param out buffer to store the e-mail patch in
* @param diff containing the commit
* @param opts structure with options to influence content and formatting.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_diff_format_email(
git_buf *out,
git_diff *diff,
const git_diff_format_email_options *opts);
/**
* Create an e-mail ready patch for a commit.
*
* Does not support creating patches for merge commits (yet).
*
* @param out buffer to store the e-mail patch in
* @param repo containing the commit
* @param commit pointer to up commit
* @param patch_no patch number of the commit
* @param total_patches total number of patches in the patch set
* @param flags determines the formatting of the e-mail
* @param diff_opts structure with options to influence diff or NULL for defaults.
* @return 0 or an error code
*/
GIT_EXTERN(int) git_diff_commit_as_email(
git_buf *out,
git_repository *repo,
git_commit *commit,
size_t patch_no,
size_t total_patches,
git_diff_format_email_flags_t flags,
const git_diff_options *diff_opts);
/**
* Initializes a `git_diff_format_email_options` with default values. Equivalent to
* creating an instance with GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT.
*
* @param opts the `git_diff_format_email_options` instance to initialize.
* @param version the version of the struct; you should pass
* `GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION` here.
* @return Zero on success; -1 on failure.
*/
GIT_EXTERN(int) git_diff_format_email_init_options(
git_diff_format_email_options *opts,
int version);
GIT_END_DECL GIT_END_DECL
......
...@@ -141,12 +141,12 @@ GIT_EXTERN(void) git_patch_free(git_patch *patch); ...@@ -141,12 +141,12 @@ GIT_EXTERN(void) git_patch_free(git_patch *patch);
/** /**
* Get the delta associated with a patch * Get the delta associated with a patch
*/ */
GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(git_patch *patch); GIT_EXTERN(const git_diff_delta *) git_patch_get_delta(const git_patch *patch);
/** /**
* Get the number of hunks in a patch * Get the number of hunks in a patch
*/ */
GIT_EXTERN(size_t) git_patch_num_hunks(git_patch *patch); GIT_EXTERN(size_t) git_patch_num_hunks(const git_patch *patch);
/** /**
* Get line counts of each type in a patch. * Get line counts of each type in a patch.
...@@ -197,7 +197,7 @@ GIT_EXTERN(int) git_patch_get_hunk( ...@@ -197,7 +197,7 @@ GIT_EXTERN(int) git_patch_get_hunk(
* @return Number of lines in hunk or -1 if invalid hunk index * @return Number of lines in hunk or -1 if invalid hunk index
*/ */
GIT_EXTERN(int) git_patch_num_lines_in_hunk( GIT_EXTERN(int) git_patch_num_lines_in_hunk(
git_patch *patch, const git_patch *patch,
size_t hunk_idx); size_t hunk_idx);
/** /**
......
...@@ -148,6 +148,15 @@ int git_buf_putc(git_buf *buf, char c) ...@@ -148,6 +148,15 @@ int git_buf_putc(git_buf *buf, char c)
return 0; return 0;
} }
int git_buf_putcn(git_buf *buf, char c, size_t len)
{
ENSURE_SIZE(buf, buf->size + len + 1);
memset(buf->ptr + buf->size, c, len);
buf->size += len;
buf->ptr[buf->size] = '\0';
return 0;
}
int git_buf_put(git_buf *buf, const char *data, size_t len) int git_buf_put(git_buf *buf, const char *data, size_t len)
{ {
ENSURE_SIZE(buf, buf->size + len + 1); ENSURE_SIZE(buf, buf->size + len + 1);
......
...@@ -88,6 +88,7 @@ GIT_INLINE(bool) git_buf_oom(const git_buf *buf) ...@@ -88,6 +88,7 @@ GIT_INLINE(bool) git_buf_oom(const git_buf *buf)
*/ */
int git_buf_sets(git_buf *buf, const char *string); int git_buf_sets(git_buf *buf, const char *string);
int git_buf_putc(git_buf *buf, char c); int git_buf_putc(git_buf *buf, char c);
int git_buf_putcn(git_buf *buf, char c, size_t len);
int git_buf_put(git_buf *buf, const char *data, size_t len); int git_buf_put(git_buf *buf, const char *data, size_t len);
int git_buf_puts(git_buf *buf, const char *string); int git_buf_puts(git_buf *buf, const char *string);
int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); int git_buf_printf(git_buf *buf, const char *format, ...) GIT_FORMAT_PRINTF(2, 3);
......
...@@ -874,3 +874,31 @@ int git__date_parse(git_time_t *out, const char *date) ...@@ -874,3 +874,31 @@ int git__date_parse(git_time_t *out, const char *date)
*out = approxidate_str(date, time_sec, &error_ret); *out = approxidate_str(date, time_sec, &error_ret);
return error_ret; return error_ret;
} }
int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date)
{
int written;
struct tm gmt;
time_t t;
assert(out && date);
t = (time_t) (date->time + date->offset * 60);
if (p_gmtime_r (&t, &gmt) == NULL)
return -1;
written = p_snprintf(out, len, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d",
weekday_names[gmt.tm_wday],
gmt.tm_mday,
month_names[gmt.tm_mon],
gmt.tm_year + 1900,
gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
date->offset / 60, date->offset % 60);
if (written < 0 || (written > (int) len - 1))
return -1;
return 0;
}
...@@ -1427,6 +1427,216 @@ int git_diff__paired_foreach( ...@@ -1427,6 +1427,216 @@ int git_diff__paired_foreach(
return error; return error;
} }
int git_diff__commit(
git_diff **diff,
git_repository *repo,
const git_commit *commit,
const git_diff_options *opts)
{
git_commit *parent = NULL;
git_diff *commit_diff = NULL;
git_tree *old_tree = NULL, *new_tree = NULL;
size_t parents;
int error = 0;
if ((parents = git_commit_parentcount(commit)) > 1) {
char commit_oidstr[GIT_OID_HEXSZ + 1];
error = -1;
giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
goto on_error;
}
if (parents > 0)
if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
(error = git_commit_tree(&old_tree, parent)) < 0)
goto on_error;
if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
(error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
goto on_error;
*diff = commit_diff;
on_error:
git_tree_free(new_tree);
git_tree_free(old_tree);
git_commit_free(parent);
return error;
}
int git_diff_format_email__append_header_tobuf(
git_buf *out,
const git_oid *id,
const git_signature *author,
const char *summary,
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);
return error;
}
int git_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;
}
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;
int error;
assert(out && diff && opts);
assert(opts->summary && opts->id && opts->author);
GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options");
if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) {
if (opts->patch_no > opts->total_patches) {
giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches);
return -1;
}
if (opts->patch_no == 0) {
giterr_set(GITERR_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) {
giterr_set(GITERR_INVALID, "summary is empty");
error = -1;
}
summary = git__calloc(offset + 1, sizeof(char));
GITERR_CHECK_ALLOC(summary);
strncpy(summary, opts->summary, offset);
}
error = git_diff_format_email__append_header_tobuf(out,
opts->id, opts->author, summary == NULL ? opts->summary : summary,
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 ||
(error = git_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);
return error;
}
int git_diff_commit_as_email(
git_buf *out,
git_repository *repo,
git_commit *commit,
size_t patch_no,
size_t total_patches,
git_diff_format_email_flags_t flags,
const git_diff_options *diff_opts)
{
git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
int error;
assert (out && repo && 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.author = git_commit_author(commit);
if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
return error;
error = git_diff_format_email(out, diff, &opts);
git_diff_free(diff);
return error;
}
int git_diff_init_options(git_diff_options* opts, int version) int git_diff_init_options(git_diff_options* opts, int version)
{ {
if (version != GIT_DIFF_OPTIONS_VERSION) { if (version != GIT_DIFF_OPTIONS_VERSION) {
...@@ -1450,3 +1660,15 @@ int git_diff_find_init_options(git_diff_find_options* opts, int version) ...@@ -1450,3 +1660,15 @@ int git_diff_find_init_options(git_diff_find_options* opts, int version)
return 0; return 0;
} }
} }
int git_diff_format_email_init_options(git_diff_format_email_options* opts, int version)
{
if (version != GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION) {
giterr_set(GITERR_INVALID, "Invalid version %d for git_diff_format_email_options", version);
return -1;
} else {
git_diff_format_email_options o = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
memcpy(opts, &o, sizeof(o));
return 0;
}
}
...@@ -116,6 +116,9 @@ extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); ...@@ -116,6 +116,9 @@ extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
extern int git_diff_find_similar__calc_similarity( extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload); int *score, void *siga, void *sigb, void *payload);
extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
/* /*
* Sometimes a git_diff_file will have a zero size; this attempts to * Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is * fill in the size without loading the blob if possible. If that is
......
...@@ -631,13 +631,13 @@ void git_patch_free(git_patch *patch) ...@@ -631,13 +631,13 @@ void git_patch_free(git_patch *patch)
GIT_REFCOUNT_DEC(patch, diff_patch_free); GIT_REFCOUNT_DEC(patch, diff_patch_free);
} }
const git_diff_delta *git_patch_get_delta(git_patch *patch) const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{ {
assert(patch); assert(patch);
return patch->delta; return patch->delta;
} }
size_t git_patch_num_hunks(git_patch *patch) size_t git_patch_num_hunks(const git_patch *patch)
{ {
assert(patch); assert(patch);
return git_array_size(patch->hunks); return git_array_size(patch->hunks);
...@@ -708,7 +708,7 @@ int git_patch_get_hunk( ...@@ -708,7 +708,7 @@ int git_patch_get_hunk(
return 0; return 0;
} }
int git_patch_num_lines_in_hunk(git_patch *patch, size_t hunk_idx) int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
{ {
diff_patch_hunk *hunk; diff_patch_hunk *hunk;
assert(patch); assert(patch);
......
/*
* 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 "common.h"
#include "vector.h"
#include "diff.h"
#include "diff_patch.h"
#define DIFF_RENAME_FILE_SEPARATOR " => "
struct git_diff_stats {
git_vector patches;
size_t files_changed;
size_t insertions;
size_t deletions;
};
static size_t diff_get_filename_padding(
int has_renames,
const git_diff_stats *stats)
{
const git_patch *patch = NULL;
size_t i, max_padding = 0;
if (has_renames) {
git_vector_foreach(&stats->patches, i, patch) {
const git_diff_delta *delta = NULL;
size_t len;
delta = git_patch_get_delta(patch);
if (strcmp(delta->old_file.path, delta->new_file.path) == 0)
continue;
if ((len = strlen(delta->old_file.path) + strlen(delta->new_file.path)) > max_padding)
max_padding = len;
}
}
git_vector_foreach(&stats->patches, i, patch) {
const git_diff_delta *delta = NULL;
size_t len;
delta = git_patch_get_delta(patch);
len = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0)
continue;
if (len > max_padding)
max_padding = len;
}
return max_padding;
}
int git_diff_file_stats__full_to_buf(
git_buf *out,
size_t max_padding,
int has_renames,
const git_patch *patch)
{
const char *old_path = NULL, *new_path = NULL;
const git_diff_delta *delta = NULL;
size_t padding, old_size, new_size;
int error;
delta = git_patch_get_delta(patch);
old_path = delta->old_file.path;
new_path = delta->new_file.path;
old_size = delta->old_file.size;
new_size = delta->new_file.size;
if ((error = git_buf_printf(out, " %s", old_path)) < 0)
goto on_error;
if (strcmp(old_path, new_path) != 0) {
padding = max_padding - strlen(old_path) - strlen(new_path);
if ((error = git_buf_printf(out, DIFF_RENAME_FILE_SEPARATOR "%s", new_path)) < 0)
goto on_error;
}
else {
padding = max_padding - strlen(old_path);
if (has_renames)
padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
}
if ((error = git_buf_putcn(out, ' ', padding)) < 0 ||
(error = git_buf_puts(out, " | ")) < 0)
goto on_error;
if (delta->flags & GIT_DIFF_FLAG_BINARY) {
if ((error = git_buf_printf(out, "Bin %" PRIuZ " -> %" PRIuZ " bytes", old_size, new_size)) < 0)
goto on_error;
}
else {
size_t insertions, deletions;
if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0)
goto on_error;
if ((error = git_buf_printf(out, "%" PRIuZ, insertions + deletions)) < 0)
goto on_error;
if (insertions || deletions) {
if ((error = git_buf_putc(out, ' ')) < 0 ||
(error = git_buf_putcn(out, '+', insertions)) < 0 ||
(error = git_buf_putcn(out, '-', deletions)) < 0)
goto on_error;
}
}
error = git_buf_putc(out, '\n');
on_error:
return error;
}
int git_diff_file_stats__number_to_buf(
git_buf *out,
const git_patch *patch)
{
const git_diff_delta *delta = NULL;
const char *path = NULL;
size_t insertions, deletions;
int error;
delta = git_patch_get_delta(patch);
path = delta->new_file.path;
if ((error = git_patch_line_stats(NULL, &insertions, &deletions, patch)) < 0)
return error;
if (delta->flags & GIT_DIFF_FLAG_BINARY)
error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
else
error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n", insertions, deletions, path);
return error;
}
int git_diff_file_stats__summary_to_buf(
git_buf *out,
const git_patch *patch)
{
const git_diff_delta *delta = NULL;
delta = git_patch_get_delta(patch);
if (delta->old_file.mode != delta->new_file.mode) {
if (delta->old_file.mode == 0) {
git_buf_printf(out, " create mode %06o %s\n",
delta->new_file.mode, delta->new_file.path);
}
else if (delta->new_file.mode == 0) {
git_buf_printf(out, " delete mode %06o %s\n",
delta->old_file.mode, delta->old_file.path);
}
else {
git_buf_printf(out, " mode change %06o => %06o %s\n",
delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
}
}
return 0;
}
int git_diff_stats__has_renames(
const git_diff_stats *stats)
{
git_patch *patch = NULL;
size_t i;
git_vector_foreach(&stats->patches, i, patch) {
const git_diff_delta *delta = git_patch_get_delta(patch);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
return 1;
}
}
return 0;
}
int git_diff_stats__add_file_stats(
git_diff_stats *stats,
git_patch *patch)
{
const git_diff_delta *delta = NULL;
int error = 0;
if ((delta = git_patch_get_delta(patch)) == NULL)
return -1;
if ((error = git_vector_insert(&stats->patches, patch)) < 0)
return error;
return error;
}
int git_diff_get_stats(
git_diff_stats **out,
git_diff *diff)
{
size_t i, deltas;
size_t total_insertions = 0, total_deletions = 0;
git_diff_stats *stats = NULL;
int error = 0;
assert(out && diff);
stats = git__calloc(1, sizeof(git_diff_stats));
GITERR_CHECK_ALLOC(stats);
deltas = git_diff_num_deltas(diff);
for (i = 0; i < deltas; ++i) {
git_patch *patch = NULL;
size_t add, remove;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
goto on_error;
if ((error = git_patch_line_stats(NULL, &add, &remove, patch)) < 0 ||
(error = git_diff_stats__add_file_stats(stats, patch)) < 0) {
git_patch_free(patch);
goto on_error;
}
total_insertions += add;
total_deletions += remove;
}
stats->files_changed = deltas;
stats->insertions = total_insertions;
stats->deletions = total_deletions;
*out = stats;
goto done;
on_error:
git_diff_stats_free(stats);
done:
return error;
}
size_t git_diff_stats_files_changed(
const git_diff_stats *stats)
{
assert(stats);
return stats->files_changed;
}
size_t git_diff_stats_insertions(
const git_diff_stats *stats)
{
assert(stats);
return stats->insertions;
}
size_t git_diff_stats_deletions(
const git_diff_stats *stats)
{
assert(stats);
return stats->deletions;
}
int git_diff_stats_to_buf(
git_buf *out,
const git_diff_stats *stats,
git_diff_stats_format_t format)
{
git_patch *patch = NULL;
size_t i;
int has_renames = 0, error = 0;
assert(out && stats);
/* check if we have renames, it affects the padding */
has_renames = git_diff_stats__has_renames(stats);
git_vector_foreach(&stats->patches, i, patch) {
if (format & GIT_DIFF_STATS_FULL) {
size_t max_padding = diff_get_filename_padding(has_renames, stats);
error = git_diff_file_stats__full_to_buf(out, max_padding, has_renames, patch);
}
else if (format & GIT_DIFF_STATS_NUMBER) {
error = git_diff_file_stats__number_to_buf(out, patch);
}
if (error < 0)
return error;
}
if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
error = git_buf_printf(out, " %" PRIuZ " file%s changed, %" PRIuZ " insertions(+), %" PRIuZ " deletions(-)\n",
stats->files_changed, stats->files_changed > 1 ? "s" : "",
stats->insertions, stats->deletions);
if (error < 0)
return error;
}
if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
git_vector_foreach(&stats->patches, i, patch) {
if ((error = git_diff_file_stats__summary_to_buf(out, patch)) < 0)
return error;
}
if (git_vector_length(&stats->patches) > 0)
git_buf_putc(out, '\n');
}
return error;
}
void git_diff_stats_free(git_diff_stats *stats)
{
size_t i;
git_patch *patch;
if (stats == NULL)
return;
git_vector_foreach(&stats->patches, i, patch)
git_patch_free(patch);
git_vector_free(&stats->patches);
git__free(stats);
}
...@@ -20,6 +20,8 @@ ...@@ -20,6 +20,8 @@
# define max(a,b) ((a) > (b) ? (a) : (b)) # define max(a,b) ((a) > (b) ? (a) : (b))
#endif #endif
#define GIT_DATE_RFC2822_SZ 32
/* /*
* Custom memory allocation wrappers * Custom memory allocation wrappers
* that set error code and error message * that set error code and error message
...@@ -329,6 +331,16 @@ extern int git__parse_bool(int *out, const char *value); ...@@ -329,6 +331,16 @@ extern int git__parse_bool(int *out, const char *value);
extern int git__date_parse(git_time_t *out, const char *date); extern int git__date_parse(git_time_t *out, const char *date);
/* /*
* Format a git_time as a RFC2822 string
*
* @param out buffer to store formatted date; a '\\0' terminator will automatically be added.
* @param len size of the buffer; should be atleast `GIT_DATE_RFC2822_SZ` in size;
* @param date the date to be formatted
* @return 0 if successful; -1 on error
*/
extern int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date);
/*
* Unescapes a string in-place. * Unescapes a string in-place.
* *
* Edge cases behavior: * Edge cases behavior:
......
#include "clar_libgit2.h"
#include "util.h"
void test_date_rfc2822__format_rfc2822_no_offset(void)
{
git_time t = {1397031663, 0};
char buf[GIT_DATE_RFC2822_SZ];
cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
cl_assert(strcmp(buf, "Wed, 9 Apr 2014 08:21:03 +0000") == 0);
}
void test_date_rfc2822__format_rfc2822_positive_offset(void)
{
git_time t = {1397031663, 120};
char buf[GIT_DATE_RFC2822_SZ];
cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
cl_assert(strcmp(buf, "Wed, 9 Apr 2014 10:21:03 +0200") == 0);
}
void test_date_rfc2822__format_rfc2822_negative_offset(void)
{
git_time t = {1397031663, -120};
char buf[GIT_DATE_RFC2822_SZ];
cl_git_pass(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
cl_assert(strcmp(buf, "Wed, 9 Apr 2014 06:21:03 -0200") == 0);
}
void test_date_rfc2822__format_rfc2822_buffer_too_small(void)
{
// "Wed, 10 Apr 2014 08:21:03 +0000"
git_time t = {1397031663 + 86400, 0};
char buf[GIT_DATE_RFC2822_SZ-1];
cl_git_fail(git__date_rfc2822_fmt(buf, sizeof(buf), &t));
}
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
file1.txt
file1.txt
_file1.txt_
file1.txt
file1.txt
file1.txt_renamed
file1.txt
file1.txt
file1.txt
file1.txt_renamed
file1.txt
file1.txt
_file1.txt_
_file1.txt_
file1.txt
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment