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