mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-12 08:45:01 +00:00

Now that the `git_diff_foreach` function does not depend on internals of the `git_patch_generated` structure anymore, we can easily move it to the actual diff code.
377 lines
8.4 KiB
C
377 lines
8.4 KiB
C
/*
|
|
* 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 "git2/version.h"
|
|
#include "common.h"
|
|
#include "diff.h"
|
|
#include "diff_generate.h"
|
|
#include "patch.h"
|
|
#include "commit.h"
|
|
#include "index.h"
|
|
|
|
#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
|
|
(((DIFF)->opts.flags & (FLAG)) != 0)
|
|
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
|
|
(((DIFF)->opts.flags & (FLAG)) == 0)
|
|
#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
|
|
(VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
|
|
|
|
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
|
|
{
|
|
const char *str = delta->old_file.path;
|
|
|
|
if (!str ||
|
|
delta->status == GIT_DELTA_ADDED ||
|
|
delta->status == GIT_DELTA_RENAMED ||
|
|
delta->status == GIT_DELTA_COPIED)
|
|
str = delta->new_file.path;
|
|
|
|
return str;
|
|
}
|
|
|
|
const char *git_diff_delta__path(const git_diff_delta *delta)
|
|
{
|
|
return diff_delta__path(delta);
|
|
}
|
|
|
|
int git_diff_delta__cmp(const void *a, const void *b)
|
|
{
|
|
const git_diff_delta *da = a, *db = b;
|
|
int val = strcmp(diff_delta__path(da), diff_delta__path(db));
|
|
return val ? val : ((int)da->status - (int)db->status);
|
|
}
|
|
|
|
int git_diff_delta__casecmp(const void *a, const void *b)
|
|
{
|
|
const git_diff_delta *da = a, *db = b;
|
|
int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
|
|
return val ? val : ((int)da->status - (int)db->status);
|
|
}
|
|
|
|
int git_diff__entry_cmp(const void *a, const void *b)
|
|
{
|
|
const git_index_entry *entry_a = a;
|
|
const git_index_entry *entry_b = b;
|
|
|
|
return strcmp(entry_a->path, entry_b->path);
|
|
}
|
|
|
|
int git_diff__entry_icmp(const void *a, const void *b)
|
|
{
|
|
const git_index_entry *entry_a = a;
|
|
const git_index_entry *entry_b = b;
|
|
|
|
return strcasecmp(entry_a->path, entry_b->path);
|
|
}
|
|
|
|
void git_diff_free(git_diff *diff)
|
|
{
|
|
if (!diff)
|
|
return;
|
|
|
|
GIT_REFCOUNT_DEC(diff, diff->free_fn);
|
|
}
|
|
|
|
void git_diff_addref(git_diff *diff)
|
|
{
|
|
GIT_REFCOUNT_INC(diff);
|
|
}
|
|
|
|
size_t git_diff_num_deltas(const git_diff *diff)
|
|
{
|
|
assert(diff);
|
|
return diff->deltas.length;
|
|
}
|
|
|
|
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
|
|
{
|
|
size_t i, count = 0;
|
|
const git_diff_delta *delta;
|
|
|
|
assert(diff);
|
|
|
|
git_vector_foreach(&diff->deltas, i, delta) {
|
|
count += (delta->status == type);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
|
|
{
|
|
assert(diff);
|
|
return git_vector_get(&diff->deltas, idx);
|
|
}
|
|
|
|
int git_diff_is_sorted_icase(const git_diff *diff)
|
|
{
|
|
return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
|
|
}
|
|
|
|
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
|
|
{
|
|
assert(out);
|
|
GITERR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
|
|
out->stat_calls = diff->perf.stat_calls;
|
|
out->oid_calculations = diff->perf.oid_calculations;
|
|
return 0;
|
|
}
|
|
|
|
int git_diff_foreach(
|
|
git_diff *diff,
|
|
git_diff_file_cb file_cb,
|
|
git_diff_binary_cb binary_cb,
|
|
git_diff_hunk_cb hunk_cb,
|
|
git_diff_line_cb data_cb,
|
|
void *payload)
|
|
{
|
|
int error = 0;
|
|
git_diff_delta *delta;
|
|
size_t idx;
|
|
|
|
assert(diff);
|
|
|
|
git_vector_foreach(&diff->deltas, idx, delta) {
|
|
git_patch *patch;
|
|
|
|
/* check flags against patch status */
|
|
if (git_diff_delta__should_skip(&diff->opts, delta))
|
|
continue;
|
|
|
|
if ((error = git_patch_from_diff(&patch, diff, idx)) != 0)
|
|
break;
|
|
|
|
error = git_patch__invoke_callbacks(patch, file_cb, binary_cb,
|
|
hunk_cb, data_cb, payload);
|
|
git_patch_free(patch);
|
|
|
|
if (error)
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
int git_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;
|
|
}
|
|
|
|
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;
|
|
size_t allocsize;
|
|
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");
|
|
|
|
ignore_marker = (opts->flags &
|
|
GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
|
|
|
|
if (!ignore_marker) {
|
|
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;
|
|
goto on_error;
|
|
}
|
|
|
|
GITERR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
|
|
summary = git__calloc(allocsize, 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->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 = 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.body = git_commit_body(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, unsigned int version)
|
|
{
|
|
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
|
|
opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
|
|
return 0;
|
|
}
|
|
|
|
int git_diff_find_init_options(
|
|
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;
|
|
}
|
|
|
|
int git_diff_format_email_init_options(
|
|
git_diff_format_email_options *opts, unsigned int version)
|
|
{
|
|
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
|
|
opts, version, git_diff_format_email_options,
|
|
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
|
|
return 0;
|
|
}
|
|
|