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 @@ ...@@ -10,7 +10,7 @@
#include "git2/patch.h" #include "git2/patch.h"
#include "git2/filter.h" #include "git2/filter.h"
#include "array.h" #include "array.h"
#include "diff_patch.h" #include "patch.h"
#include "fileops.h" #include "fileops.h"
#include "apply.h" #include "apply.h"
#include "delta.h" #include "delta.h"
...@@ -163,7 +163,7 @@ static int update_hunk( ...@@ -163,7 +163,7 @@ static int update_hunk(
static int apply_hunk( static int apply_hunk(
patch_image *image, patch_image *image,
git_patch *patch, git_patch *patch,
diff_patch_hunk *hunk) git_patch_hunk *hunk)
{ {
patch_image preimage, postimage; patch_image preimage, postimage;
size_t line_num, i; size_t line_num, i;
...@@ -218,7 +218,7 @@ static int apply_hunks( ...@@ -218,7 +218,7 @@ static int apply_hunks(
size_t source_len, size_t source_len,
git_patch *patch) git_patch *patch)
{ {
diff_patch_hunk *hunk; git_patch_hunk *hunk;
git_diff_line *line; git_diff_line *line;
patch_image image; patch_image image;
size_t i; size_t i;
...@@ -340,9 +340,11 @@ int git_apply__patch( ...@@ -340,9 +340,11 @@ int git_apply__patch(
*mode_out = 0; *mode_out = 0;
if (patch->delta->status != GIT_DELTA_DELETED) { if (patch->delta->status != GIT_DELTA_DELETED) {
filename = git__strdup(patch->nfile.file->path); const git_diff_file *newfile = patch->newfile(patch);
mode = patch->nfile.file->mode ?
patch->nfile.file->mode : GIT_FILEMODE_BLOB; filename = git__strdup(newfile->path);
mode = newfile->mode ?
newfile->mode : GIT_FILEMODE_BLOB;
} }
if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
......
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "git2/attr.h" #include "git2/attr.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "strmap.h" #include "strmap.h"
#include "map.h" #include "map.h"
......
...@@ -6,7 +6,8 @@ ...@@ -6,7 +6,8 @@
*/ */
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h" #include "diff_file.h"
#include "patch_diff.h"
#include "fileops.h" #include "fileops.h"
#include "zstream.h" #include "zstream.h"
#include "blob.h" #include "blob.h"
...@@ -14,19 +15,19 @@ ...@@ -14,19 +15,19 @@
#include "git2/sys/diff.h" #include "git2/sys/diff.h"
typedef struct { typedef struct {
git_diff *diff;
git_diff_format_t format; git_diff_format_t format;
git_diff_line_cb print_cb; git_diff_line_cb print_cb;
void *payload; void *payload;
git_buf *buf; git_buf *buf;
git_diff_line line;
const char *old_prefix;
const char *new_prefix;
uint32_t flags; uint32_t flags;
int oid_strlen; int oid_strlen;
git_diff_line line;
unsigned int int (*strcomp)(const char *, const char *);
content_loaded : 1,
content_allocated : 1;
git_diff_file_content *ofile;
git_diff_file_content *nfile;
} diff_print_info; } diff_print_info;
static int diff_print_info_init__common( static int diff_print_info_init__common(
...@@ -74,11 +75,13 @@ static int diff_print_info_init_fromdiff( ...@@ -74,11 +75,13 @@ static int diff_print_info_init_fromdiff(
memset(pi, 0, sizeof(diff_print_info)); memset(pi, 0, sizeof(diff_print_info));
pi->diff = diff;
if (diff) { if (diff) {
pi->flags = diff->opts.flags; pi->flags = diff->opts.flags;
pi->oid_strlen = diff->opts.id_abbrev; 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); return diff_print_info_init__common(pi, out, repo, format, cb, payload);
...@@ -92,24 +95,16 @@ static int diff_print_info_init_frompatch( ...@@ -92,24 +95,16 @@ static int diff_print_info_init_frompatch(
git_diff_line_cb cb, git_diff_line_cb cb,
void *payload) void *payload)
{ {
git_repository *repo;
assert(patch); assert(patch);
repo = patch->diff ? patch->diff->repo : NULL;
memset(pi, 0, sizeof(diff_print_info)); memset(pi, 0, sizeof(diff_print_info));
pi->diff = patch->diff;
pi->flags = patch->diff_opts.flags; pi->flags = patch->diff_opts.flags;
pi->oid_strlen = patch->diff_opts.id_abbrev; 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; return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
pi->ofile = &patch->ofile;
pi->nfile = &patch->nfile;
return diff_print_info_init__common(pi, out, repo, format, cb, payload);
} }
static char diff_pick_suffix(int mode) static char diff_pick_suffix(int mode)
...@@ -173,8 +168,8 @@ static int diff_print_one_name_status( ...@@ -173,8 +168,8 @@ static int diff_print_one_name_status(
diff_print_info *pi = data; diff_print_info *pi = data;
git_buf *out = pi->buf; git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status); char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
int (*strcomp)(const char *, const char *) = int(*strcomp)(const char *, const char *) = pi->strcomp ?
pi->diff ? pi->diff->strcomp : git__strcmp; pi->strcomp : git__strcmp;
GIT_UNUSED(progress); GIT_UNUSED(progress);
...@@ -367,39 +362,6 @@ static int format_binary( ...@@ -367,39 +362,6 @@ static int format_binary(
return 0; 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( static int diff_print_patch_file_binary(
diff_print_info *pi, git_diff_delta *delta, diff_print_info *pi, git_diff_delta *delta,
const char *old_pfx, const char *new_pfx, const char *old_pfx, const char *new_pfx,
...@@ -411,10 +373,6 @@ static int diff_print_patch_file_binary( ...@@ -411,10 +373,6 @@ static int diff_print_patch_file_binary(
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
goto noshow; 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) if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
return 0; return 0;
...@@ -450,9 +408,9 @@ static int diff_print_patch_file( ...@@ -450,9 +408,9 @@ static int diff_print_patch_file(
int error; int error;
diff_print_info *pi = data; diff_print_info *pi = data;
const char *oldpfx = 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 = 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) || bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
(pi->flags & GIT_DIFF_FORCE_BINARY); (pi->flags & GIT_DIFF_FORCE_BINARY);
...@@ -488,9 +446,9 @@ static int diff_print_patch_binary( ...@@ -488,9 +446,9 @@ static int diff_print_patch_binary(
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
const char *old_pfx = 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 = 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; int error;
git_buf_clear(pi->buf); git_buf_clear(pi->buf);
...@@ -585,43 +543,11 @@ int git_diff_print( ...@@ -585,43 +543,11 @@ int git_diff_print(
giterr_set_after_callback_function(error, "git_diff_print"); giterr_set_after_callback_function(error, "git_diff_print");
} }
git__free(pi.nfile);
git__free(pi.ofile);
git_buf_free(&buf); git_buf_free(&buf);
return error; 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( int git_diff_print_callback__to_buf(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk, const git_diff_hunk *hunk,
...@@ -662,6 +588,37 @@ int git_diff_print_callback__to_file_handle( ...@@ -662,6 +588,37 @@ int git_diff_print_callback__to_file_handle(
return 0; 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 */ /* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch) int git_patch_to_buf(git_buf *out, git_patch *patch)
{ {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#include "common.h" #include "common.h"
#include "vector.h" #include "vector.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h" #include "patch_diff.h"
#define DIFF_RENAME_FILE_SEPARATOR " => " #define DIFF_RENAME_FILE_SEPARATOR " => "
#define STATS_FULL_MIN_SCALE 7 #define STATS_FULL_MIN_SCALE 7
...@@ -190,8 +190,9 @@ int git_diff_get_stats( ...@@ -190,8 +190,9 @@ int git_diff_get_stats(
break; break;
/* keep a count of renames because it will affect formatting */ /* 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); namelen = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) { if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
namelen += strlen(delta->old_file.path); namelen += strlen(delta->old_file.path);
......
...@@ -8,8 +8,8 @@ ...@@ -8,8 +8,8 @@
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "diff_patch.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"
#include "patch_diff.h"
static int git_xdiff_scan_int(const char **str, int *value) static int git_xdiff_scan_int(const char **str, int *value)
{ {
...@@ -56,7 +56,7 @@ fail: ...@@ -56,7 +56,7 @@ fail:
typedef struct { typedef struct {
git_xdiff_output *xo; git_xdiff_output *xo;
git_patch *patch; git_patch_diff *patch;
git_diff_hunk hunk; git_diff_hunk hunk;
int old_lineno, new_lineno; int old_lineno, new_lineno;
mmfile_t xd_old_data, xd_new_data; mmfile_t xd_old_data, xd_new_data;
...@@ -110,9 +110,9 @@ static int diff_update_lines( ...@@ -110,9 +110,9 @@ static int diff_update_lines(
static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
{ {
git_xdiff_info *info = priv; git_xdiff_info *info = priv;
git_patch *patch = info->patch; git_patch_diff *patch = info->patch;
const git_diff_delta *delta = git_patch_get_delta(patch); const git_diff_delta *delta = patch->base.delta;
git_diff_output *output = &info->xo->output; git_patch_diff_output *output = &info->xo->output;
git_diff_line line; git_diff_line line;
if (len == 1) { if (len == 1) {
...@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
return output->error; 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_output *xo = (git_xdiff_output *)output;
git_xdiff_info info; git_xdiff_info info;
...@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) ...@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
xo->callback.priv = &info; xo->callback.priv = &info;
git_diff_find_context_init( 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; xo->config.find_func_priv = &findctxt;
if (xo->config.find_func != NULL) if (xo->config.find_func != NULL)
...@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) ...@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
* updates are needed to xo->params.flags * updates are needed to xo->params.flags
*/ */
git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); git_patch_diff_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_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
......
...@@ -8,20 +8,20 @@ ...@@ -8,20 +8,20 @@
#define INCLUDE_diff_xdiff_h__ #define INCLUDE_diff_xdiff_h__
#include "diff.h" #include "diff.h"
#include "diff_patch.h"
#include "xdiff/xdiff.h" #include "xdiff/xdiff.h"
#include "patch_diff.h"
/* xdiff cannot cope with large files. these files should not be passed to /* xdiff cannot cope with large files. these files should not be passed to
* xdiff. callers should treat these large files as binary. * xdiff. callers should treat these large files as binary.
*/ */
#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023) #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 * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
* of the output to use xdiff to generate the diffs. * of the output to use xdiff to generate the diffs.
*/ */
typedef struct { typedef struct {
git_diff_output output; git_patch_diff_output output;
xdemitconf_t config; xdemitconf_t config;
xpparam_t params; xpparam_t params;
......
#include "git2/patch.h" #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 { int git_patch__invoke_callbacks(
const char *content; git_patch *patch,
size_t content_len; git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
const char *line; git_diff_hunk_cb hunk_cb,
size_t line_len; git_diff_line_cb line_cb,
size_t line_num; void *payload)
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)
{
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; int error = 0;
uint32_t i, j;
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) if (file_cb)
goto done; error = file_cb(patch->delta, 0, payload);
git_path_squash_slashes(path); if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (binary_cb)
error = binary_cb(patch->delta, &patch->binary, payload);
done:
return error; 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);
if (!hunk_cb && !line_cb)
return error; 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) for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
{ git_patch_hunk *h = git_array_get(patch->hunks, i);
return parse_header_path((char **)&patch->nfile.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])) if (hunk_cb)
return parse_err("invalid file mode at line %d", ctx->line_num); error = hunk_cb(patch->delta, &h->hunk, payload);
if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0) if (!line_cb)
return ret; continue;
if (m > UINT16_MAX)
return -1;
*mode = (uint16_t)m;
parse_advance_chars(ctx, (end - ctx->line));
return ret;
}
static int parse_header_oid( for (j = 0; !error && j < h->line_count; ++j) {
git_oid *oid, git_diff_line *l =
size_t *oid_len, git_array_get(patch->lines, h->line_start + j);
patch_parse_ctx *ctx)
{
size_t len;
for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) { error = line_cb(patch->delta, &h->hunk, l, payload);
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 *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->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);
if (parse_header_mode(&mode, ctx) < 0)
return -1;
if (!patch->delta->new_file.mode)
patch->delta->new_file.mode = mode;
if (!patch->delta->old_file.mode)
patch->delta->old_file.mode = mode;
} }
return 0; return error;
}
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( size_t git_patch_size(
git_patch *patch, git_patch *patch,
patch_parse_ctx *ctx) int include_context,
int include_hunk_headers,
int include_file_headers)
{ {
git__free((char *)patch->nfile.file->path); size_t out;
patch->nfile.file->path = NULL; assert(patch);
patch->delta->status = GIT_DELTA_ADDED;
return parse_header_mode(&patch->nfile.file->mode, ctx); out = patch->content_size;
}
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) if (!include_context)
return -1; out -= patch->context_size;
if (header_path_len < git_buf_len(&path)) if (include_hunk_headers)
return parse_err("rename path is invalid at line %d", ctx->line_num); out += patch->header_size;
/* This sanity check exists because git core uses the data in the if (include_file_headers) {
* "rename from" / "rename to" lines, but it's formatted differently git_buf file_header = GIT_BUF_INIT;
* 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 (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
return 0; giterr_clear();
} else
out += git_buf_len(&file_header);
static int parse_header_renamefrom(git_patch *patch, patch_parse_ctx *ctx)
{
patch->delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->ofile.file->path,
&ctx->header_old_path,
ctx);
}
static int parse_header_renameto(git_patch *patch, patch_parse_ctx *ctx)
{
patch->delta->status |= GIT_DELTA_RENAMED;
return parse_header_rename(
(char **)&patch->nfile.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 *patch, patch_parse_ctx *ctx) git_buf_free(&file_header);
{ }
if (parse_header_percent(&patch->delta->similarity, ctx) < 0)
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
return 0; return out;
} }
static int parse_header_dissimilarity(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)
{ {
uint16_t dissimilarity; size_t totals[3], idx;
if (parse_header_percent(&dissimilarity, ctx) < 0) memset(totals, 0, sizeof(totals));
return parse_err("invalid similarity percentage at line %d",
ctx->line_num);
patch->delta->similarity = 100 - dissimilarity; for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
git_diff_line *line = git_array_get(patch->lines, idx);
return 0; if (!line)
}
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(
git_patch *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; continue;
/* Do not advance if this is the patch separator */ switch (line->origin) {
if (op->fn == NULL) case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
goto done; case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
case GIT_DIFF_LINE_DELETION: totals[2]++; break;
parse_advance_chars(ctx, op_len); default:
/* diff --stat and --numstat don't count EOFNL marks because
if ((error = op->fn(patch, ctx)) < 0) * they will always be paired with a ADDITION or DELETION line.
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; break;
} }
} }
done: if (total_ctxt)
return error; *total_ctxt = totals[0];
} if (total_adds)
*total_adds = totals[1];
static int parse_number(git_off_t *out, patch_parse_ctx *ctx) if (total_dels)
{ *total_dels = totals[2];
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; return 0;
} }
static int parse_int(int *out, patch_parse_ctx *ctx) const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{ {
git_off_t num; assert(patch);
return patch->delta;
if (parse_number(&num, ctx) < 0 || !git__is_int(num))
return -1;
*out = (int)num;
return 0;
} }
static int parse_hunk_header( size_t git_patch_num_hunks(const git_patch *patch)
diff_patch_hunk *hunk,
patch_parse_ctx *ctx)
{ {
const char *header_start = ctx->line; assert(patch);
return git_array_size(patch->hunks);
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( static int patch_error_outofrange(const char *thing)
git_patch *patch,
diff_patch_hunk *hunk,
patch_parse_ctx *ctx)
{ {
git_diff_line *line; giterr_set(GITERR_INVALID, "patch %s index out of range", thing);
int error = 0; return GIT_ENOTFOUND;
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) int git_patch_get_hunk(
{ const git_diff_hunk **out,
GIT_UNUSED(patch); size_t *lines_in_hunk,
GIT_UNUSED(ctx);
return 1;
}
static int parse_patch_header(
git_patch *patch, git_patch *patch,
patch_parse_ctx *ctx) size_t hunk_idx)
{ {
int error = 0; git_patch_hunk *hunk;
assert(patch);
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 hunk = git_array_get(patch->hunks, hunk_idx);
* 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 if (!hunk) {
* noise, continue. if (out) *out = NULL;
*/ if (lines_in_hunk) *lines_in_hunk = 0;
if (parse_hunk_header(&hunk, ctx) < 0) { return patch_error_outofrange("hunk");
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 (out) *out = &hunk->hunk;
if (ctx->remain < ctx->line_len + 6) if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
break; return 0;
/* 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;
error = 0;
continue;
}
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;
}
}
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( int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
git_patch *patch,
patch_parse_ctx *ctx)
{ {
int error; git_patch_hunk *hunk;
assert(patch);
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 (!(hunk = git_array_get(patch->hunks, hunk_idx)))
return 0; return patch_error_outofrange("hunk");
return (int)hunk->line_count;
} }
static int parse_patch_hunks( int git_patch_get_line_in_hunk(
const git_diff_line **out,
git_patch *patch, git_patch *patch,
patch_parse_ctx *ctx) size_t hunk_idx,
size_t line_of_hunk)
{ {
diff_patch_hunk *hunk; git_patch_hunk *hunk;
int error = 0; git_diff_line *line;
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); assert(patch);
hunk->line_count = 0;
if ((error = parse_hunk_header(hunk, ctx)) < 0 || if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
(error = parse_hunk_body(patch, hunk, ctx)) < 0) if (out) *out = NULL;
goto done; return patch_error_outofrange("hunk");
} }
done: if (line_of_hunk >= hunk->line_count ||
return error; !(line = git_array_get(
} patch->lines, hunk->line_start + line_of_hunk))) {
if (out) *out = NULL;
static int parse_patch_body(git_patch *patch, patch_parse_ctx *ctx) return patch_error_outofrange("line");
{ }
if (ctx->line_len >= 16 && memcmp(ctx->line, "GIT binary patch", 16) == 0)
return parse_patch_binary(patch, ctx);
else if (ctx->line_len >= 4 && memcmp(ctx->line, "@@ -", 4) == 0)
return parse_patch_hunks(patch, ctx);
if (out) *out = line;
return 0; return 0;
} }
static int check_patch(git_patch *patch) static void git_patch__free(git_patch *patch)
{ {
if (!patch->ofile.file->path && patch->delta->status != GIT_DELTA_ADDED) git_array_clear(patch->lines);
return parse_err("missing old file path"); git_array_clear(patch->hunks);
if (!patch->nfile.file->path && patch->delta->status != GIT_DELTA_DELETED) git__free((char *)patch->binary.old_file.data);
return parse_err("missing new file path"); git__free((char *)patch->binary.new_file.data);
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 && if (patch->free_fn)
!(patch->delta->flags & GIT_DIFF_FLAG_BINARY) && patch->free_fn(patch);
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( void git_patch_free(git_patch *patch)
git_patch **out,
const char *content,
size_t content_len)
{ {
patch_parse_ctx ctx = {0}; if (patch)
git_patch *patch; GIT_REFCOUNT_DEC(patch, git_patch__free);
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;
} }
/*
* 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 @@ ...@@ -9,47 +9,82 @@
#include "diff.h" #include "diff.h"
#include "diff_file.h" #include "diff_file.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "diff_patch.h" #include "patch_diff.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"
#include "delta.h" #include "delta.h"
#include "zstream.h" #include "zstream.h"
#include "fileops.h" #include "fileops.h"
static void diff_output_init( 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*); 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; return;
if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
(patch->nfile.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 || else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
(patch->nfile.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->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_diff_update_binary(patch);
patch->flags |= GIT_DIFF_PATCH_INITIALIZED; patch->flags |= GIT_PATCH_DIFF_INITIALIZED;
if (patch->diff) if (patch->diff)
git_diff_addref(patch->diff); git_diff_addref(patch->diff);
} }
static int diff_patch_normalize_options( static int patch_diff_normalize_options(
git_diff_options *out, git_diff_options *out,
const git_diff_options *opts) const git_diff_options *opts)
{ {
...@@ -75,38 +110,40 @@ static int diff_patch_normalize_options( ...@@ -75,38 +110,40 @@ static int diff_patch_normalize_options(
return 0; return 0;
} }
static int diff_patch_init_from_diff( static int patch_diff_init(
git_patch *patch, git_diff *diff, size_t delta_index) git_patch_diff *patch, git_diff *diff, size_t delta_index)
{ {
int error = 0; int error = 0;
memset(patch, 0, sizeof(*patch)); memset(patch, 0, sizeof(*patch));
patch->diff = diff; patch->diff = diff;
patch->delta = git_vector_get(&diff->deltas, delta_index); patch->base.repo = diff->repo;
patch->base.delta = git_vector_get(&diff->deltas, delta_index);
patch->delta_index = delta_index; patch->delta_index = delta_index;
if ((error = diff_patch_normalize_options( if ((error = patch_diff_normalize_options(
&patch->diff_opts, &diff->opts)) < 0 || &patch->base.diff_opts, &diff->opts)) < 0 ||
(error = git_diff_file_content__init_from_diff( (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( (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; return error;
diff_patch_init_common(patch); patch_diff_init_common(patch);
return 0; return 0;
} }
static int diff_patch_alloc_from_diff( static int patch_diff_alloc_from_diff(
git_patch **out, git_diff *diff, size_t delta_index) git_patch_diff **out, git_diff *diff, size_t delta_index)
{ {
int error; 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); GITERR_CHECK_ALLOC(patch);
if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { if (!(error = patch_diff_init(patch, diff, delta_index))) {
patch->flags |= GIT_DIFF_PATCH_ALLOCATED; patch->flags |= GIT_PATCH_DIFF_ALLOCATED;
GIT_REFCOUNT_INC(patch); GIT_REFCOUNT_INC(patch);
} else { } else {
git__free(patch); git__free(patch);
...@@ -117,27 +154,27 @@ static int diff_patch_alloc_from_diff( ...@@ -117,27 +154,27 @@ static int diff_patch_alloc_from_diff(
return error; 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 false;
return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; 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; size_t olen, nlen;
if (patch->delta->status == GIT_DELTA_UNMODIFIED) if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
return false; return false;
/* if we've determined this to be binary (and we are not showing binary /* 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 * data) then we have skipped loading the map data. instead, query the
* file data itself. * file data itself.
*/ */
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
(patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
olen = (size_t)patch->ofile.file->size; olen = (size_t)patch->ofile.file->size;
nlen = (size_t)patch->nfile.file->size; nlen = (size_t)patch->nfile.file->size;
} else { } else {
...@@ -154,12 +191,12 @@ static bool diff_patch_diffable(git_patch *patch) ...@@ -154,12 +191,12 @@ static bool diff_patch_diffable(git_patch *patch)
!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); !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; int error = 0;
bool incomplete_data; bool incomplete_data;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) if ((patch->flags & GIT_PATCH_DIFF_LOADED) != 0)
return 0; return 0;
/* if no hunk and data callbacks and user doesn't care if data looks /* 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) ...@@ -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 (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( 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)) should_skip_binary(patch, patch->ofile.file))
goto cleanup; goto cleanup;
} }
if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( 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)) should_skip_binary(patch, patch->nfile.file))
goto cleanup; goto cleanup;
} }
...@@ -194,13 +231,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) ...@@ -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 */ /* once workdir has been tried, load other data as needed */
if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( 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)) should_skip_binary(patch, patch->ofile.file))
goto cleanup; goto cleanup;
} }
if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( 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)) should_skip_binary(patch, patch->nfile.file))
goto cleanup; goto cleanup;
} }
...@@ -212,24 +249,24 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) ...@@ -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 == patch->nfile.file->mode &&
patch->ofile.file->mode != GIT_FILEMODE_COMMIT && patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->delta->status = GIT_DELTA_UNMODIFIED; patch->base.delta->status = GIT_DELTA_UNMODIFIED;
cleanup: cleanup:
diff_patch_update_binary(patch); patch_diff_update_binary(patch);
if (!error) { if (!error) {
if (diff_patch_diffable(patch)) if (patch_diff_diffable(patch))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE; patch->flags |= GIT_PATCH_DIFF_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED; patch->flags |= GIT_PATCH_DIFF_LOADED;
} }
return error; return error;
} }
static int diff_patch_invoke_file_callback( static int patch_diff_invoke_file_callback(
git_patch *patch, git_diff_output *output) git_patch_diff *patch, git_patch_diff_output *output)
{ {
float progress = patch->diff ? float progress = patch->diff ?
((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
...@@ -238,7 +275,7 @@ static int diff_patch_invoke_file_callback( ...@@ -238,7 +275,7 @@ static int diff_patch_invoke_file_callback(
return 0; return 0;
return giterr_set_after_callback_function( 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"); "git_patch");
} }
...@@ -309,7 +346,7 @@ done: ...@@ -309,7 +346,7 @@ done:
return error; 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}}; git_diff_binary binary = {{0}};
const char *old_data = patch->ofile.map.data; const char *old_data = patch->ofile.map.data;
...@@ -334,7 +371,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch) ...@@ -334,7 +371,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error; return error;
error = giterr_set_after_callback_function( 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_patch");
git__free((char *) binary.old_file.data); git__free((char *) binary.old_file.data);
...@@ -343,25 +380,25 @@ static int diff_binary(git_diff_output *output, git_patch *patch) ...@@ -343,25 +380,25 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error; 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; int error = 0;
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) if ((patch->flags & GIT_PATCH_DIFF_DIFFED) != 0)
return 0; return 0;
/* if we are not looking at the binary or text data, don't do the diff */ /* 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) if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
return 0; return 0;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && if ((patch->flags & GIT_PATCH_DIFF_LOADED) == 0 &&
(error = diff_patch_load(patch, output)) < 0) (error = patch_diff_load(patch, output)) < 0)
return error; return error;
if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0) if ((patch->flags & GIT_PATCH_DIFF_DIFFABLE) == 0)
return 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) if (output->binary_cb)
error = diff_binary(output, patch); error = diff_binary(output, patch);
} }
...@@ -370,33 +407,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output) ...@@ -370,33 +407,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output)
error = output->diff_cb(output, patch); error = output->diff_cb(output, patch);
} }
patch->flags |= GIT_DIFF_PATCH_DIFFED; patch->flags |= GIT_PATCH_DIFF_DIFFED;
return error; 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) static int diff_required(git_diff *diff, const char *action)
{ {
if (diff) if (diff)
...@@ -416,7 +430,7 @@ int git_diff_foreach( ...@@ -416,7 +430,7 @@ int git_diff_foreach(
int error = 0; int error = 0;
git_xdiff_output xo; git_xdiff_output xo;
size_t idx; size_t idx;
git_patch patch; git_patch_diff patch;
if ((error = diff_required(diff, "git_diff_foreach")) < 0) if ((error = diff_required(diff, "git_diff_foreach")) < 0)
return error; return error;
...@@ -427,24 +441,24 @@ int git_diff_foreach( ...@@ -427,24 +441,24 @@ int git_diff_foreach(
&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload); &xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts); 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 */ /* 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; continue;
if (binary_cb || hunk_cb || data_cb) { if (binary_cb || hunk_cb || data_cb) {
if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 || if ((error = patch_diff_init(&patch, diff, idx)) != 0 ||
(error = diff_patch_load(&patch, &xo.output)) != 0) (error = patch_diff_load(&patch, &xo.output)) != 0)
return error; 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) 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) if (error)
break; break;
...@@ -454,15 +468,15 @@ int git_diff_foreach( ...@@ -454,15 +468,15 @@ int git_diff_foreach(
} }
typedef struct { typedef struct {
git_patch patch; git_patch_diff patch;
git_diff_delta delta; git_diff_delta delta;
char paths[GIT_FLEX_ARRAY]; 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; 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_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.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) ...@@ -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)) if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
pd->delta.status = GIT_DELTA_UNMODIFIED; 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 && if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
return error; 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) if (!error)
error = diff_patch_generate(patch, (git_diff_output *)xo); error = patch_diff_generate(patch, (git_patch_diff_output *)xo);
return error; return error;
} }
static int diff_patch_from_sources( static int patch_diff_from_sources(
diff_patch_with_delta *pd, patch_diff_with_delta *pd,
git_xdiff_output *xo, git_xdiff_output *xo,
git_diff_file_content_src *oldsrc, git_diff_file_content_src *oldsrc,
git_diff_file_content_src *newsrc, git_diff_file_content_src *newsrc,
...@@ -503,7 +517,7 @@ static int diff_patch_from_sources( ...@@ -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 *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; 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; return error;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
...@@ -511,7 +525,7 @@ static int diff_patch_from_sources( ...@@ -511,7 +525,7 @@ static int diff_patch_from_sources(
tmp = ldata; ldata = rdata; rdata = tmp; tmp = ldata; ldata = rdata; rdata = tmp;
} }
pd->patch.delta = &pd->delta; pd->patch.base.delta = &pd->delta;
if (!oldsrc->as_path) { if (!oldsrc->as_path) {
if (newsrc->as_path) if (newsrc->as_path)
...@@ -534,12 +548,12 @@ static int diff_patch_from_sources( ...@@ -534,12 +548,12 @@ static int diff_patch_from_sources(
return diff_single_generate(pd, xo); return diff_single_generate(pd, xo);
} }
static int diff_patch_with_delta_alloc( static int patch_diff_with_delta_alloc(
diff_patch_with_delta **out, patch_diff_with_delta **out,
const char **old_path, const char **old_path,
const char **new_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 old_len = *old_path ? strlen(*old_path) : 0;
size_t new_len = *new_path ? strlen(*new_path) : 0; size_t new_len = *new_path ? strlen(*new_path) : 0;
size_t alloc_len; size_t alloc_len;
...@@ -551,7 +565,7 @@ static int diff_patch_with_delta_alloc( ...@@ -551,7 +565,7 @@ static int diff_patch_with_delta_alloc(
*out = pd = git__calloc(1, alloc_len); *out = pd = git__calloc(1, alloc_len);
GITERR_CHECK_ALLOC(pd); GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; pd->patch.flags = GIT_PATCH_DIFF_ALLOCATED;
if (*old_path) { if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len); memcpy(&pd->paths[0], *old_path, old_len);
...@@ -579,7 +593,7 @@ static int diff_from_sources( ...@@ -579,7 +593,7 @@ static int diff_from_sources(
void *payload) void *payload)
{ {
int error = 0; int error = 0;
diff_patch_with_delta pd; patch_diff_with_delta pd;
git_xdiff_output xo; git_xdiff_output xo;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
...@@ -589,9 +603,9 @@ static int diff_from_sources( ...@@ -589,9 +603,9 @@ static int diff_from_sources(
memset(&pd, 0, sizeof(pd)); 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; return error;
} }
...@@ -603,13 +617,13 @@ static int patch_from_sources( ...@@ -603,13 +617,13 @@ static int patch_from_sources(
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
diff_patch_with_delta *pd; patch_diff_with_delta *pd;
git_xdiff_output xo; git_xdiff_output xo;
assert(out); assert(out);
*out = NULL; *out = NULL;
if ((error = diff_patch_with_delta_alloc( if ((error = patch_diff_with_delta_alloc(
&pd, &oldsrc->as_path, &newsrc->as_path)) < 0) &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
return error; return error;
...@@ -617,7 +631,7 @@ static int patch_from_sources( ...@@ -617,7 +631,7 @@ static int patch_from_sources(
diff_output_to_patch(&xo.output, &pd->patch); diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts); 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; *out = (git_patch *)pd;
else else
git_patch_free((git_patch *)pd); git_patch_free((git_patch *)pd);
...@@ -742,7 +756,7 @@ int git_patch_from_diff( ...@@ -742,7 +756,7 @@ int git_patch_from_diff(
int error = 0; int error = 0;
git_xdiff_output xo; git_xdiff_output xo;
git_diff_delta *delta = NULL; git_diff_delta *delta = NULL;
git_patch *patch = NULL; git_patch_diff *patch = NULL;
if (patch_ptr) *patch_ptr = NULL; if (patch_ptr) *patch_ptr = NULL;
...@@ -764,17 +778,17 @@ int git_patch_from_diff( ...@@ -764,17 +778,17 @@ int git_patch_from_diff(
(diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
return 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; return error;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_to_patch(&xo.output, patch); diff_output_to_patch(&xo.output, patch);
git_xdiff_init(&xo, &diff->opts); 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) if (!error)
error = diff_patch_generate(patch, &xo.output); error = patch_diff_generate(patch, &xo.output);
if (!error) { if (!error) {
/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
...@@ -782,237 +796,34 @@ int git_patch_from_diff( ...@@ -782,237 +796,34 @@ int git_patch_from_diff(
} }
if (error || !patch_ptr) if (error || !patch_ptr)
git_patch_free(patch); git_patch_free(&patch->base);
else else
*patch_ptr = patch; *patch_ptr = &patch->base;
return error; return error;
} }
void git_patch_free(git_patch *patch) git_diff_driver *git_patch_diff_driver(git_patch_diff *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)
{ {
/* ofile driver is representative for whole patch */ /* ofile driver is representative for whole patch */
return patch->ofile.driver; return patch->ofile.driver;
} }
void git_patch__old_data( void git_patch_diff_old_data(
char **ptr, size_t *len, git_patch *patch) char **ptr, size_t *len, git_patch_diff *patch)
{ {
*ptr = patch->ofile.map.data; *ptr = patch->ofile.map.data;
*len = patch->ofile.map.len; *len = patch->ofile.map.len;
} }
void git_patch__new_data( void git_patch_diff_new_data(
char **ptr, size_t *len, git_patch *patch) char **ptr, size_t *len, git_patch_diff *patch)
{ {
*ptr = patch->nfile.map.data; *ptr = patch->nfile.map.data;
*len = patch->nfile.map.len; *len = patch->nfile.map.len;
} }
int git_patch__invoke_callbacks( static int patch_diff_file_cb(
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(
const git_diff_delta *delta, const git_diff_delta *delta,
float progress, float progress,
void *payload) void *payload)
...@@ -1021,7 +832,7 @@ static int diff_patch_file_cb( ...@@ -1021,7 +832,7 @@ static int diff_patch_file_cb(
return 0; return 0;
} }
static int diff_patch_binary_cb( static int patch_diff_binary_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_binary *binary, const git_diff_binary *binary,
void *payload) void *payload)
...@@ -1051,62 +862,62 @@ static int diff_patch_binary_cb( ...@@ -1051,62 +862,62 @@ static int diff_patch_binary_cb(
return 0; return 0;
} }
static int diff_patch_hunk_cb( static int git_patch_hunk_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk_, const git_diff_hunk *hunk_,
void *payload) void *payload)
{ {
git_patch *patch = payload; git_patch_diff *patch = payload;
diff_patch_hunk *hunk; git_patch_hunk *hunk;
GIT_UNUSED(delta); GIT_UNUSED(delta);
hunk = git_array_alloc(patch->hunks); hunk = git_array_alloc(patch->base.hunks);
GITERR_CHECK_ALLOC(hunk); GITERR_CHECK_ALLOC(hunk);
memcpy(&hunk->hunk, hunk_, sizeof(hunk->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; hunk->line_count = 0;
return 0; return 0;
} }
static int diff_patch_line_cb( static int patch_diff_line_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk_, const git_diff_hunk *hunk_,
const git_diff_line *line_, const git_diff_line *line_,
void *payload) void *payload)
{ {
git_patch *patch = payload; git_patch_diff *patch = payload;
diff_patch_hunk *hunk; git_patch_hunk *hunk;
git_diff_line *line; git_diff_line *line;
GIT_UNUSED(delta); GIT_UNUSED(delta);
GIT_UNUSED(hunk_); 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 */ 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); GITERR_CHECK_ALLOC(line);
memcpy(line, line_, sizeof(*line)); memcpy(line, line_, sizeof(*line));
/* do some bookkeeping so we can provide old/new line numbers */ /* 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 || if (line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION) line->origin == GIT_DIFF_LINE_DELETION)
patch->content_size += 1; patch->base.content_size += 1;
else if (line->origin == GIT_DIFF_LINE_CONTEXT) { else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
patch->content_size += 1; patch->base.content_size += 1;
patch->context_size += line->content_len + 1; patch->base.context_size += line->content_len + 1;
} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) } 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++; hunk->line_count++;
...@@ -1114,7 +925,7 @@ static int diff_patch_line_cb( ...@@ -1114,7 +925,7 @@ static int diff_patch_line_cb(
} }
static void diff_output_init( static void diff_output_init(
git_diff_output *out, git_patch_diff_output *out,
const git_diff_options *opts, const git_diff_options *opts,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb, git_diff_binary_cb binary_cb,
...@@ -1133,14 +944,14 @@ static void diff_output_init( ...@@ -1133,14 +944,14 @@ static void diff_output_init(
out->payload = payload; 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( diff_output_init(
out, out,
NULL, NULL,
diff_patch_file_cb, patch_diff_file_cb,
diff_patch_binary_cb, patch_diff_binary_cb,
diff_patch_hunk_cb, git_patch_hunk_cb,
diff_patch_line_cb, patch_diff_line_cb,
patch); patch);
} }
...@@ -10,60 +10,40 @@ ...@@ -10,60 +10,40 @@
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_file.h" #include "diff_file.h"
#include "array.h" #include "patch.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;
enum { enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0), GIT_PATCH_DIFF_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_INITIALIZED = (1 << 1), GIT_PATCH_DIFF_INITIALIZED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2), GIT_PATCH_DIFF_LOADED = (1 << 2),
/* the two sides are different */ /* 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 */ /* the difference between the two sides has been computed */
GIT_DIFF_PATCH_DIFFED = (1 << 4), GIT_PATCH_DIFF_DIFFED = (1 << 4),
GIT_DIFF_PATCH_FLATTENED = (1 << 5), GIT_PATCH_DIFF_FLATTENED = (1 << 5),
}; };
struct git_patch { struct git_patch_diff {
git_refcount rc; struct git_patch base;
git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
git_diff_options diff_opts;
git_diff_delta *delta;
size_t delta_index; size_t delta_index;
git_diff_file_content ofile; git_diff_file_content ofile;
git_diff_file_content nfile; git_diff_file_content nfile;
uint32_t flags; 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; 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_diff_old_data(char **, size_t *, git_patch_diff *);
extern void git_patch__new_data(char **, size_t *, git_patch *); extern void git_patch_diff_new_data(char **, size_t *, git_patch_diff *);
extern int git_patch__invoke_callbacks( typedef struct git_patch_diff_output git_patch_diff_output;
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_diff_output git_diff_output; struct git_patch_diff_output {
struct git_diff_output {
/* these callbacks are issued with the diff data */ /* these callbacks are issued with the diff data */
git_diff_file_cb file_cb; git_diff_file_cb file_cb;
git_diff_binary_cb binary_cb; git_diff_binary_cb binary_cb;
...@@ -77,7 +57,7 @@ struct git_diff_output { ...@@ -77,7 +57,7 @@ struct git_diff_output {
/* this callback is used to do the diff and drive the other callbacks. /* 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. * 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 #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) ...@@ -106,6 +106,7 @@ void test_apply_fromfile__change_middle_nocontext(void)
&diff_opts, "b/file.txt", 0100644)); &diff_opts, "b/file.txt", 0100644));
} }
void test_apply_fromfile__change_firstline(void) void test_apply_fromfile__change_firstline(void)
{ {
cl_git_pass(validate_and_apply_patchfile( 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