Commit 804d5fe9 by Edward Thomson Committed by Edward Thomson

patch: abstract patches into diff'ed and parsed

Patches can now come from a variety of sources - either internally
generated (from diffing two commits) or as the results of parsing
some external data.
parent 8d2eef27
......@@ -10,7 +10,7 @@
#include "git2/patch.h"
#include "git2/filter.h"
#include "array.h"
#include "diff_patch.h"
#include "patch.h"
#include "fileops.h"
#include "apply.h"
#include "delta.h"
......@@ -163,7 +163,7 @@ static int update_hunk(
static int apply_hunk(
patch_image *image,
git_patch *patch,
diff_patch_hunk *hunk)
git_patch_hunk *hunk)
{
patch_image preimage, postimage;
size_t line_num, i;
......@@ -218,7 +218,7 @@ static int apply_hunks(
size_t source_len,
git_patch *patch)
{
diff_patch_hunk *hunk;
git_patch_hunk *hunk;
git_diff_line *line;
patch_image image;
size_t i;
......@@ -340,9 +340,11 @@ int git_apply__patch(
*mode_out = 0;
if (patch->delta->status != GIT_DELTA_DELETED) {
filename = git__strdup(patch->nfile.file->path);
mode = patch->nfile.file->mode ?
patch->nfile.file->mode : GIT_FILEMODE_BLOB;
const git_diff_file *newfile = patch->newfile(patch);
filename = git__strdup(newfile->path);
mode = newfile->mode ?
newfile->mode : GIT_FILEMODE_BLOB;
}
if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
......
......@@ -9,7 +9,6 @@
#include "git2/attr.h"
#include "diff.h"
#include "diff_patch.h"
#include "diff_driver.h"
#include "strmap.h"
#include "map.h"
......
......@@ -6,7 +6,8 @@
*/
#include "common.h"
#include "diff.h"
#include "diff_patch.h"
#include "diff_file.h"
#include "patch_diff.h"
#include "fileops.h"
#include "zstream.h"
#include "blob.h"
......@@ -14,19 +15,19 @@
#include "git2/sys/diff.h"
typedef struct {
git_diff *diff;
git_diff_format_t format;
git_diff_line_cb print_cb;
void *payload;
git_buf *buf;
git_diff_line line;
const char *old_prefix;
const char *new_prefix;
uint32_t flags;
int oid_strlen;
git_diff_line line;
unsigned int
content_loaded : 1,
content_allocated : 1;
git_diff_file_content *ofile;
git_diff_file_content *nfile;
int (*strcomp)(const char *, const char *);
} diff_print_info;
static int diff_print_info_init__common(
......@@ -74,11 +75,13 @@ static int diff_print_info_init_fromdiff(
memset(pi, 0, sizeof(diff_print_info));
pi->diff = diff;
if (diff) {
pi->flags = diff->opts.flags;
pi->oid_strlen = diff->opts.id_abbrev;
pi->old_prefix = diff->opts.old_prefix;
pi->new_prefix = diff->opts.new_prefix;
pi->strcomp = diff->strcomp;
}
return diff_print_info_init__common(pi, out, repo, format, cb, payload);
......@@ -92,24 +95,16 @@ static int diff_print_info_init_frompatch(
git_diff_line_cb cb,
void *payload)
{
git_repository *repo;
assert(patch);
repo = patch->diff ? patch->diff->repo : NULL;
memset(pi, 0, sizeof(diff_print_info));
pi->diff = patch->diff;
pi->flags = patch->diff_opts.flags;
pi->oid_strlen = patch->diff_opts.id_abbrev;
pi->old_prefix = patch->diff_opts.old_prefix;
pi->new_prefix = patch->diff_opts.new_prefix;
pi->content_loaded = 1;
pi->ofile = &patch->ofile;
pi->nfile = &patch->nfile;
return diff_print_info_init__common(pi, out, repo, format, cb, payload);
return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
}
static char diff_pick_suffix(int mode)
......@@ -173,8 +168,8 @@ static int diff_print_one_name_status(
diff_print_info *pi = data;
git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
int (*strcomp)(const char *, const char *) =
pi->diff ? pi->diff->strcomp : git__strcmp;
int(*strcomp)(const char *, const char *) = pi->strcomp ?
pi->strcomp : git__strcmp;
GIT_UNUSED(progress);
......@@ -367,39 +362,6 @@ static int format_binary(
return 0;
}
static int diff_print_load_content(
diff_print_info *pi,
git_diff_delta *delta)
{
git_diff_file_content *ofile, *nfile;
int error;
assert(pi->diff);
ofile = git__calloc(1, sizeof(git_diff_file_content));
nfile = git__calloc(1, sizeof(git_diff_file_content));
GITERR_CHECK_ALLOC(ofile);
GITERR_CHECK_ALLOC(nfile);
if ((error = git_diff_file_content__init_from_diff(
ofile, pi->diff, delta, true)) < 0 ||
(error = git_diff_file_content__init_from_diff(
nfile, pi->diff, delta, true)) < 0) {
git__free(ofile);
git__free(nfile);
return error;
}
pi->content_loaded = 1;
pi->content_allocated = 1;
pi->ofile = ofile;
pi->nfile = nfile;
return 0;
}
static int diff_print_patch_file_binary(
diff_print_info *pi, git_diff_delta *delta,
const char *old_pfx, const char *new_pfx,
......@@ -411,10 +373,6 @@ static int diff_print_patch_file_binary(
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
goto noshow;
if (!pi->content_loaded &&
(error = diff_print_load_content(pi, delta)) < 0)
return error;
if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
return 0;
......@@ -450,9 +408,9 @@ static int diff_print_patch_file(
int error;
diff_print_info *pi = data;
const char *oldpfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
(pi->flags & GIT_DIFF_FORCE_BINARY);
......@@ -488,9 +446,9 @@ static int diff_print_patch_binary(
{
diff_print_info *pi = data;
const char *old_pfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *new_pfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
int error;
git_buf_clear(pi->buf);
......@@ -585,43 +543,11 @@ int git_diff_print(
giterr_set_after_callback_function(error, "git_diff_print");
}
git__free(pi.nfile);
git__free(pi.ofile);
git_buf_free(&buf);
return error;
}
/* print a git_patch to an output callback */
int git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf temp = GIT_BUF_INIT;
diff_print_info pi;
assert(patch && print_cb);
if (!(error = diff_print_info_init_frompatch(
&pi, &temp, patch,
GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
{
error = git_patch__invoke_callbacks(
patch, diff_print_patch_file, diff_print_patch_binary,
diff_print_patch_hunk, diff_print_patch_line, &pi);
if (error) /* make sure error message is set */
giterr_set_after_callback_function(error, "git_patch_print");
}
git_buf_free(&temp);
return error;
}
int git_diff_print_callback__to_buf(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
......@@ -662,6 +588,37 @@ int git_diff_print_callback__to_file_handle(
return 0;
}
/* print a git_patch to an output callback */
int git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf temp = GIT_BUF_INIT;
diff_print_info pi;
assert(patch && print_cb);
if (!(error = diff_print_info_init_frompatch(
&pi, &temp, patch,
GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
{
error = git_patch__invoke_callbacks(
patch,
diff_print_patch_file, diff_print_patch_binary,
diff_print_patch_hunk, diff_print_patch_line,
&pi);
if (error) /* make sure error message is set */
giterr_set_after_callback_function(error, "git_patch_print");
}
git_buf_free(&temp);
return error;
}
/* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch)
{
......
......@@ -7,7 +7,7 @@
#include "common.h"
#include "vector.h"
#include "diff.h"
#include "diff_patch.h"
#include "patch_diff.h"
#define DIFF_RENAME_FILE_SEPARATOR " => "
#define STATS_FULL_MIN_SCALE 7
......@@ -190,8 +190,9 @@ int git_diff_get_stats(
break;
/* keep a count of renames because it will affect formatting */
delta = git_patch_get_delta(patch);
delta = patch->delta;
/* TODO ugh */
namelen = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
namelen += strlen(delta->old_file.path);
......
......@@ -8,8 +8,8 @@
#include "common.h"
#include "diff.h"
#include "diff_driver.h"
#include "diff_patch.h"
#include "diff_xdiff.h"
#include "patch_diff.h"
static int git_xdiff_scan_int(const char **str, int *value)
{
......@@ -56,7 +56,7 @@ fail:
typedef struct {
git_xdiff_output *xo;
git_patch *patch;
git_patch_diff *patch;
git_diff_hunk hunk;
int old_lineno, new_lineno;
mmfile_t xd_old_data, xd_new_data;
......@@ -110,9 +110,9 @@ static int diff_update_lines(
static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
{
git_xdiff_info *info = priv;
git_patch *patch = info->patch;
const git_diff_delta *delta = git_patch_get_delta(patch);
git_diff_output *output = &info->xo->output;
git_patch_diff *patch = info->patch;
const git_diff_delta *delta = patch->base.delta;
git_patch_diff_output *output = &info->xo->output;
git_diff_line line;
if (len == 1) {
......@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
return output->error;
}
static int git_xdiff(git_diff_output *output, git_patch *patch)
static int git_xdiff(git_patch_diff_output *output, git_patch_diff *patch)
{
git_xdiff_output *xo = (git_xdiff_output *)output;
git_xdiff_info info;
......@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
xo->callback.priv = &info;
git_diff_find_context_init(
&xo->config.find_func, &findctxt, git_patch__driver(patch));
&xo->config.find_func, &findctxt, git_patch_diff_driver(patch));
xo->config.find_func_priv = &findctxt;
if (xo->config.find_func != NULL)
......@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
* updates are needed to xo->params.flags
*/
git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
git_patch__new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
git_patch_diff_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
git_patch_diff_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
......
......@@ -8,20 +8,20 @@
#define INCLUDE_diff_xdiff_h__
#include "diff.h"
#include "diff_patch.h"
#include "xdiff/xdiff.h"
#include "patch_diff.h"
/* xdiff cannot cope with large files. these files should not be passed to
* xdiff. callers should treat these large files as binary.
*/
#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023)
/* A git_xdiff_output is a git_diff_output with extra fields necessary
/* A git_xdiff_output is a git_patch_diff_output with extra fields necessary
* to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
* of the output to use xdiff to generate the diffs.
*/
typedef struct {
git_diff_output output;
git_patch_diff_output output;
xdemitconf_t config;
xpparam_t params;
......
#include "git2/patch.h"
#include "diff_patch.h"
#include "diff.h"
#include "patch.h"
#define parse_err(...) \
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
typedef struct {
const char *content;
size_t content_len;
const char *line;
size_t line_len;
size_t line_num;
size_t remain;
char *header_new_path;
char *header_old_path;
} patch_parse_ctx;
static void parse_advance_line(patch_parse_ctx *ctx)
{
ctx->line += ctx->line_len;
ctx->remain -= ctx->line_len;
ctx->line_len = git__linenlen(ctx->line, ctx->remain);
ctx->line_num++;
}
static void parse_advance_chars(patch_parse_ctx *ctx, size_t char_cnt)
{
ctx->line += char_cnt;
ctx->remain -= char_cnt;
ctx->line_len -= char_cnt;
}
static int parse_advance_expected(
patch_parse_ctx *ctx,
const char *expected,
size_t expected_len)
int git_patch__invoke_callbacks(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload)
{
if (ctx->line_len < expected_len)
return -1;
int error = 0;
uint32_t i, j;
if (memcmp(ctx->line, expected, expected_len) != 0)
return -1;
if (file_cb)
error = file_cb(patch->delta, 0, payload);
parse_advance_chars(ctx, expected_len);
return 0;
}
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (binary_cb)
error = binary_cb(patch->delta, &patch->binary, payload);
static int parse_advance_ws(patch_parse_ctx *ctx)
{
int ret = -1;
while (ctx->line_len > 0 &&
ctx->line[0] != '\n' &&
git__isspace(ctx->line[0])) {
ctx->line++;
ctx->line_len--;
ctx->remain--;
ret = 0;
return error;
}
return ret;
}
if (!hunk_cb && !line_cb)
return error;
static int parse_advance_nl(patch_parse_ctx *ctx)
{
if (ctx->line_len != 1 || ctx->line[0] != '\n')
return -1;
for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
git_patch_hunk *h = git_array_get(patch->hunks, i);
parse_advance_line(ctx);
return 0;
}
if (hunk_cb)
error = hunk_cb(patch->delta, &h->hunk, payload);
static int header_path_len(patch_parse_ctx *ctx)
{
bool inquote = 0;
bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"');
size_t len;
if (!line_cb)
continue;
for (len = quoted; len < ctx->line_len; len++) {
if (!quoted && git__isspace(ctx->line[len]))
break;
else if (quoted && !inquote && ctx->line[len] == '"') {
len++;
break;
}
for (j = 0; !error && j < h->line_count; ++j) {
git_diff_line *l =
git_array_get(patch->lines, h->line_start + j);
inquote = (!inquote && ctx->line[len] == '\\');
error = line_cb(patch->delta, &h->hunk, l, payload);
}
}
return len;
}
static int parse_header_path_buf(git_buf *path, patch_parse_ctx *ctx)
{
int path_len, error = 0;
path_len = header_path_len(ctx);
if ((error = git_buf_put(path, ctx->line, path_len)) < 0)
goto done;
parse_advance_chars(ctx, path_len);
git_buf_rtrim(path);
if (path->size > 0 && path->ptr[0] == '"')
error = git_buf_unquote(path);
if (error < 0)
goto done;
git_path_squash_slashes(path);
done:
return error;
}
static int parse_header_path(char **out, patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
int error = parse_header_path_buf(&path, ctx);
*out = git_buf_detach(&path);
return error;
}
static int parse_header_git_oldpath(git_patch *patch, patch_parse_ctx *ctx)
{
return parse_header_path((char **)&patch->ofile.file->path, ctx);
}
static int parse_header_git_newpath(git_patch *patch, patch_parse_ctx *ctx)
{
return parse_header_path((char **)&patch->nfile.file->path, ctx);
}
static int parse_header_mode(uint16_t *mode, patch_parse_ctx *ctx)
size_t git_patch_size(
git_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers)
{
const char *end;
int32_t m;
int ret;
size_t out;
if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]))
return parse_err("invalid file mode at line %d", ctx->line_num);
assert(patch);
if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0)
return ret;
out = patch->content_size;
if (m > UINT16_MAX)
return -1;
if (!include_context)
out -= patch->context_size;
*mode = (uint16_t)m;
if (include_hunk_headers)
out += patch->header_size;
parse_advance_chars(ctx, (end - ctx->line));
if (include_file_headers) {
git_buf file_header = GIT_BUF_INIT;
return ret;
}
if (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
giterr_clear();
else
out += git_buf_len(&file_header);
static int parse_header_oid(
git_oid *oid,
size_t *oid_len,
patch_parse_ctx *ctx)
{
size_t len;
for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) {
if (!git__isxdigit(ctx->line[len]))
break;
git_buf_free(&file_header);
}
if (len < GIT_OID_MINPREFIXLEN ||
git_oid_fromstrn(oid, ctx->line, len) < 0)
return parse_err("invalid hex formatted object id at line %d",
ctx->line_num);
parse_advance_chars(ctx, len);
*oid_len = len;
return 0;
return out;
}
static int parse_header_git_index(git_patch *patch, patch_parse_ctx *ctx)
int git_patch_line_stats(
size_t *total_ctxt,
size_t *total_adds,
size_t *total_dels,
const git_patch *patch)
{
/*
* TODO: we read the prefix provided in the diff into the delta's id
* field, but do not mark is at an abbreviated id.
*/
size_t oid_len, nid_len;
if (parse_header_oid(&patch->delta->old_file.id, &oid_len, ctx) < 0 ||
parse_advance_expected(ctx, "..", 2) < 0 ||
parse_header_oid(&patch->delta->new_file.id, &nid_len, ctx) < 0)
return -1;
if (ctx->line_len > 0 && ctx->line[0] == ' ') {
uint16_t mode;
parse_advance_chars(ctx, 1);
size_t totals[3], idx;
if (parse_header_mode(&mode, ctx) < 0)
return -1;
memset(totals, 0, sizeof(totals));
if (!patch->delta->new_file.mode)
patch->delta->new_file.mode = mode;
for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
git_diff_line *line = git_array_get(patch->lines, idx);
if (!line)
continue;
if (!patch->delta->old_file.mode)
patch->delta->old_file.mode = mode;
switch (line->origin) {
case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
case GIT_DIFF_LINE_DELETION: totals[2]++; break;
default:
/* diff --stat and --numstat don't count EOFNL marks because
* they will always be paired with a ADDITION or DELETION line.
*/
break;
}
}
return 0;
}
static int parse_header_git_oldmode(git_patch *patch, patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->ofile.file->mode, ctx);
}
static int parse_header_git_newmode(git_patch *patch, patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->nfile.file->mode, ctx);
}
static int parse_header_git_deletedfilemode(
git_patch *patch,
patch_parse_ctx *ctx)
{
git__free((char *)patch->ofile.file->path);
patch->ofile.file->path = NULL;
patch->delta->status = GIT_DELTA_DELETED;
return parse_header_mode(&patch->ofile.file->mode, ctx);
}
static int parse_header_git_newfilemode(
git_patch *patch,
patch_parse_ctx *ctx)
{
git__free((char *)patch->nfile.file->path);
patch->nfile.file->path = NULL;
patch->delta->status = GIT_DELTA_ADDED;
return parse_header_mode(&patch->nfile.file->mode, ctx);
}
static int parse_header_rename(
char **out,
char **header_path,
patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
size_t header_path_len, prefix_len;
if (*header_path == NULL)
return parse_err("rename without proper git diff header at line %d",
ctx->line_num);
header_path_len = strlen(*header_path);
if (parse_header_path_buf(&path, ctx) < 0)
return -1;
if (header_path_len < git_buf_len(&path))
return parse_err("rename path is invalid at line %d", ctx->line_num);
/* This sanity check exists because git core uses the data in the
* "rename from" / "rename to" lines, but it's formatted differently
* than the other paths and lacks the normal prefix. This irregularity
* causes us to ignore these paths (we always store the prefixed paths)
* but instead validate that they match the suffix of the paths we parsed
* since we would behave differently from git core if they ever differed.
* Instead, we raise an error, rather than parsing differently.
*/
prefix_len = header_path_len - path.size;
if (strncmp(*header_path + prefix_len, path.ptr, path.size) != 0 ||
(prefix_len > 0 && (*header_path)[prefix_len - 1] != '/'))
return parse_err("rename path does not match header at line %d",
ctx->line_num);
*out = *header_path;
*header_path = NULL;
git_buf_free(&path);
if (total_ctxt)
*total_ctxt = totals[0];
if (total_adds)
*total_adds = totals[1];
if (total_dels)
*total_dels = totals[2];
return 0;
}
static int parse_header_renamefrom(git_patch *patch, patch_parse_ctx *ctx)
const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{
patch->delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->ofile.file->path,
&ctx->header_old_path,
ctx);
assert(patch);
return patch->delta;
}
static int parse_header_renameto(git_patch *patch, patch_parse_ctx *ctx)
size_t git_patch_num_hunks(const git_patch *patch)
{
patch->delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->nfile.file->path,
&ctx->header_new_path,
ctx);
assert(patch);
return git_array_size(patch->hunks);
}
static int parse_header_percent(uint16_t *out, patch_parse_ctx *ctx)
static int patch_error_outofrange(const char *thing)
{
int32_t val;
const char *end;
if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) ||
git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0)
return -1;
parse_advance_chars(ctx, (end - ctx->line));
if (parse_advance_expected(ctx, "%", 1) < 0)
return -1;
if (val > 100)
return -1;
*out = val;
return 0;
giterr_set(GITERR_INVALID, "patch %s index out of range", thing);
return GIT_ENOTFOUND;
}
static int parse_header_similarity(git_patch *patch, patch_parse_ctx *ctx)
{
if (parse_header_percent(&patch->delta->similarity, ctx) < 0)
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
return 0;
}
static int parse_header_dissimilarity(git_patch *patch, patch_parse_ctx *ctx)
{
uint16_t dissimilarity;
if (parse_header_percent(&dissimilarity, ctx) < 0)
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
patch->delta->similarity = 100 - dissimilarity;
return 0;
}
typedef struct {
const char *str;
int (*fn)(git_patch *, patch_parse_ctx *);
} header_git_op;
static const header_git_op header_git_ops[] = {
{ "@@ -", NULL },
{ "GIT binary patch", NULL },
{ "--- ", parse_header_git_oldpath },
{ "+++ ", parse_header_git_newpath },
{ "index ", parse_header_git_index },
{ "old mode ", parse_header_git_oldmode },
{ "new mode ", parse_header_git_newmode },
{ "deleted file mode ", parse_header_git_deletedfilemode },
{ "new file mode ", parse_header_git_newfilemode },
{ "rename from ", parse_header_renamefrom },
{ "rename to ", parse_header_renameto },
{ "rename old ", parse_header_renamefrom },
{ "rename new ", parse_header_renameto },
{ "similarity index ", parse_header_similarity },
{ "dissimilarity index ", parse_header_dissimilarity },
};
static int parse_header_git(
int git_patch_get_hunk(
const git_diff_hunk **out,
size_t *lines_in_hunk,
git_patch *patch,
patch_parse_ctx *ctx)
size_t hunk_idx)
{
size_t i;
int error = 0;
/* Parse the diff --git line */
if (parse_advance_expected(ctx, "diff --git ", 11) < 0)
return parse_err("corrupt git diff header at line %d", ctx->line_num);
if (parse_header_path(&ctx->header_old_path, ctx) < 0)
return parse_err("corrupt old path in git diff header at line %d",
ctx->line_num);
if (parse_advance_ws(ctx) < 0 ||
parse_header_path(&ctx->header_new_path, ctx) < 0)
return parse_err("corrupt new path in git diff header at line %d",
ctx->line_num);
/* Parse remaining header lines */
for (parse_advance_line(ctx); ctx->remain > 0; parse_advance_line(ctx)) {
if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n')
break;
for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) {
const header_git_op *op = &header_git_ops[i];
size_t op_len = strlen(op->str);
if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0)
continue;
git_patch_hunk *hunk;
assert(patch);
/* Do not advance if this is the patch separator */
if (op->fn == NULL)
goto done;
hunk = git_array_get(patch->hunks, hunk_idx);
parse_advance_chars(ctx, op_len);
if ((error = op->fn(patch, ctx)) < 0)
goto done;
parse_advance_ws(ctx);
parse_advance_expected(ctx, "\n", 1);
if (ctx->line_len > 0) {
error = parse_err("trailing data at line %d", ctx->line_num);
goto done;
}
break;
}
if (!hunk) {
if (out) *out = NULL;
if (lines_in_hunk) *lines_in_hunk = 0;
return patch_error_outofrange("hunk");
}
done:
return error;
}
static int parse_number(git_off_t *out, patch_parse_ctx *ctx)
{
const char *end;
int64_t num;
if (!git__isdigit(ctx->line[0]))
return -1;
if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0)
return -1;
if (num < 0)
return -1;
*out = num;
parse_advance_chars(ctx, (end - ctx->line));
return 0;
}
static int parse_int(int *out, patch_parse_ctx *ctx)
{
git_off_t num;
if (parse_number(&num, ctx) < 0 || !git__is_int(num))
return -1;
*out = (int)num;
if (out) *out = &hunk->hunk;
if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
return 0;
}
static int parse_hunk_header(
diff_patch_hunk *hunk,
patch_parse_ctx *ctx)
int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
{
const char *header_start = ctx->line;
hunk->hunk.old_lines = 1;
hunk->hunk.new_lines = 1;
if (parse_advance_expected(ctx, "@@ -", 4) < 0 ||
parse_int(&hunk->hunk.old_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected(ctx, ",", 1) < 0 ||
parse_int(&hunk->hunk.old_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected(ctx, " +", 2) < 0 ||
parse_int(&hunk->hunk.new_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected(ctx, ",", 1) < 0 ||
parse_int(&hunk->hunk.new_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected(ctx, " @@", 3) < 0)
goto fail;
parse_advance_line(ctx);
if (!hunk->hunk.old_lines && !hunk->hunk.new_lines)
goto fail;
hunk->hunk.header_len = ctx->line - header_start;
if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1))
return parse_err("oversized patch hunk header at line %d",
ctx->line_num);
memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len);
hunk->hunk.header[hunk->hunk.header_len] = '\0';
return 0;
git_patch_hunk *hunk;
assert(patch);
fail:
giterr_set(GITERR_PATCH, "invalid patch hunk header at line %d",
ctx->line_num);
return -1;
if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
return patch_error_outofrange("hunk");
return (int)hunk->line_count;
}
static int parse_hunk_body(
int git_patch_get_line_in_hunk(
const git_diff_line **out,
git_patch *patch,
diff_patch_hunk *hunk,
patch_parse_ctx *ctx)
size_t hunk_idx,
size_t line_of_hunk)
{
git_patch_hunk *hunk;
git_diff_line *line;
int error = 0;
int oldlines = hunk->hunk.old_lines;
int newlines = hunk->hunk.new_lines;
for (;
ctx->remain > 4 && (oldlines || newlines) &&
memcmp(ctx->line, "@@ -", 4) != 0;
parse_advance_line(ctx)) {
int origin;
int prefix = 1;
if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') {
error = parse_err("invalid patch instruction at line %d",
ctx->line_num);
goto done;
}
switch (ctx->line[0]) {
case '\n':
prefix = 0;
case ' ':
origin = GIT_DIFF_LINE_CONTEXT;
oldlines--;
newlines--;
break;
case '-':
origin = GIT_DIFF_LINE_DELETION;
oldlines--;
break;
case '+':
origin = GIT_DIFF_LINE_ADDITION;
newlines--;
break;
default:
error = parse_err("invalid patch hunk at line %d", ctx->line_num);
goto done;
}
line = git_array_alloc(patch->lines);
GITERR_CHECK_ALLOC(line);
memset(line, 0x0, sizeof(git_diff_line));
line->content = ctx->line + prefix;
line->content_len = ctx->line_len - prefix;
line->content_offset = ctx->content_len - ctx->remain;
line->origin = origin;
hunk->line_count++;
}
if (oldlines || newlines) {
error = parse_err(
"invalid patch hunk, expected %d old lines and %d new lines",
hunk->hunk.old_lines, hunk->hunk.new_lines);
goto done;
}
/* Handle "\ No newline at end of file". Only expect the leading
* backslash, though, because the rest of the string could be
* localized. Because `diff` optimizes for the case where you
* want to apply the patch by hand.
*/
if (ctx->line_len >= 2 && memcmp(ctx->line, "\\ ", 2) == 0 &&
git_array_size(patch->lines) > 0) {
line = git_array_get(patch->lines, git_array_size(patch->lines)-1);
if (line->content_len < 1) {
error = parse_err("cannot trim trailing newline of empty line");
goto done;
}
line->content_len--;
parse_advance_line(ctx);
}
done:
return error;
}
static int parse_header_traditional(git_patch *patch, patch_parse_ctx *ctx)
{
GIT_UNUSED(patch);
GIT_UNUSED(ctx);
return 1;
}
static int parse_patch_header(
git_patch *patch,
patch_parse_ctx *ctx)
{
int error = 0;
for (ctx->line = ctx->content; ctx->remain > 0; parse_advance_line(ctx)) {
/* This line is too short to be a patch header. */
if (ctx->line_len < 6)
continue;
/* This might be a hunk header without a patch header, provide a
* sensible error message. */
if (memcmp(ctx->line, "@@ -", 4) == 0) {
size_t line_num = ctx->line_num;
diff_patch_hunk hunk;
/* If this cannot be parsed as a hunk header, it's just leading
* noise, continue.
*/
if (parse_hunk_header(&hunk, ctx) < 0) {
giterr_clear();
continue;
}
error = parse_err("invalid hunk header outside patch at line %d",
line_num);
goto done;
}
/* This buffer is too short to contain a patch. */
if (ctx->remain < ctx->line_len + 6)
break;
/* A proper git patch */
if (ctx->line_len >= 11 && memcmp(ctx->line, "diff --git ", 11) == 0) {
if ((error = parse_header_git(patch, ctx)) < 0)
goto done;
/* For modechange only patches, it does not include filenames;
* instead we need to use the paths in the diff --git header.
*/
if (!patch->ofile.file->path && !patch->nfile.file->path) {
if (!ctx->header_old_path || !ctx->header_new_path) {
error = parse_err("git diff header lacks old / new paths");
goto done;
}
patch->ofile.file->path = ctx->header_old_path;
ctx->header_old_path = NULL;
patch->nfile.file->path = ctx->header_new_path;
ctx->header_new_path = NULL;
}
goto done;
}
if ((error = parse_header_traditional(patch, ctx)) <= 0)
goto done;
assert(patch);
error = 0;
continue;
if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
if (out) *out = NULL;
return patch_error_outofrange("hunk");
}
error = parse_err("no header in patch file");
done:
return error;
}
static int parse_patch_binary_side(
git_diff_binary_file *binary,
patch_parse_ctx *ctx)
{
git_diff_binary_t type = GIT_DIFF_BINARY_NONE;
git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT;
git_off_t len;
int error = 0;
if (ctx->line_len >= 8 && memcmp(ctx->line, "literal ", 8) == 0) {
type = GIT_DIFF_BINARY_LITERAL;
parse_advance_chars(ctx, 8);
} else if (ctx->line_len >= 6 && memcmp(ctx->line, "delta ", 6) == 0) {
type = GIT_DIFF_BINARY_DELTA;
parse_advance_chars(ctx, 6);
} else {
error = parse_err("unknown binary delta type at line %d", ctx->line_num);
goto done;
}
if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) {
error = parse_err("invalid binary size at line %d", ctx->line_num);
goto done;
}
while (ctx->line_len) {
char c = ctx->line[0];
size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size;
if (c == '\n')
break;
else if (c >= 'A' && c <= 'Z')
decoded_len = c - 'A' + 1;
else if (c >= 'a' && c <= 'z')
decoded_len = c - 'a' + 26 + 1;
if (!decoded_len) {
error = parse_err("invalid binary length at line %d", ctx->line_num);
goto done;
}
parse_advance_chars(ctx, 1);
encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
if (encoded_len > ctx->line_len - 1) {
error = parse_err("truncated binary data at line %d", ctx->line_num);
goto done;
}
if ((error = git_buf_decode_base85(
&decoded, ctx->line, encoded_len, decoded_len)) < 0)
goto done;
if (decoded.size - decoded_orig != decoded_len) {
error = parse_err("truncated binary data at line %d", ctx->line_num);
goto done;
}
parse_advance_chars(ctx, encoded_len);
if (parse_advance_nl(ctx) < 0) {
error = parse_err("trailing data at line %d", ctx->line_num);
goto done;
}
if (line_of_hunk >= hunk->line_count ||
!(line = git_array_get(
patch->lines, hunk->line_start + line_of_hunk))) {
if (out) *out = NULL;
return patch_error_outofrange("line");
}
binary->type = type;
binary->inflatedlen = (size_t)len;
binary->datalen = decoded.size;
binary->data = git_buf_detach(&decoded);
done:
git_buf_free(&base85);
git_buf_free(&decoded);
return error;
}
static int parse_patch_binary(
git_patch *patch,
patch_parse_ctx *ctx)
{
int error;
if (parse_advance_expected(ctx, "GIT binary patch", 16) < 0 ||
parse_advance_nl(ctx) < 0)
return parse_err("corrupt git binary header at line %d", ctx->line_num);
/* parse old->new binary diff */
if ((error = parse_patch_binary_side(&patch->binary.new_file, ctx)) < 0)
return error;
if (parse_advance_nl(ctx) < 0)
return parse_err("corrupt git binary separator at line %d", ctx->line_num);
/* parse new->old binary diff */
if ((error = parse_patch_binary_side(&patch->binary.old_file, ctx)) < 0)
return error;
patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
if (out) *out = line;
return 0;
}
static int parse_patch_hunks(
git_patch *patch,
patch_parse_ctx *ctx)
{
diff_patch_hunk *hunk;
int error = 0;
for (; ctx->line_len > 4 && memcmp(ctx->line, "@@ -", 4) == 0; ) {
hunk = git_array_alloc(patch->hunks);
GITERR_CHECK_ALLOC(hunk);
memset(hunk, 0, sizeof(diff_patch_hunk));
hunk->line_start = git_array_size(patch->lines);
hunk->line_count = 0;
if ((error = parse_hunk_header(hunk, ctx)) < 0 ||
(error = parse_hunk_body(patch, hunk, ctx)) < 0)
goto done;
}
done:
return error;
}
static int parse_patch_body(git_patch *patch, patch_parse_ctx *ctx)
static void git_patch__free(git_patch *patch)
{
if (ctx->line_len >= 16 && memcmp(ctx->line, "GIT binary patch", 16) == 0)
return parse_patch_binary(patch, ctx);
git_array_clear(patch->lines);
git_array_clear(patch->hunks);
else if (ctx->line_len >= 4 && memcmp(ctx->line, "@@ -", 4) == 0)
return parse_patch_hunks(patch, ctx);
git__free((char *)patch->binary.old_file.data);
git__free((char *)patch->binary.new_file.data);
return 0;
if (patch->free_fn)
patch->free_fn(patch);
}
static int check_patch(git_patch *patch)
void git_patch_free(git_patch *patch)
{
if (!patch->ofile.file->path && patch->delta->status != GIT_DELTA_ADDED)
return parse_err("missing old file path");
if (!patch->nfile.file->path && patch->delta->status != GIT_DELTA_DELETED)
return parse_err("missing new file path");
if (patch->ofile.file->path && patch->nfile.file->path) {
if (!patch->nfile.file->mode)
patch->nfile.file->mode = patch->ofile.file->mode;
}
if (patch->delta->status == GIT_DELTA_MODIFIED &&
!(patch->delta->flags & GIT_DIFF_FLAG_BINARY) &&
patch->nfile.file->mode == patch->ofile.file->mode &&
git_array_size(patch->hunks) == 0)
return parse_err("patch with no hunks");
return 0;
}
int git_patch_from_patchfile(
git_patch **out,
const char *content,
size_t content_len)
{
patch_parse_ctx ctx = {0};
git_patch *patch;
int error = 0;
*out = NULL;
patch = git__calloc(1, sizeof(git_patch));
GITERR_CHECK_ALLOC(patch);
patch->delta = git__calloc(1, sizeof(git_diff_delta));
patch->ofile.file = git__calloc(1, sizeof(git_diff_file));
patch->nfile.file = git__calloc(1, sizeof(git_diff_file));
patch->delta->status = GIT_DELTA_MODIFIED;
ctx.content = content;
ctx.content_len = content_len;
ctx.remain = content_len;
if ((error = parse_patch_header(patch, &ctx)) < 0 ||
(error = parse_patch_body(patch, &ctx)) < 0 ||
(error = check_patch(patch)) < 0)
goto done;
*out = patch;
done:
git__free(ctx.header_old_path);
git__free(ctx.header_new_path);
return error;
if (patch)
GIT_REFCOUNT_DEC(patch, git_patch__free);
}
/*
* 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_patch_h__
#define INCLUDE_patch_h__
#include "git2/patch.h"
#include "array.h"
/* cached information about a hunk in a patch */
typedef struct git_patch_hunk {
git_diff_hunk hunk;
size_t line_start;
size_t line_count;
} git_patch_hunk;
struct git_patch {
git_refcount rc;
git_repository *repo; /* may be null */
git_diff_options diff_opts;
git_diff_delta *delta;
git_diff_binary binary;
git_array_t(git_patch_hunk) hunks;
git_array_t(git_diff_line) lines;
size_t header_size;
size_t content_size;
size_t context_size;
const git_diff_file *(*newfile)(git_patch *patch);
const git_diff_file *(*oldfile)(git_patch *patch);
void (*free_fn)(git_patch *patch);
};
extern int git_patch__invoke_callbacks(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload);
extern int git_patch_line_stats(
size_t *total_ctxt,
size_t *total_adds,
size_t *total_dels,
const git_patch *patch);
extern void git_patch_free(git_patch *patch);
#endif
......@@ -9,47 +9,82 @@
#include "diff.h"
#include "diff_file.h"
#include "diff_driver.h"
#include "diff_patch.h"
#include "patch_diff.h"
#include "diff_xdiff.h"
#include "delta.h"
#include "zstream.h"
#include "fileops.h"
static void diff_output_init(
git_diff_output*, const git_diff_options*, git_diff_file_cb,
git_patch_diff_output *, const git_diff_options *, git_diff_file_cb,
git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
static void diff_output_to_patch(git_diff_output *, git_patch *);
static void diff_output_to_patch(git_patch_diff_output *, git_patch_diff *);
static void diff_patch_update_binary(git_patch *patch)
static const git_diff_file *patch_diff_newfile(git_patch *p)
{
if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
git_patch_diff *patch = (git_patch_diff *)p;
return patch->nfile.file;
}
static const git_diff_file *patch_diff_oldfile(git_patch *p)
{
git_patch_diff *patch = (git_patch_diff *)p;
return patch->ofile.file;
}
static void patch_diff_free(git_patch *p)
{
git_patch_diff *patch = (git_patch_diff *)p;
git_diff_file_content__clear(&patch->ofile);
git_diff_file_content__clear(&patch->nfile);
git_diff_free(patch->diff); /* decrements refcount */
patch->diff = NULL;
git_pool_clear(&patch->flattened);
git__free((char *)patch->base.diff_opts.old_prefix);
git__free((char *)patch->base.diff_opts.new_prefix);
if (patch->flags & GIT_PATCH_DIFF_ALLOCATED)
git__free(patch);
}
static void patch_diff_update_binary(git_patch_diff *patch)
{
if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
return;
if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
(patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
(patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}
static void diff_patch_init_common(git_patch *patch)
static void patch_diff_init_common(git_patch_diff *patch)
{
diff_patch_update_binary(patch);
patch->base.newfile = patch_diff_newfile;
patch->base.oldfile = patch_diff_oldfile;
patch->base.free_fn = patch_diff_free;
patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
patch_diff_update_binary(patch);
patch->flags |= GIT_PATCH_DIFF_INITIALIZED;
if (patch->diff)
git_diff_addref(patch->diff);
}
static int diff_patch_normalize_options(
static int patch_diff_normalize_options(
git_diff_options *out,
const git_diff_options *opts)
{
......@@ -75,38 +110,40 @@ static int diff_patch_normalize_options(
return 0;
}
static int diff_patch_init_from_diff(
git_patch *patch, git_diff *diff, size_t delta_index)
static int patch_diff_init(
git_patch_diff *patch, git_diff *diff, size_t delta_index)
{
int error = 0;
memset(patch, 0, sizeof(*patch));
patch->diff = diff;
patch->delta = git_vector_get(&diff->deltas, delta_index);
patch->diff = diff;
patch->base.repo = diff->repo;
patch->base.delta = git_vector_get(&diff->deltas, delta_index);
patch->delta_index = delta_index;
if ((error = diff_patch_normalize_options(
&patch->diff_opts, &diff->opts)) < 0 ||
if ((error = patch_diff_normalize_options(
&patch->base.diff_opts, &diff->opts)) < 0 ||
(error = git_diff_file_content__init_from_diff(
&patch->ofile, diff, patch->delta, true)) < 0 ||
&patch->ofile, diff, patch->base.delta, true)) < 0 ||
(error = git_diff_file_content__init_from_diff(
&patch->nfile, diff, patch->delta, false)) < 0)
&patch->nfile, diff, patch->base.delta, false)) < 0)
return error;
diff_patch_init_common(patch);
patch_diff_init_common(patch);
return 0;
}
static int diff_patch_alloc_from_diff(
git_patch **out, git_diff *diff, size_t delta_index)
static int patch_diff_alloc_from_diff(
git_patch_diff **out, git_diff *diff, size_t delta_index)
{
int error;
git_patch *patch = git__calloc(1, sizeof(git_patch));
git_patch_diff *patch = git__calloc(1, sizeof(git_patch_diff));
GITERR_CHECK_ALLOC(patch);
if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
if (!(error = patch_diff_init(patch, diff, delta_index))) {
patch->flags |= GIT_PATCH_DIFF_ALLOCATED;
GIT_REFCOUNT_INC(patch);
} else {
git__free(patch);
......@@ -117,27 +154,27 @@ static int diff_patch_alloc_from_diff(
return error;
}
GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file)
GIT_INLINE(bool) should_skip_binary(git_patch_diff *patch, git_diff_file *file)
{
if ((patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
return false;
return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
}
static bool diff_patch_diffable(git_patch *patch)
static bool patch_diff_diffable(git_patch_diff *patch)
{
size_t olen, nlen;
if (patch->delta->status == GIT_DELTA_UNMODIFIED)
if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
return false;
/* if we've determined this to be binary (and we are not showing binary
* data) then we have skipped loading the map data. instead, query the
* file data itself.
*/
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
(patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
(patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
olen = (size_t)patch->ofile.file->size;
nlen = (size_t)patch->nfile.file->size;
} else {
......@@ -154,12 +191,12 @@ static bool diff_patch_diffable(git_patch *patch)
!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
}
static int diff_patch_load(git_patch *patch, git_diff_output *output)
static int patch_diff_load(git_patch_diff *patch, git_patch_diff_output *output)
{
int error = 0;
bool incomplete_data;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
if ((patch->flags & GIT_PATCH_DIFF_LOADED) != 0)
return 0;
/* if no hunk and data callbacks and user doesn't care if data looks
......@@ -180,13 +217,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
*/
if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
&patch->ofile, &patch->diff_opts)) < 0 ||
&patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file))
goto cleanup;
}
if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
&patch->nfile, &patch->diff_opts)) < 0 ||
&patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file))
goto cleanup;
}
......@@ -194,13 +231,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
/* once workdir has been tried, load other data as needed */
if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
&patch->ofile, &patch->diff_opts)) < 0 ||
&patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file))
goto cleanup;
}
if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
&patch->nfile, &patch->diff_opts)) < 0 ||
&patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file))
goto cleanup;
}
......@@ -212,24 +249,24 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
patch->ofile.file->mode == patch->nfile.file->mode &&
patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->delta->status = GIT_DELTA_UNMODIFIED;
patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->base.delta->status = GIT_DELTA_UNMODIFIED;
cleanup:
diff_patch_update_binary(patch);
patch_diff_update_binary(patch);
if (!error) {
if (diff_patch_diffable(patch))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
if (patch_diff_diffable(patch))
patch->flags |= GIT_PATCH_DIFF_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED;
patch->flags |= GIT_PATCH_DIFF_LOADED;
}
return error;
}
static int diff_patch_invoke_file_callback(
git_patch *patch, git_diff_output *output)
static int patch_diff_invoke_file_callback(
git_patch_diff *patch, git_patch_diff_output *output)
{
float progress = patch->diff ?
((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
......@@ -238,7 +275,7 @@ static int diff_patch_invoke_file_callback(
return 0;
return giterr_set_after_callback_function(
output->file_cb(patch->delta, progress, output->payload),
output->file_cb(patch->base.delta, progress, output->payload),
"git_patch");
}
......@@ -309,7 +346,7 @@ done:
return error;
}
static int diff_binary(git_diff_output *output, git_patch *patch)
static int diff_binary(git_patch_diff_output *output, git_patch_diff *patch)
{
git_diff_binary binary = {{0}};
const char *old_data = patch->ofile.map.data;
......@@ -334,7 +371,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error;
error = giterr_set_after_callback_function(
output->binary_cb(patch->delta, &binary, output->payload),
output->binary_cb(patch->base.delta, &binary, output->payload),
"git_patch");
git__free((char *) binary.old_file.data);
......@@ -343,25 +380,25 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error;
}
static int diff_patch_generate(git_patch *patch, git_diff_output *output)
static int patch_diff_generate(git_patch_diff *patch, git_patch_diff_output *output)
{
int error = 0;
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
if ((patch->flags & GIT_PATCH_DIFF_DIFFED) != 0)
return 0;
/* if we are not looking at the binary or text data, don't do the diff */
if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
return 0;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
(error = diff_patch_load(patch, output)) < 0)
if ((patch->flags & GIT_PATCH_DIFF_LOADED) == 0 &&
(error = patch_diff_load(patch, output)) < 0)
return error;
if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
if ((patch->flags & GIT_PATCH_DIFF_DIFFABLE) == 0)
return 0;
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (output->binary_cb)
error = diff_binary(output, patch);
}
......@@ -370,33 +407,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output)
error = output->diff_cb(output, patch);
}
patch->flags |= GIT_DIFF_PATCH_DIFFED;
patch->flags |= GIT_PATCH_DIFF_DIFFED;
return error;
}
static void diff_patch_free(git_patch *patch)
{
git_diff_file_content__clear(&patch->ofile);
git_diff_file_content__clear(&patch->nfile);
git_array_clear(patch->lines);
git_array_clear(patch->hunks);
git_diff_free(patch->diff); /* decrements refcount */
patch->diff = NULL;
git_pool_clear(&patch->flattened);
git__free((char *)patch->diff_opts.old_prefix);
git__free((char *)patch->diff_opts.new_prefix);
git__free((char *)patch->binary.old_file.data);
git__free((char *)patch->binary.new_file.data);
if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
git__free(patch);
}
static int diff_required(git_diff *diff, const char *action)
{
if (diff)
......@@ -416,7 +430,7 @@ int git_diff_foreach(
int error = 0;
git_xdiff_output xo;
size_t idx;
git_patch patch;
git_patch_diff patch;
if ((error = diff_required(diff, "git_diff_foreach")) < 0)
return error;
......@@ -427,24 +441,24 @@ int git_diff_foreach(
&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts);
git_vector_foreach(&diff->deltas, idx, patch.delta) {
git_vector_foreach(&diff->deltas, idx, patch.base.delta) {
/* check flags against patch status */
if (git_diff_delta__should_skip(&diff->opts, patch.delta))
if (git_diff_delta__should_skip(&diff->opts, patch.base.delta))
continue;
if (binary_cb || hunk_cb || data_cb) {
if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 ||
(error = diff_patch_load(&patch, &xo.output)) != 0)
if ((error = patch_diff_init(&patch, diff, idx)) != 0 ||
(error = patch_diff_load(&patch, &xo.output)) != 0)
return error;
}
if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
if ((error = patch_diff_invoke_file_callback(&patch, &xo.output)) == 0) {
if (binary_cb || hunk_cb || data_cb)
error = diff_patch_generate(&patch, &xo.output);
error = patch_diff_generate(&patch, &xo.output);
}
git_patch_free(&patch);
git_patch_free(&patch.base);
if (error)
break;
......@@ -454,15 +468,15 @@ int git_diff_foreach(
}
typedef struct {
git_patch patch;
git_patch_diff patch;
git_diff_delta delta;
char paths[GIT_FLEX_ARRAY];
} diff_patch_with_delta;
} patch_diff_with_delta;
static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
static int diff_single_generate(patch_diff_with_delta *pd, git_xdiff_output *xo)
{
int error = 0;
git_patch *patch = &pd->patch;
git_patch_diff *patch = &pd->patch;
bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
......@@ -473,24 +487,24 @@ static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
pd->delta.status = GIT_DELTA_UNMODIFIED;
patch->delta = &pd->delta;
patch->base.delta = &pd->delta;
diff_patch_init_common(patch);
patch_diff_init_common(patch);
if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
return error;
error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
error = patch_diff_invoke_file_callback(patch, (git_patch_diff_output *)xo);
if (!error)
error = diff_patch_generate(patch, (git_diff_output *)xo);
error = patch_diff_generate(patch, (git_patch_diff_output *)xo);
return error;
}
static int diff_patch_from_sources(
diff_patch_with_delta *pd,
static int patch_diff_from_sources(
patch_diff_with_delta *pd,
git_xdiff_output *xo,
git_diff_file_content_src *oldsrc,
git_diff_file_content_src *newsrc,
......@@ -503,7 +517,7 @@ static int diff_patch_from_sources(
git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0)
if ((error = patch_diff_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
return error;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
......@@ -511,7 +525,7 @@ static int diff_patch_from_sources(
tmp = ldata; ldata = rdata; rdata = tmp;
}
pd->patch.delta = &pd->delta;
pd->patch.base.delta = &pd->delta;
if (!oldsrc->as_path) {
if (newsrc->as_path)
......@@ -534,12 +548,12 @@ static int diff_patch_from_sources(
return diff_single_generate(pd, xo);
}
static int diff_patch_with_delta_alloc(
diff_patch_with_delta **out,
static int patch_diff_with_delta_alloc(
patch_diff_with_delta **out,
const char **old_path,
const char **new_path)
{
diff_patch_with_delta *pd;
patch_diff_with_delta *pd;
size_t old_len = *old_path ? strlen(*old_path) : 0;
size_t new_len = *new_path ? strlen(*new_path) : 0;
size_t alloc_len;
......@@ -551,7 +565,7 @@ static int diff_patch_with_delta_alloc(
*out = pd = git__calloc(1, alloc_len);
GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
pd->patch.flags = GIT_PATCH_DIFF_ALLOCATED;
if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len);
......@@ -579,7 +593,7 @@ static int diff_from_sources(
void *payload)
{
int error = 0;
diff_patch_with_delta pd;
patch_diff_with_delta pd;
git_xdiff_output xo;
memset(&xo, 0, sizeof(xo));
......@@ -589,9 +603,9 @@ static int diff_from_sources(
memset(&pd, 0, sizeof(pd));
error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
error = patch_diff_from_sources(&pd, &xo, oldsrc, newsrc, opts);
git_patch_free(&pd.patch);
git_patch_free(&pd.patch.base);
return error;
}
......@@ -603,13 +617,13 @@ static int patch_from_sources(
const git_diff_options *opts)
{
int error = 0;
diff_patch_with_delta *pd;
patch_diff_with_delta *pd;
git_xdiff_output xo;
assert(out);
*out = NULL;
if ((error = diff_patch_with_delta_alloc(
if ((error = patch_diff_with_delta_alloc(
&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
return error;
......@@ -617,7 +631,7 @@ static int patch_from_sources(
diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
if (!(error = patch_diff_from_sources(pd, &xo, oldsrc, newsrc, opts)))
*out = (git_patch *)pd;
else
git_patch_free((git_patch *)pd);
......@@ -742,7 +756,7 @@ int git_patch_from_diff(
int error = 0;
git_xdiff_output xo;
git_diff_delta *delta = NULL;
git_patch *patch = NULL;
git_patch_diff *patch = NULL;
if (patch_ptr) *patch_ptr = NULL;
......@@ -764,17 +778,17 @@ int git_patch_from_diff(
(diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
return 0;
if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
if ((error = patch_diff_alloc_from_diff(&patch, diff, idx)) < 0)
return error;
memset(&xo, 0, sizeof(xo));
diff_output_to_patch(&xo.output, patch);
git_xdiff_init(&xo, &diff->opts);
error = diff_patch_invoke_file_callback(patch, &xo.output);
error = patch_diff_invoke_file_callback(patch, &xo.output);
if (!error)
error = diff_patch_generate(patch, &xo.output);
error = patch_diff_generate(patch, &xo.output);
if (!error) {
/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
......@@ -782,237 +796,34 @@ int git_patch_from_diff(
}
if (error || !patch_ptr)
git_patch_free(patch);
git_patch_free(&patch->base);
else
*patch_ptr = patch;
*patch_ptr = &patch->base;
return error;
}
void git_patch_free(git_patch *patch)
{
if (patch)
GIT_REFCOUNT_DEC(patch, diff_patch_free);
}
const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{
assert(patch);
return patch->delta;
}
size_t git_patch_num_hunks(const git_patch *patch)
{
assert(patch);
return git_array_size(patch->hunks);
}
int git_patch_line_stats(
size_t *total_ctxt,
size_t *total_adds,
size_t *total_dels,
const git_patch *patch)
{
size_t totals[3], idx;
memset(totals, 0, sizeof(totals));
for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
git_diff_line *line = git_array_get(patch->lines, idx);
if (!line)
continue;
switch (line->origin) {
case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
case GIT_DIFF_LINE_DELETION: totals[2]++; break;
default:
/* diff --stat and --numstat don't count EOFNL marks because
* they will always be paired with a ADDITION or DELETION line.
*/
break;
}
}
if (total_ctxt)
*total_ctxt = totals[0];
if (total_adds)
*total_adds = totals[1];
if (total_dels)
*total_dels = totals[2];
return 0;
}
static int diff_error_outofrange(const char *thing)
{
giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
return GIT_ENOTFOUND;
}
int git_patch_get_hunk(
const git_diff_hunk **out,
size_t *lines_in_hunk,
git_patch *patch,
size_t hunk_idx)
{
diff_patch_hunk *hunk;
assert(patch);
hunk = git_array_get(patch->hunks, hunk_idx);
if (!hunk) {
if (out) *out = NULL;
if (lines_in_hunk) *lines_in_hunk = 0;
return diff_error_outofrange("hunk");
}
if (out) *out = &hunk->hunk;
if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
return 0;
}
int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
{
diff_patch_hunk *hunk;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
return diff_error_outofrange("hunk");
return (int)hunk->line_count;
}
int git_patch_get_line_in_hunk(
const git_diff_line **out,
git_patch *patch,
size_t hunk_idx,
size_t line_of_hunk)
{
diff_patch_hunk *hunk;
git_diff_line *line;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
if (out) *out = NULL;
return diff_error_outofrange("hunk");
}
if (line_of_hunk >= hunk->line_count ||
!(line = git_array_get(
patch->lines, hunk->line_start + line_of_hunk))) {
if (out) *out = NULL;
return diff_error_outofrange("line");
}
if (out) *out = line;
return 0;
}
size_t git_patch_size(
git_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers)
{
size_t out;
assert(patch);
out = patch->content_size;
if (!include_context)
out -= patch->context_size;
if (include_hunk_headers)
out += patch->header_size;
if (include_file_headers) {
git_buf file_header = GIT_BUF_INIT;
if (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
giterr_clear();
else
out += git_buf_len(&file_header);
git_buf_free(&file_header);
}
return out;
}
git_diff *git_patch__diff(git_patch *patch)
{
return patch->diff;
}
git_diff_driver *git_patch__driver(git_patch *patch)
git_diff_driver *git_patch_diff_driver(git_patch_diff *patch)
{
/* ofile driver is representative for whole patch */
return patch->ofile.driver;
}
void git_patch__old_data(
char **ptr, size_t *len, git_patch *patch)
void git_patch_diff_old_data(
char **ptr, size_t *len, git_patch_diff *patch)
{
*ptr = patch->ofile.map.data;
*len = patch->ofile.map.len;
}
void git_patch__new_data(
char **ptr, size_t *len, git_patch *patch)
void git_patch_diff_new_data(
char **ptr, size_t *len, git_patch_diff *patch)
{
*ptr = patch->nfile.map.data;
*len = patch->nfile.map.len;
}
int git_patch__invoke_callbacks(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload)
{
int error = 0;
uint32_t i, j;
if (file_cb)
error = file_cb(patch->delta, 0, payload);
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (binary_cb)
error = binary_cb(patch->delta, &patch->binary, payload);
return error;
}
if (!hunk_cb && !line_cb)
return error;
for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
diff_patch_hunk *h = git_array_get(patch->hunks, i);
if (hunk_cb)
error = hunk_cb(patch->delta, &h->hunk, payload);
if (!line_cb)
continue;
for (j = 0; !error && j < h->line_count; ++j) {
git_diff_line *l =
git_array_get(patch->lines, h->line_start + j);
error = line_cb(patch->delta, &h->hunk, l, payload);
}
}
return error;
}
static int diff_patch_file_cb(
static int patch_diff_file_cb(
const git_diff_delta *delta,
float progress,
void *payload)
......@@ -1021,7 +832,7 @@ static int diff_patch_file_cb(
return 0;
}
static int diff_patch_binary_cb(
static int patch_diff_binary_cb(
const git_diff_delta *delta,
const git_diff_binary *binary,
void *payload)
......@@ -1051,62 +862,62 @@ static int diff_patch_binary_cb(
return 0;
}
static int diff_patch_hunk_cb(
static int git_patch_hunk_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk_,
void *payload)
{
git_patch *patch = payload;
diff_patch_hunk *hunk;
git_patch_diff *patch = payload;
git_patch_hunk *hunk;
GIT_UNUSED(delta);
hunk = git_array_alloc(patch->hunks);
hunk = git_array_alloc(patch->base.hunks);
GITERR_CHECK_ALLOC(hunk);
memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
patch->header_size += hunk_->header_len;
patch->base.header_size += hunk_->header_len;
hunk->line_start = git_array_size(patch->lines);
hunk->line_start = git_array_size(patch->base.lines);
hunk->line_count = 0;
return 0;
}
static int diff_patch_line_cb(
static int patch_diff_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk_,
const git_diff_line *line_,
void *payload)
{
git_patch *patch = payload;
diff_patch_hunk *hunk;
git_patch_diff *patch = payload;
git_patch_hunk *hunk;
git_diff_line *line;
GIT_UNUSED(delta);
GIT_UNUSED(hunk_);
hunk = git_array_last(patch->hunks);
hunk = git_array_last(patch->base.hunks);
assert(hunk); /* programmer error if no hunk is available */
line = git_array_alloc(patch->lines);
line = git_array_alloc(patch->base.lines);
GITERR_CHECK_ALLOC(line);
memcpy(line, line_, sizeof(*line));
/* do some bookkeeping so we can provide old/new line numbers */
patch->content_size += line->content_len;
patch->base.content_size += line->content_len;
if (line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION)
patch->content_size += 1;
patch->base.content_size += 1;
else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
patch->content_size += 1;
patch->context_size += line->content_len + 1;
patch->base.content_size += 1;
patch->base.context_size += line->content_len + 1;
} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
patch->context_size += line->content_len;
patch->base.context_size += line->content_len;
hunk->line_count++;
......@@ -1114,7 +925,7 @@ static int diff_patch_line_cb(
}
static void diff_output_init(
git_diff_output *out,
git_patch_diff_output *out,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
......@@ -1133,14 +944,14 @@ static void diff_output_init(
out->payload = payload;
}
static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
static void diff_output_to_patch(git_patch_diff_output *out, git_patch_diff *patch)
{
diff_output_init(
out,
NULL,
diff_patch_file_cb,
diff_patch_binary_cb,
diff_patch_hunk_cb,
diff_patch_line_cb,
patch_diff_file_cb,
patch_diff_binary_cb,
git_patch_hunk_cb,
patch_diff_line_cb,
patch);
}
......@@ -10,60 +10,40 @@
#include "common.h"
#include "diff.h"
#include "diff_file.h"
#include "array.h"
#include "git2/patch.h"
/* cached information about a hunk in a diff */
typedef struct diff_patch_hunk {
git_diff_hunk hunk;
size_t line_start;
size_t line_count;
} diff_patch_hunk;
#include "patch.h"
enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2),
GIT_PATCH_DIFF_ALLOCATED = (1 << 0),
GIT_PATCH_DIFF_INITIALIZED = (1 << 1),
GIT_PATCH_DIFF_LOADED = (1 << 2),
/* the two sides are different */
GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
GIT_PATCH_DIFF_DIFFABLE = (1 << 3),
/* the difference between the two sides has been computed */
GIT_DIFF_PATCH_DIFFED = (1 << 4),
GIT_DIFF_PATCH_FLATTENED = (1 << 5),
GIT_PATCH_DIFF_DIFFED = (1 << 4),
GIT_PATCH_DIFF_FLATTENED = (1 << 5),
};
struct git_patch {
git_refcount rc;
struct git_patch_diff {
struct git_patch base;
git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
git_diff_options diff_opts;
git_diff_delta *delta;
size_t delta_index;
git_diff_file_content ofile;
git_diff_file_content nfile;
uint32_t flags;
git_diff_binary binary;
git_array_t(diff_patch_hunk) hunks;
git_array_t(git_diff_line) lines;
size_t content_size, context_size, header_size;
git_pool flattened;
};
extern git_diff *git_patch__diff(git_patch *);
typedef struct git_patch_diff git_patch_diff;
extern git_diff_driver *git_patch__driver(git_patch *);
extern git_diff_driver *git_patch_diff_driver(git_patch_diff *);
extern void git_patch__old_data(char **, size_t *, git_patch *);
extern void git_patch__new_data(char **, size_t *, git_patch *);
extern void git_patch_diff_old_data(char **, size_t *, git_patch_diff *);
extern void git_patch_diff_new_data(char **, size_t *, git_patch_diff *);
extern int git_patch__invoke_callbacks(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload);
typedef struct git_patch_diff_output git_patch_diff_output;
typedef struct git_diff_output git_diff_output;
struct git_diff_output {
struct git_patch_diff_output {
/* these callbacks are issued with the diff data */
git_diff_file_cb file_cb;
git_diff_binary_cb binary_cb;
......@@ -77,7 +57,7 @@ struct git_diff_output {
/* this callback is used to do the diff and drive the other callbacks.
* see diff_xdiff.h for how to use this in practice for now.
*/
int (*diff_cb)(git_diff_output *output, git_patch *patch);
int (*diff_cb)(git_patch_diff_output *output, git_patch_diff *patch);
};
#endif
#include "git2/patch.h"
#include "patch.h"
#include "path.h"
#define parse_err(...) \
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
typedef struct {
git_patch base;
git_diff_file old_file;
git_diff_file new_file;
} git_patch_parsed;
typedef struct {
const char *content;
size_t content_len;
const char *line;
size_t line_len;
size_t line_num;
size_t remain;
/* TODO: move this into the parse struct? its lifecycle is odd... */
char *header_new_path;
char *header_old_path;
} patch_parse_ctx;
static void parse_advance_line(patch_parse_ctx *ctx)
{
ctx->line += ctx->line_len;
ctx->remain -= ctx->line_len;
ctx->line_len = git__linenlen(ctx->line, ctx->remain);
ctx->line_num++;
}
static void parse_advance_chars(patch_parse_ctx *ctx, size_t char_cnt)
{
ctx->line += char_cnt;
ctx->remain -= char_cnt;
ctx->line_len -= char_cnt;
}
static int parse_advance_expected(
patch_parse_ctx *ctx,
const char *expected,
size_t expected_len)
{
if (ctx->line_len < expected_len)
return -1;
if (memcmp(ctx->line, expected, expected_len) != 0)
return -1;
parse_advance_chars(ctx, expected_len);
return 0;
}
static int parse_advance_ws(patch_parse_ctx *ctx)
{
int ret = -1;
while (ctx->line_len > 0 &&
ctx->line[0] != '\n' &&
git__isspace(ctx->line[0])) {
ctx->line++;
ctx->line_len--;
ctx->remain--;
ret = 0;
}
return ret;
}
static int parse_advance_nl(patch_parse_ctx *ctx)
{
if (ctx->line_len != 1 || ctx->line[0] != '\n')
return -1;
parse_advance_line(ctx);
return 0;
}
static int header_path_len(patch_parse_ctx *ctx)
{
bool inquote = 0;
bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"');
size_t len;
for (len = quoted; len < ctx->line_len; len++) {
if (!quoted && git__isspace(ctx->line[len]))
break;
else if (quoted && !inquote && ctx->line[len] == '"') {
len++;
break;
}
inquote = (!inquote && ctx->line[len] == '\\');
}
return len;
}
static int parse_header_path_buf(git_buf *path, patch_parse_ctx *ctx)
{
int path_len, error = 0;
path_len = header_path_len(ctx);
if ((error = git_buf_put(path, ctx->line, path_len)) < 0)
goto done;
parse_advance_chars(ctx, path_len);
git_buf_rtrim(path);
if (path->size > 0 && path->ptr[0] == '"')
error = git_buf_unquote(path);
if (error < 0)
goto done;
git_path_squash_slashes(path);
done:
return error;
}
static int parse_header_path(char **out, patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
int error = parse_header_path_buf(&path, ctx);
*out = git_buf_detach(&path);
return error;
}
static int parse_header_git_oldpath(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
return parse_header_path((char **)&patch->old_file.path, ctx);
}
static int parse_header_git_newpath(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
return parse_header_path((char **)&patch->new_file.path, ctx);
}
static int parse_header_mode(uint16_t *mode, patch_parse_ctx *ctx)
{
const char *end;
int32_t m;
int ret;
if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]))
return parse_err("invalid file mode at line %d", ctx->line_num);
if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0)
return ret;
if (m > UINT16_MAX)
return -1;
*mode = (uint16_t)m;
parse_advance_chars(ctx, (end - ctx->line));
return ret;
}
static int parse_header_oid(
git_oid *oid,
size_t *oid_len,
patch_parse_ctx *ctx)
{
size_t len;
for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) {
if (!git__isxdigit(ctx->line[len]))
break;
}
if (len < GIT_OID_MINPREFIXLEN ||
git_oid_fromstrn(oid, ctx->line, len) < 0)
return parse_err("invalid hex formatted object id at line %d",
ctx->line_num);
parse_advance_chars(ctx, len);
*oid_len = len;
return 0;
}
static int parse_header_git_index(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
/*
* TODO: we read the prefix provided in the diff into the delta's id
* field, but do not mark is at an abbreviated id.
*/
size_t oid_len, nid_len;
if (parse_header_oid(&patch->base.delta->old_file.id, &oid_len, ctx) < 0 ||
parse_advance_expected(ctx, "..", 2) < 0 ||
parse_header_oid(&patch->base.delta->new_file.id, &nid_len, ctx) < 0)
return -1;
if (ctx->line_len > 0 && ctx->line[0] == ' ') {
uint16_t mode;
parse_advance_chars(ctx, 1);
if (parse_header_mode(&mode, ctx) < 0)
return -1;
if (!patch->base.delta->new_file.mode)
patch->base.delta->new_file.mode = mode;
if (!patch->base.delta->old_file.mode)
patch->base.delta->old_file.mode = mode;
}
return 0;
}
static int parse_header_git_oldmode(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->old_file.mode, ctx);
}
static int parse_header_git_newmode(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->new_file.mode, ctx);
}
static int parse_header_git_deletedfilemode(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
git__free((char *)patch->old_file.path);
patch->old_file.path = NULL;
patch->base.delta->status = GIT_DELTA_DELETED;
return parse_header_mode(&patch->old_file.mode, ctx);
}
static int parse_header_git_newfilemode(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
git__free((char *)patch->new_file.path);
patch->new_file.path = NULL;
patch->base.delta->status = GIT_DELTA_ADDED;
return parse_header_mode(&patch->new_file.mode, ctx);
}
static int parse_header_rename(
char **out,
char **header_path,
patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
size_t header_path_len, prefix_len;
if (*header_path == NULL)
return parse_err("rename without proper git diff header at line %d",
ctx->line_num);
header_path_len = strlen(*header_path);
if (parse_header_path_buf(&path, ctx) < 0)
return -1;
if (header_path_len < git_buf_len(&path))
return parse_err("rename path is invalid at line %d", ctx->line_num);
/* This sanity check exists because git core uses the data in the
* "rename from" / "rename to" lines, but it's formatted differently
* than the other paths and lacks the normal prefix. This irregularity
* causes us to ignore these paths (we always store the prefixed paths)
* but instead validate that they match the suffix of the paths we parsed
* since we would behave differently from git core if they ever differed.
* Instead, we raise an error, rather than parsing differently.
*/
prefix_len = header_path_len - path.size;
if (strncmp(*header_path + prefix_len, path.ptr, path.size) != 0 ||
(prefix_len > 0 && (*header_path)[prefix_len - 1] != '/'))
return parse_err("rename path does not match header at line %d",
ctx->line_num);
*out = *header_path;
*header_path = NULL;
git_buf_free(&path);
return 0;
}
static int parse_header_renamefrom(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
patch->base.delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->old_file.path,
&ctx->header_old_path,
ctx);
}
static int parse_header_renameto(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
patch->base.delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->new_file.path,
&ctx->header_new_path,
ctx);
}
static int parse_header_percent(uint16_t *out, patch_parse_ctx *ctx)
{
int32_t val;
const char *end;
if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) ||
git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0)
return -1;
parse_advance_chars(ctx, (end - ctx->line));
if (parse_advance_expected(ctx, "%", 1) < 0)
return -1;
if (val > 100)
return -1;
*out = val;
return 0;
}
static int parse_header_similarity(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0)
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
return 0;
}
static int parse_header_dissimilarity(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
uint16_t dissimilarity;
if (parse_header_percent(&dissimilarity, ctx) < 0)
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
patch->base.delta->similarity = 100 - dissimilarity;
return 0;
}
typedef struct {
const char *str;
int(*fn)(git_patch_parsed *, patch_parse_ctx *);
} header_git_op;
static const header_git_op header_git_ops[] = {
{ "@@ -", NULL },
{ "GIT binary patch", NULL },
{ "--- ", parse_header_git_oldpath },
{ "+++ ", parse_header_git_newpath },
{ "index ", parse_header_git_index },
{ "old mode ", parse_header_git_oldmode },
{ "new mode ", parse_header_git_newmode },
{ "deleted file mode ", parse_header_git_deletedfilemode },
{ "new file mode ", parse_header_git_newfilemode },
{ "rename from ", parse_header_renamefrom },
{ "rename to ", parse_header_renameto },
{ "rename old ", parse_header_renamefrom },
{ "rename new ", parse_header_renameto },
{ "similarity index ", parse_header_similarity },
{ "dissimilarity index ", parse_header_dissimilarity },
};
static int parse_header_git(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
size_t i;
int error = 0;
/* Parse the diff --git line */
if (parse_advance_expected(ctx, "diff --git ", 11) < 0)
return parse_err("corrupt git diff header at line %d", ctx->line_num);
if (parse_header_path(&ctx->header_old_path, ctx) < 0)
return parse_err("corrupt old path in git diff header at line %d",
ctx->line_num);
if (parse_advance_ws(ctx) < 0 ||
parse_header_path(&ctx->header_new_path, ctx) < 0)
return parse_err("corrupt new path in git diff header at line %d",
ctx->line_num);
/* Parse remaining header lines */
for (parse_advance_line(ctx); ctx->remain > 0; parse_advance_line(ctx)) {
if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n')
break;
for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) {
const header_git_op *op = &header_git_ops[i];
size_t op_len = strlen(op->str);
if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0)
continue;
/* Do not advance if this is the patch separator */
if (op->fn == NULL)
goto done;
parse_advance_chars(ctx, op_len);
if ((error = op->fn(patch, ctx)) < 0)
goto done;
parse_advance_ws(ctx);
parse_advance_expected(ctx, "\n", 1);
if (ctx->line_len > 0) {
error = parse_err("trailing data at line %d", ctx->line_num);
goto done;
}
break;
}
}
done:
return error;
}
static int parse_number(git_off_t *out, patch_parse_ctx *ctx)
{
const char *end;
int64_t num;
if (!git__isdigit(ctx->line[0]))
return -1;
if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0)
return -1;
if (num < 0)
return -1;
*out = num;
parse_advance_chars(ctx, (end - ctx->line));
return 0;
}
static int parse_int(int *out, patch_parse_ctx *ctx)
{
git_off_t num;
if (parse_number(&num, ctx) < 0 || !git__is_int(num))
return -1;
*out = (int)num;
return 0;
}
static int parse_hunk_header(
git_patch_hunk *hunk,
patch_parse_ctx *ctx)
{
const char *header_start = ctx->line;
hunk->hunk.old_lines = 1;
hunk->hunk.new_lines = 1;
if (parse_advance_expected(ctx, "@@ -", 4) < 0 ||
parse_int(&hunk->hunk.old_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected(ctx, ",", 1) < 0 ||
parse_int(&hunk->hunk.old_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected(ctx, " +", 2) < 0 ||
parse_int(&hunk->hunk.new_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected(ctx, ",", 1) < 0 ||
parse_int(&hunk->hunk.new_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected(ctx, " @@", 3) < 0)
goto fail;
parse_advance_line(ctx);
if (!hunk->hunk.old_lines && !hunk->hunk.new_lines)
goto fail;
hunk->hunk.header_len = ctx->line - header_start;
if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1))
return parse_err("oversized patch hunk header at line %d",
ctx->line_num);
memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len);
hunk->hunk.header[hunk->hunk.header_len] = '\0';
return 0;
fail:
giterr_set(GITERR_PATCH, "invalid patch hunk header at line %d",
ctx->line_num);
return -1;
}
static int parse_hunk_body(
git_patch_parsed *patch,
git_patch_hunk *hunk,
patch_parse_ctx *ctx)
{
git_diff_line *line;
int error = 0;
int oldlines = hunk->hunk.old_lines;
int newlines = hunk->hunk.new_lines;
for (;
ctx->remain > 4 && (oldlines || newlines) &&
memcmp(ctx->line, "@@ -", 4) != 0;
parse_advance_line(ctx)) {
int origin;
int prefix = 1;
if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') {
error = parse_err("invalid patch instruction at line %d",
ctx->line_num);
goto done;
}
switch (ctx->line[0]) {
case '\n':
prefix = 0;
case ' ':
origin = GIT_DIFF_LINE_CONTEXT;
oldlines--;
newlines--;
break;
case '-':
origin = GIT_DIFF_LINE_DELETION;
oldlines--;
break;
case '+':
origin = GIT_DIFF_LINE_ADDITION;
newlines--;
break;
default:
error = parse_err("invalid patch hunk at line %d", ctx->line_num);
goto done;
}
line = git_array_alloc(patch->base.lines);
GITERR_CHECK_ALLOC(line);
memset(line, 0x0, sizeof(git_diff_line));
line->content = ctx->line + prefix;
line->content_len = ctx->line_len - prefix;
line->content_offset = ctx->content_len - ctx->remain;
line->origin = origin;
hunk->line_count++;
}
if (oldlines || newlines) {
error = parse_err(
"invalid patch hunk, expected %d old lines and %d new lines",
hunk->hunk.old_lines, hunk->hunk.new_lines);
goto done;
}
/* Handle "\ No newline at end of file". Only expect the leading
* backslash, though, because the rest of the string could be
* localized. Because `diff` optimizes for the case where you
* want to apply the patch by hand.
*/
if (ctx->line_len >= 2 && memcmp(ctx->line, "\\ ", 2) == 0 &&
git_array_size(patch->base.lines) > 0) {
line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1);
if (line->content_len < 1) {
error = parse_err("cannot trim trailing newline of empty line");
goto done;
}
line->content_len--;
parse_advance_line(ctx);
}
done:
return error;
}
static int parsed_patch_header(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
int error = 0;
for (ctx->line = ctx->content; ctx->remain > 0; parse_advance_line(ctx)) {
/* This line is too short to be a patch header. */
if (ctx->line_len < 6)
continue;
/* This might be a hunk header without a patch header, provide a
* sensible error message. */
if (memcmp(ctx->line, "@@ -", 4) == 0) {
size_t line_num = ctx->line_num;
git_patch_hunk hunk;
/* If this cannot be parsed as a hunk header, it's just leading
* noise, continue.
*/
if (parse_hunk_header(&hunk, ctx) < 0) {
giterr_clear();
continue;
}
error = parse_err("invalid hunk header outside patch at line %d",
line_num);
goto done;
}
/* This buffer is too short to contain a patch. */
if (ctx->remain < ctx->line_len + 6)
break;
/* A proper git patch */
if (ctx->line_len >= 11 && memcmp(ctx->line, "diff --git ", 11) == 0) {
if ((error = parse_header_git(patch, ctx)) < 0)
goto done;
/* For modechange only patches, it does not include filenames;
* instead we need to use the paths in the diff --git header.
*/
if (!patch->old_file.path && !patch->new_file.path) {
if (!ctx->header_old_path || !ctx->header_new_path) {
error = parse_err("git diff header lacks old / new paths");
goto done;
}
patch->old_file.path = ctx->header_old_path;
ctx->header_old_path = NULL;
patch->new_file.path = ctx->header_new_path;
ctx->header_new_path = NULL;
}
goto done;
}
error = 0;
continue;
}
error = parse_err("no header in patch file");
done:
return error;
}
static int parsed_patch_binary_side(
git_diff_binary_file *binary,
patch_parse_ctx *ctx)
{
git_diff_binary_t type = GIT_DIFF_BINARY_NONE;
git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT;
git_off_t len;
int error = 0;
if (ctx->line_len >= 8 && memcmp(ctx->line, "literal ", 8) == 0) {
type = GIT_DIFF_BINARY_LITERAL;
parse_advance_chars(ctx, 8);
}
else if (ctx->line_len >= 6 && memcmp(ctx->line, "delta ", 6) == 0) {
type = GIT_DIFF_BINARY_DELTA;
parse_advance_chars(ctx, 6);
}
else {
error = parse_err("unknown binary delta type at line %d", ctx->line_num);
goto done;
}
if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) {
error = parse_err("invalid binary size at line %d", ctx->line_num);
goto done;
}
while (ctx->line_len) {
char c = ctx->line[0];
size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size;
if (c == '\n')
break;
else if (c >= 'A' && c <= 'Z')
decoded_len = c - 'A' + 1;
else if (c >= 'a' && c <= 'z')
decoded_len = c - 'a' + (('z' - 'a') + 1) + 1;
if (!decoded_len) {
error = parse_err("invalid binary length at line %d", ctx->line_num);
goto done;
}
parse_advance_chars(ctx, 1);
encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
if (encoded_len > ctx->line_len - 1) {
error = parse_err("truncated binary data at line %d", ctx->line_num);
goto done;
}
if ((error = git_buf_decode_base85(
&decoded, ctx->line, encoded_len, decoded_len)) < 0)
goto done;
if (decoded.size - decoded_orig != decoded_len) {
error = parse_err("truncated binary data at line %d", ctx->line_num);
goto done;
}
parse_advance_chars(ctx, encoded_len);
if (parse_advance_nl(ctx) < 0) {
error = parse_err("trailing data at line %d", ctx->line_num);
goto done;
}
}
binary->type = type;
binary->inflatedlen = (size_t)len;
binary->datalen = decoded.size;
binary->data = git_buf_detach(&decoded);
done:
git_buf_free(&base85);
git_buf_free(&decoded);
return error;
}
static int parsed_patch_binary(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
int error;
if (parse_advance_expected(ctx, "GIT binary patch", 16) < 0 ||
parse_advance_nl(ctx) < 0)
return parse_err("corrupt git binary header at line %d", ctx->line_num);
/* parse old->new binary diff */
if ((error = parsed_patch_binary_side(
&patch->base.binary.new_file, ctx)) < 0)
return error;
if (parse_advance_nl(ctx) < 0)
return parse_err("corrupt git binary separator at line %d",
ctx->line_num);
/* parse new->old binary diff */
if ((error = parsed_patch_binary_side(
&patch->base.binary.old_file, ctx)) < 0)
return error;
patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
return 0;
}
static int parsed_patch_hunks(
git_patch_parsed *patch,
patch_parse_ctx *ctx)
{
git_patch_hunk *hunk;
int error = 0;
for (; ctx->line_len > 4 && memcmp(ctx->line, "@@ -", 4) == 0; ) {
hunk = git_array_alloc(patch->base.hunks);
GITERR_CHECK_ALLOC(hunk);
memset(hunk, 0, sizeof(git_patch_hunk));
hunk->line_start = git_array_size(patch->base.lines);
hunk->line_count = 0;
if ((error = parse_hunk_header(hunk, ctx)) < 0 ||
(error = parse_hunk_body(patch, hunk, ctx)) < 0)
goto done;
}
done:
return error;
}
static int parsed_patch_body(
git_patch_parsed *patch, patch_parse_ctx *ctx)
{
if (ctx->line_len >= 16 && memcmp(ctx->line, "GIT binary patch", 16) == 0)
return parsed_patch_binary(patch, ctx);
else if (ctx->line_len >= 4 && memcmp(ctx->line, "@@ -", 4) == 0)
return parsed_patch_hunks(patch, ctx);
return 0;
}
static int check_patch(git_patch_parsed *patch)
{
if (!patch->old_file.path && patch->base.delta->status != GIT_DELTA_ADDED)
return parse_err("missing old file path");
if (!patch->new_file.path && patch->base.delta->status != GIT_DELTA_DELETED)
return parse_err("missing new file path");
if (patch->old_file.path && patch->new_file.path) {
if (!patch->new_file.mode)
patch->new_file.mode = patch->old_file.mode;
}
if (patch->base.delta->status == GIT_DELTA_MODIFIED &&
!(patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) &&
patch->new_file.mode == patch->old_file.mode &&
git_array_size(patch->base.hunks) == 0)
return parse_err("patch with no hunks");
return 0;
}
static const git_diff_file *parsed_patch_newfile(git_patch *p)
{
git_patch_parsed *patch = (git_patch_parsed *)p;
return &patch->new_file;
}
static const git_diff_file *parsed_patch_oldfile(git_patch *p)
{
git_patch_parsed *patch = (git_patch_parsed *)p;
return &patch->old_file;
}
int git_patch_from_patchfile(
git_patch **out,
const char *content,
size_t content_len)
{
patch_parse_ctx ctx = { 0 };
git_patch_parsed *patch;
int error = 0;
*out = NULL;
patch = git__calloc(1, sizeof(git_patch_parsed));
GITERR_CHECK_ALLOC(patch);
patch->base.newfile = parsed_patch_newfile;
patch->base.oldfile = parsed_patch_oldfile;
patch->base.delta = git__calloc(1, sizeof(git_diff_delta));
patch->base.delta->status = GIT_DELTA_MODIFIED;
ctx.content = content;
ctx.content_len = content_len;
ctx.remain = content_len;
if ((error = parsed_patch_header(patch, &ctx)) < 0 ||
(error = parsed_patch_body(patch, &ctx)) < 0 ||
(error = check_patch(patch)) < 0)
goto done;
GIT_REFCOUNT_INC(patch);
*out = &patch->base;
done:
git__free(ctx.header_old_path);
git__free(ctx.header_new_path);
return error;
}
......@@ -106,6 +106,7 @@ void test_apply_fromfile__change_middle_nocontext(void)
&diff_opts, "b/file.txt", 0100644));
}
void test_apply_fromfile__change_firstline(void)
{
cl_git_pass(validate_and_apply_patchfile(
......
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