summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/apply.c14
-rw-r--r--src/diff_driver.c1
-rw-r--r--src/diff_patch.h83
-rw-r--r--src/diff_print.c149
-rw-r--r--src/diff_stats.c5
-rw-r--r--src/diff_xdiff.c18
-rw-r--r--src/diff_xdiff.h6
-rw-r--r--src/patch.c943
-rw-r--r--src/patch.h57
-rw-r--r--src/patch_diff.c (renamed from src/diff_patch.c)509
-rw-r--r--src/patch_diff.h63
-rw-r--r--src/patch_parse.c920
12 files changed, 1407 insertions, 1361 deletions
diff --git a/src/apply.c b/src/apply.c
index c667c6308..875f3d042 100644
--- a/src/apply.c
+++ b/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)
diff --git a/src/diff_driver.c b/src/diff_driver.c
index bc3518991..3874c838e 100644
--- a/src/diff_driver.c
+++ b/src/diff_driver.c
@@ -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"
diff --git a/src/diff_patch.h b/src/diff_patch.h
deleted file mode 100644
index 7b4dacdde..000000000
--- a/src/diff_patch.h
+++ /dev/null
@@ -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
diff --git a/src/diff_print.c b/src/diff_print.c
index d18348462..09bf77aef 100644
--- a/src/diff_print.c
+++ b/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)
{
diff --git a/src/diff_stats.c b/src/diff_stats.c
index 42ccbfb87..f2eb69680 100644
--- a/src/diff_stats.c
+++ b/src/diff_stats.c
@@ -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);
diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c
index 1057df3aa..8017c9541 100644
--- a/src/diff_xdiff.c
+++ b/src/diff_xdiff.c
@@ -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) {
diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h
index 98e11b2cb..e1c58a229 100644
--- a/src/diff_xdiff.h
+++ b/src/diff_xdiff.h
@@ -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;
diff --git a/src/patch.c b/src/patch.c
index f6eceac2d..f05cfb21a 100644
--- a/src/patch.c
+++ b/src/patch.c
@@ -1,894 +1,213 @@
#include "git2/patch.h"
-#include "diff_patch.h"
+#include "diff.h"
+#include "patch.h"
-#define parse_err(...) \
- ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
-typedef struct {
- const char *content;
- size_t content_len;
-
- const char *line;
- size_t line_len;
- size_t line_num;
-
- size_t remain;
-
- char *header_new_path;
- char *header_old_path;
-} patch_parse_ctx;
-
-
-static void parse_advance_line(patch_parse_ctx *ctx)
-{
- ctx->line += ctx->line_len;
- ctx->remain -= ctx->line_len;
- ctx->line_len = git__linenlen(ctx->line, ctx->remain);
- ctx->line_num++;
-}
-
-static void parse_advance_chars(patch_parse_ctx *ctx, size_t char_cnt)
-{
- ctx->line += char_cnt;
- ctx->remain -= char_cnt;
- ctx->line_len -= char_cnt;
-}
-
-static int parse_advance_expected(
- patch_parse_ctx *ctx,
- const char *expected,
- size_t expected_len)
+int git_patch__invoke_callbacks(
+ git_patch *patch,
+ git_diff_file_cb file_cb,
+ git_diff_binary_cb binary_cb,
+ git_diff_hunk_cb hunk_cb,
+ git_diff_line_cb line_cb,
+ void *payload)
{
- if (ctx->line_len < expected_len)
- return -1;
+ int error = 0;
+ uint32_t i, j;
- if (memcmp(ctx->line, expected, expected_len) != 0)
- return -1;
+ if (file_cb)
+ error = file_cb(patch->delta, 0, payload);
- parse_advance_chars(ctx, expected_len);
- return 0;
-}
+ if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
+ if (binary_cb)
+ error = binary_cb(patch->delta, &patch->binary, payload);
-static int parse_advance_ws(patch_parse_ctx *ctx)
-{
- int ret = -1;
-
- while (ctx->line_len > 0 &&
- ctx->line[0] != '\n' &&
- git__isspace(ctx->line[0])) {
- ctx->line++;
- ctx->line_len--;
- ctx->remain--;
- ret = 0;
+ return error;
}
- return ret;
-}
+ if (!hunk_cb && !line_cb)
+ return error;
-static int parse_advance_nl(patch_parse_ctx *ctx)
-{
- if (ctx->line_len != 1 || ctx->line[0] != '\n')
- return -1;
+ for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
+ git_patch_hunk *h = git_array_get(patch->hunks, i);
- parse_advance_line(ctx);
- return 0;
-}
+ if (hunk_cb)
+ error = hunk_cb(patch->delta, &h->hunk, payload);
-static int header_path_len(patch_parse_ctx *ctx)
-{
- bool inquote = 0;
- bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"');
- size_t len;
+ if (!line_cb)
+ continue;
- for (len = quoted; len < ctx->line_len; len++) {
- if (!quoted && git__isspace(ctx->line[len]))
- break;
- else if (quoted && !inquote && ctx->line[len] == '"') {
- len++;
- break;
- }
+ for (j = 0; !error && j < h->line_count; ++j) {
+ git_diff_line *l =
+ git_array_get(patch->lines, h->line_start + j);
- inquote = (!inquote && ctx->line[len] == '\\');
+ error = line_cb(patch->delta, &h->hunk, l, payload);
+ }
}
- return len;
-}
-
-static int parse_header_path_buf(git_buf *path, patch_parse_ctx *ctx)
-{
- int path_len, error = 0;
-
- path_len = header_path_len(ctx);
-
- if ((error = git_buf_put(path, ctx->line, path_len)) < 0)
- goto done;
-
- parse_advance_chars(ctx, path_len);
-
- git_buf_rtrim(path);
-
- if (path->size > 0 && path->ptr[0] == '"')
- error = git_buf_unquote(path);
-
- if (error < 0)
- goto done;
-
- git_path_squash_slashes(path);
-
-done:
- return error;
-}
-
-static int parse_header_path(char **out, patch_parse_ctx *ctx)
-{
- git_buf path = GIT_BUF_INIT;
- int error = parse_header_path_buf(&path, ctx);
-
- *out = git_buf_detach(&path);
-
return error;
}
-static int parse_header_git_oldpath(git_patch *patch, patch_parse_ctx *ctx)
-{
- return parse_header_path((char **)&patch->ofile.file->path, ctx);
-}
-
-static int parse_header_git_newpath(git_patch *patch, patch_parse_ctx *ctx)
-{
- return parse_header_path((char **)&patch->nfile.file->path, ctx);
-}
-
-static int parse_header_mode(uint16_t *mode, patch_parse_ctx *ctx)
+size_t git_patch_size(
+ git_patch *patch,
+ int include_context,
+ int include_hunk_headers,
+ int include_file_headers)
{
- const char *end;
- int32_t m;
- int ret;
+ size_t out;
- if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]))
- return parse_err("invalid file mode at line %d", ctx->line_num);
+ assert(patch);
- if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0)
- return ret;
+ out = patch->content_size;
- if (m > UINT16_MAX)
- return -1;
+ if (!include_context)
+ out -= patch->context_size;
- *mode = (uint16_t)m;
+ if (include_hunk_headers)
+ out += patch->header_size;
- parse_advance_chars(ctx, (end - ctx->line));
+ if (include_file_headers) {
+ git_buf file_header = GIT_BUF_INIT;
- return ret;
-}
+ if (git_diff_delta__format_file_header(
+ &file_header, patch->delta, NULL, NULL, 0) < 0)
+ giterr_clear();
+ else
+ out += git_buf_len(&file_header);
-static int parse_header_oid(
- git_oid *oid,
- size_t *oid_len,
- patch_parse_ctx *ctx)
-{
- size_t len;
-
- for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) {
- if (!git__isxdigit(ctx->line[len]))
- break;
+ git_buf_free(&file_header);
}
- if (len < GIT_OID_MINPREFIXLEN ||
- git_oid_fromstrn(oid, ctx->line, len) < 0)
- return parse_err("invalid hex formatted object id at line %d",
- ctx->line_num);
-
- parse_advance_chars(ctx, len);
-
- *oid_len = len;
-
- return 0;
+ return out;
}
-static int parse_header_git_index(git_patch *patch, patch_parse_ctx *ctx)
+int git_patch_line_stats(
+ size_t *total_ctxt,
+ size_t *total_adds,
+ size_t *total_dels,
+ const git_patch *patch)
{
- /*
- * TODO: we read the prefix provided in the diff into the delta's id
- * field, but do not mark is at an abbreviated id.
- */
- size_t oid_len, nid_len;
-
- if (parse_header_oid(&patch->delta->old_file.id, &oid_len, ctx) < 0 ||
- parse_advance_expected(ctx, "..", 2) < 0 ||
- parse_header_oid(&patch->delta->new_file.id, &nid_len, ctx) < 0)
- return -1;
-
- if (ctx->line_len > 0 && ctx->line[0] == ' ') {
- uint16_t mode;
-
- parse_advance_chars(ctx, 1);
+ size_t totals[3], idx;
- if (parse_header_mode(&mode, ctx) < 0)
- return -1;
+ memset(totals, 0, sizeof(totals));
- if (!patch->delta->new_file.mode)
- patch->delta->new_file.mode = mode;
+ for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
+ git_diff_line *line = git_array_get(patch->lines, idx);
+ if (!line)
+ continue;
- if (!patch->delta->old_file.mode)
- patch->delta->old_file.mode = mode;
+ switch (line->origin) {
+ case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
+ case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
+ case GIT_DIFF_LINE_DELETION: totals[2]++; break;
+ default:
+ /* diff --stat and --numstat don't count EOFNL marks because
+ * they will always be paired with a ADDITION or DELETION line.
+ */
+ break;
+ }
}
- return 0;
-}
-
-static int parse_header_git_oldmode(git_patch *patch, patch_parse_ctx *ctx)
-{
- return parse_header_mode(&patch->ofile.file->mode, ctx);
-}
-
-static int parse_header_git_newmode(git_patch *patch, patch_parse_ctx *ctx)
-{
- return parse_header_mode(&patch->nfile.file->mode, ctx);
-}
-
-static int parse_header_git_deletedfilemode(
- git_patch *patch,
- patch_parse_ctx *ctx)
-{
- git__free((char *)patch->ofile.file->path);
-
- patch->ofile.file->path = NULL;
- patch->delta->status = GIT_DELTA_DELETED;
-
- return parse_header_mode(&patch->ofile.file->mode, ctx);
-}
-
-static int parse_header_git_newfilemode(
- git_patch *patch,
- patch_parse_ctx *ctx)
-{
- git__free((char *)patch->nfile.file->path);
-
- patch->nfile.file->path = NULL;
- patch->delta->status = GIT_DELTA_ADDED;
-
- return parse_header_mode(&patch->nfile.file->mode, ctx);
-}
-
-static int parse_header_rename(
- char **out,
- char **header_path,
- patch_parse_ctx *ctx)
-{
- git_buf path = GIT_BUF_INIT;
- size_t header_path_len, prefix_len;
-
- if (*header_path == NULL)
- return parse_err("rename without proper git diff header at line %d",
- ctx->line_num);
-
- header_path_len = strlen(*header_path);
-
- if (parse_header_path_buf(&path, ctx) < 0)
- return -1;
-
- if (header_path_len < git_buf_len(&path))
- return parse_err("rename path is invalid at line %d", ctx->line_num);
-
- /* This sanity check exists because git core uses the data in the
- * "rename from" / "rename to" lines, but it's formatted differently
- * than the other paths and lacks the normal prefix. This irregularity
- * causes us to ignore these paths (we always store the prefixed paths)
- * but instead validate that they match the suffix of the paths we parsed
- * since we would behave differently from git core if they ever differed.
- * Instead, we raise an error, rather than parsing differently.
- */
- prefix_len = header_path_len - path.size;
-
- if (strncmp(*header_path + prefix_len, path.ptr, path.size) != 0 ||
- (prefix_len > 0 && (*header_path)[prefix_len - 1] != '/'))
- return parse_err("rename path does not match header at line %d",
- ctx->line_num);
-
- *out = *header_path;
- *header_path = NULL;
-
- git_buf_free(&path);
+ if (total_ctxt)
+ *total_ctxt = totals[0];
+ if (total_adds)
+ *total_adds = totals[1];
+ if (total_dels)
+ *total_dels = totals[2];
return 0;
}
-static int parse_header_renamefrom(git_patch *patch, patch_parse_ctx *ctx)
+const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{
- patch->delta->status |= GIT_DELTA_RENAMED;
-
- return parse_header_rename(
- (char **)&patch->ofile.file->path,
- &ctx->header_old_path,
- ctx);
+ assert(patch);
+ return patch->delta;
}
-static int parse_header_renameto(git_patch *patch, patch_parse_ctx *ctx)
+size_t git_patch_num_hunks(const git_patch *patch)
{
- patch->delta->status |= GIT_DELTA_RENAMED;
-
- return parse_header_rename(
- (char **)&patch->nfile.file->path,
- &ctx->header_new_path,
- ctx);
+ assert(patch);
+ return git_array_size(patch->hunks);
}
-static int parse_header_percent(uint16_t *out, patch_parse_ctx *ctx)
+static int patch_error_outofrange(const char *thing)
{
- int32_t val;
- const char *end;
-
- if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) ||
- git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0)
- return -1;
-
- parse_advance_chars(ctx, (end - ctx->line));
-
- if (parse_advance_expected(ctx, "%", 1) < 0)
- return -1;
-
- if (val > 100)
- return -1;
-
- *out = val;
- return 0;
+ giterr_set(GITERR_INVALID, "patch %s index out of range", thing);
+ return GIT_ENOTFOUND;
}
-static int parse_header_similarity(git_patch *patch, patch_parse_ctx *ctx)
-{
- if (parse_header_percent(&patch->delta->similarity, ctx) < 0)
- return parse_err("invalid similarity percentage at line %d",
- ctx->line_num);
-
- return 0;
-}
-
-static int parse_header_dissimilarity(git_patch *patch, patch_parse_ctx *ctx)
-{
- uint16_t dissimilarity;
-
- if (parse_header_percent(&dissimilarity, ctx) < 0)
- return parse_err("invalid similarity percentage at line %d",
- ctx->line_num);
-
- patch->delta->similarity = 100 - dissimilarity;
-
- return 0;
-}
-
-typedef struct {
- const char *str;
- int (*fn)(git_patch *, patch_parse_ctx *);
-} header_git_op;
-
-static const header_git_op header_git_ops[] = {
- { "@@ -", NULL },
- { "GIT binary patch", NULL },
- { "--- ", parse_header_git_oldpath },
- { "+++ ", parse_header_git_newpath },
- { "index ", parse_header_git_index },
- { "old mode ", parse_header_git_oldmode },
- { "new mode ", parse_header_git_newmode },
- { "deleted file mode ", parse_header_git_deletedfilemode },
- { "new file mode ", parse_header_git_newfilemode },
- { "rename from ", parse_header_renamefrom },
- { "rename to ", parse_header_renameto },
- { "rename old ", parse_header_renamefrom },
- { "rename new ", parse_header_renameto },
- { "similarity index ", parse_header_similarity },
- { "dissimilarity index ", parse_header_dissimilarity },
-};
-
-static int parse_header_git(
+int git_patch_get_hunk(
+ const git_diff_hunk **out,
+ size_t *lines_in_hunk,
git_patch *patch,
- patch_parse_ctx *ctx)
+ size_t hunk_idx)
{
- size_t i;
- int error = 0;
-
- /* Parse the diff --git line */
- if (parse_advance_expected(ctx, "diff --git ", 11) < 0)
- return parse_err("corrupt git diff header at line %d", ctx->line_num);
-
- if (parse_header_path(&ctx->header_old_path, ctx) < 0)
- return parse_err("corrupt old path in git diff header at line %d",
- ctx->line_num);
-
- if (parse_advance_ws(ctx) < 0 ||
- parse_header_path(&ctx->header_new_path, ctx) < 0)
- return parse_err("corrupt new path in git diff header at line %d",
- ctx->line_num);
-
- /* Parse remaining header lines */
- for (parse_advance_line(ctx); ctx->remain > 0; parse_advance_line(ctx)) {
- if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n')
- break;
-
- for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) {
- const header_git_op *op = &header_git_ops[i];
- size_t op_len = strlen(op->str);
-
- if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0)
- continue;
+ git_patch_hunk *hunk;
+ assert(patch);
- /* Do not advance if this is the patch separator */
- if (op->fn == NULL)
- goto done;
+ hunk = git_array_get(patch->hunks, hunk_idx);
- parse_advance_chars(ctx, op_len);
-
- if ((error = op->fn(patch, ctx)) < 0)
- goto done;
-
- parse_advance_ws(ctx);
- parse_advance_expected(ctx, "\n", 1);
-
- if (ctx->line_len > 0) {
- error = parse_err("trailing data at line %d", ctx->line_num);
- goto done;
- }
-
- break;
- }
+ if (!hunk) {
+ if (out) *out = NULL;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return patch_error_outofrange("hunk");
}
-done:
- return error;
-}
-
-static int parse_number(git_off_t *out, patch_parse_ctx *ctx)
-{
- const char *end;
- int64_t num;
-
- if (!git__isdigit(ctx->line[0]))
- return -1;
-
- if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0)
- return -1;
-
- if (num < 0)
- return -1;
-
- *out = num;
- parse_advance_chars(ctx, (end - ctx->line));
-
- return 0;
-}
-
-static int parse_int(int *out, patch_parse_ctx *ctx)
-{
- git_off_t num;
-
- if (parse_number(&num, ctx) < 0 || !git__is_int(num))
- return -1;
-
- *out = (int)num;
+ if (out) *out = &hunk->hunk;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
return 0;
}
-static int parse_hunk_header(
- diff_patch_hunk *hunk,
- patch_parse_ctx *ctx)
+int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
{
- const char *header_start = ctx->line;
-
- hunk->hunk.old_lines = 1;
- hunk->hunk.new_lines = 1;
-
- if (parse_advance_expected(ctx, "@@ -", 4) < 0 ||
- parse_int(&hunk->hunk.old_start, ctx) < 0)
- goto fail;
-
- if (ctx->line_len > 0 && ctx->line[0] == ',') {
- if (parse_advance_expected(ctx, ",", 1) < 0 ||
- parse_int(&hunk->hunk.old_lines, ctx) < 0)
- goto fail;
- }
-
- if (parse_advance_expected(ctx, " +", 2) < 0 ||
- parse_int(&hunk->hunk.new_start, ctx) < 0)
- goto fail;
-
- if (ctx->line_len > 0 && ctx->line[0] == ',') {
- if (parse_advance_expected(ctx, ",", 1) < 0 ||
- parse_int(&hunk->hunk.new_lines, ctx) < 0)
- goto fail;
- }
-
- if (parse_advance_expected(ctx, " @@", 3) < 0)
- goto fail;
-
- parse_advance_line(ctx);
-
- if (!hunk->hunk.old_lines && !hunk->hunk.new_lines)
- goto fail;
-
- hunk->hunk.header_len = ctx->line - header_start;
- if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1))
- return parse_err("oversized patch hunk header at line %d",
- ctx->line_num);
-
- memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len);
- hunk->hunk.header[hunk->hunk.header_len] = '\0';
-
- return 0;
+ git_patch_hunk *hunk;
+ assert(patch);
-fail:
- giterr_set(GITERR_PATCH, "invalid patch hunk header at line %d",
- ctx->line_num);
- return -1;
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
+ return patch_error_outofrange("hunk");
+ return (int)hunk->line_count;
}
-static int parse_hunk_body(
+int git_patch_get_line_in_hunk(
+ const git_diff_line **out,
git_patch *patch,
- diff_patch_hunk *hunk,
- patch_parse_ctx *ctx)
+ size_t hunk_idx,
+ size_t line_of_hunk)
{
+ git_patch_hunk *hunk;
git_diff_line *line;
- int error = 0;
-
- int oldlines = hunk->hunk.old_lines;
- int newlines = hunk->hunk.new_lines;
-
- for (;
- ctx->remain > 4 && (oldlines || newlines) &&
- memcmp(ctx->line, "@@ -", 4) != 0;
- parse_advance_line(ctx)) {
-
- int origin;
- int prefix = 1;
-
- if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') {
- error = parse_err("invalid patch instruction at line %d",
- ctx->line_num);
- goto done;
- }
-
- switch (ctx->line[0]) {
- case '\n':
- prefix = 0;
-
- case ' ':
- origin = GIT_DIFF_LINE_CONTEXT;
- oldlines--;
- newlines--;
- break;
-
- case '-':
- origin = GIT_DIFF_LINE_DELETION;
- oldlines--;
- break;
-
- case '+':
- origin = GIT_DIFF_LINE_ADDITION;
- newlines--;
- break;
-
- default:
- error = parse_err("invalid patch hunk at line %d", ctx->line_num);
- goto done;
- }
-
- line = git_array_alloc(patch->lines);
- GITERR_CHECK_ALLOC(line);
-
- memset(line, 0x0, sizeof(git_diff_line));
-
- line->content = ctx->line + prefix;
- line->content_len = ctx->line_len - prefix;
- line->content_offset = ctx->content_len - ctx->remain;
- line->origin = origin;
-
- hunk->line_count++;
- }
-
- if (oldlines || newlines) {
- error = parse_err(
- "invalid patch hunk, expected %d old lines and %d new lines",
- hunk->hunk.old_lines, hunk->hunk.new_lines);
- goto done;
- }
-
- /* Handle "\ No newline at end of file". Only expect the leading
- * backslash, though, because the rest of the string could be
- * localized. Because `diff` optimizes for the case where you
- * want to apply the patch by hand.
- */
- if (ctx->line_len >= 2 && memcmp(ctx->line, "\\ ", 2) == 0 &&
- git_array_size(patch->lines) > 0) {
-
- line = git_array_get(patch->lines, git_array_size(patch->lines)-1);
-
- if (line->content_len < 1) {
- error = parse_err("cannot trim trailing newline of empty line");
- goto done;
- }
-
- line->content_len--;
-
- parse_advance_line(ctx);
- }
-
-done:
- return error;
-}
-
-static int parse_header_traditional(git_patch *patch, patch_parse_ctx *ctx)
-{
- GIT_UNUSED(patch);
- GIT_UNUSED(ctx);
-
- return 1;
-}
-
-static int parse_patch_header(
- git_patch *patch,
- patch_parse_ctx *ctx)
-{
- int error = 0;
-
- for (ctx->line = ctx->content; ctx->remain > 0; parse_advance_line(ctx)) {
- /* This line is too short to be a patch header. */
- if (ctx->line_len < 6)
- continue;
-
- /* This might be a hunk header without a patch header, provide a
- * sensible error message. */
- if (memcmp(ctx->line, "@@ -", 4) == 0) {
- size_t line_num = ctx->line_num;
- diff_patch_hunk hunk;
-
- /* If this cannot be parsed as a hunk header, it's just leading
- * noise, continue.
- */
- if (parse_hunk_header(&hunk, ctx) < 0) {
- giterr_clear();
- continue;
- }
-
- error = parse_err("invalid hunk header outside patch at line %d",
- line_num);
- goto done;
- }
-
- /* This buffer is too short to contain a patch. */
- if (ctx->remain < ctx->line_len + 6)
- break;
-
- /* A proper git patch */
- if (ctx->line_len >= 11 && memcmp(ctx->line, "diff --git ", 11) == 0) {
- if ((error = parse_header_git(patch, ctx)) < 0)
- goto done;
-
- /* For modechange only patches, it does not include filenames;
- * instead we need to use the paths in the diff --git header.
- */
- if (!patch->ofile.file->path && !patch->nfile.file->path) {
- if (!ctx->header_old_path || !ctx->header_new_path) {
- error = parse_err("git diff header lacks old / new paths");
- goto done;
- }
-
- patch->ofile.file->path = ctx->header_old_path;
- ctx->header_old_path = NULL;
-
- patch->nfile.file->path = ctx->header_new_path;
- ctx->header_new_path = NULL;
- }
-
- goto done;
- }
- if ((error = parse_header_traditional(patch, ctx)) <= 0)
- goto done;
+ assert(patch);
- error = 0;
- continue;
+ if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
+ if (out) *out = NULL;
+ return patch_error_outofrange("hunk");
}
- error = parse_err("no header in patch file");
-
-done:
- return error;
-}
-
-static int parse_patch_binary_side(
- git_diff_binary_file *binary,
- patch_parse_ctx *ctx)
-{
- git_diff_binary_t type = GIT_DIFF_BINARY_NONE;
- git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT;
- git_off_t len;
- int error = 0;
-
- if (ctx->line_len >= 8 && memcmp(ctx->line, "literal ", 8) == 0) {
- type = GIT_DIFF_BINARY_LITERAL;
- parse_advance_chars(ctx, 8);
- } else if (ctx->line_len >= 6 && memcmp(ctx->line, "delta ", 6) == 0) {
- type = GIT_DIFF_BINARY_DELTA;
- parse_advance_chars(ctx, 6);
- } else {
- error = parse_err("unknown binary delta type at line %d", ctx->line_num);
- goto done;
- }
-
- if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) {
- error = parse_err("invalid binary size at line %d", ctx->line_num);
- goto done;
- }
-
- while (ctx->line_len) {
- char c = ctx->line[0];
- size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size;
-
- if (c == '\n')
- break;
- else if (c >= 'A' && c <= 'Z')
- decoded_len = c - 'A' + 1;
- else if (c >= 'a' && c <= 'z')
- decoded_len = c - 'a' + 26 + 1;
-
- if (!decoded_len) {
- error = parse_err("invalid binary length at line %d", ctx->line_num);
- goto done;
- }
-
- parse_advance_chars(ctx, 1);
-
- encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5;
-
- if (encoded_len > ctx->line_len - 1) {
- error = parse_err("truncated binary data at line %d", ctx->line_num);
- goto done;
- }
-
- if ((error = git_buf_decode_base85(
- &decoded, ctx->line, encoded_len, decoded_len)) < 0)
- goto done;
-
- if (decoded.size - decoded_orig != decoded_len) {
- error = parse_err("truncated binary data at line %d", ctx->line_num);
- goto done;
- }
-
- parse_advance_chars(ctx, encoded_len);
-
- if (parse_advance_nl(ctx) < 0) {
- error = parse_err("trailing data at line %d", ctx->line_num);
- goto done;
- }
+ if (line_of_hunk >= hunk->line_count ||
+ !(line = git_array_get(
+ patch->lines, hunk->line_start + line_of_hunk))) {
+ if (out) *out = NULL;
+ return patch_error_outofrange("line");
}
- binary->type = type;
- binary->inflatedlen = (size_t)len;
- binary->datalen = decoded.size;
- binary->data = git_buf_detach(&decoded);
-
-done:
- git_buf_free(&base85);
- git_buf_free(&decoded);
- return error;
-}
-
-static int parse_patch_binary(
- git_patch *patch,
- patch_parse_ctx *ctx)
-{
- int error;
-
- if (parse_advance_expected(ctx, "GIT binary patch", 16) < 0 ||
- parse_advance_nl(ctx) < 0)
- return parse_err("corrupt git binary header at line %d", ctx->line_num);
-
- /* parse old->new binary diff */
- if ((error = parse_patch_binary_side(&patch->binary.new_file, ctx)) < 0)
- return error;
-
- if (parse_advance_nl(ctx) < 0)
- return parse_err("corrupt git binary separator at line %d", ctx->line_num);
-
- /* parse new->old binary diff */
- if ((error = parse_patch_binary_side(&patch->binary.old_file, ctx)) < 0)
- return error;
-
- patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
+ if (out) *out = line;
return 0;
}
-static int parse_patch_hunks(
- git_patch *patch,
- patch_parse_ctx *ctx)
-{
- diff_patch_hunk *hunk;
- int error = 0;
-
- for (; ctx->line_len > 4 && memcmp(ctx->line, "@@ -", 4) == 0; ) {
-
- hunk = git_array_alloc(patch->hunks);
- GITERR_CHECK_ALLOC(hunk);
-
- memset(hunk, 0, sizeof(diff_patch_hunk));
-
- hunk->line_start = git_array_size(patch->lines);
- hunk->line_count = 0;
-
- if ((error = parse_hunk_header(hunk, ctx)) < 0 ||
- (error = parse_hunk_body(patch, hunk, ctx)) < 0)
- goto done;
- }
-
-done:
- return error;
-}
-
-static int parse_patch_body(git_patch *patch, patch_parse_ctx *ctx)
+static void git_patch__free(git_patch *patch)
{
- if (ctx->line_len >= 16 && memcmp(ctx->line, "GIT binary patch", 16) == 0)
- return parse_patch_binary(patch, ctx);
+ git_array_clear(patch->lines);
+ git_array_clear(patch->hunks);
- else if (ctx->line_len >= 4 && memcmp(ctx->line, "@@ -", 4) == 0)
- return parse_patch_hunks(patch, ctx);
+ git__free((char *)patch->binary.old_file.data);
+ git__free((char *)patch->binary.new_file.data);
- return 0;
+ if (patch->free_fn)
+ patch->free_fn(patch);
}
-static int check_patch(git_patch *patch)
+void git_patch_free(git_patch *patch)
{
- if (!patch->ofile.file->path && patch->delta->status != GIT_DELTA_ADDED)
- return parse_err("missing old file path");
-
- if (!patch->nfile.file->path && patch->delta->status != GIT_DELTA_DELETED)
- return parse_err("missing new file path");
-
- if (patch->ofile.file->path && patch->nfile.file->path) {
- if (!patch->nfile.file->mode)
- patch->nfile.file->mode = patch->ofile.file->mode;
- }
-
- if (patch->delta->status == GIT_DELTA_MODIFIED &&
- !(patch->delta->flags & GIT_DIFF_FLAG_BINARY) &&
- patch->nfile.file->mode == patch->ofile.file->mode &&
- git_array_size(patch->hunks) == 0)
- return parse_err("patch with no hunks");
-
- return 0;
-}
-
-int git_patch_from_patchfile(
- git_patch **out,
- const char *content,
- size_t content_len)
-{
- patch_parse_ctx ctx = {0};
- git_patch *patch;
- int error = 0;
-
- *out = NULL;
-
- patch = git__calloc(1, sizeof(git_patch));
- GITERR_CHECK_ALLOC(patch);
-
- patch->delta = git__calloc(1, sizeof(git_diff_delta));
- patch->ofile.file = git__calloc(1, sizeof(git_diff_file));
- patch->nfile.file = git__calloc(1, sizeof(git_diff_file));
-
- patch->delta->status = GIT_DELTA_MODIFIED;
-
- ctx.content = content;
- ctx.content_len = content_len;
- ctx.remain = content_len;
-
- if ((error = parse_patch_header(patch, &ctx)) < 0 ||
- (error = parse_patch_body(patch, &ctx)) < 0 ||
- (error = check_patch(patch)) < 0)
- goto done;
-
- *out = patch;
-
-done:
- git__free(ctx.header_old_path);
- git__free(ctx.header_new_path);
-
- return error;
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, git_patch__free);
}
diff --git a/src/patch.h b/src/patch.h
new file mode 100644
index 000000000..ecab570d5
--- /dev/null
+++ b/src/patch.h
@@ -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
diff --git a/src/diff_patch.c b/src/patch_diff.c
index 20a84388f..0e06cd6eb 100644
--- a/src/diff_patch.c
+++ b/src/patch_diff.c
@@ -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);
}
diff --git a/src/patch_diff.h b/src/patch_diff.h
new file mode 100644
index 000000000..076acdf43
--- /dev/null
+++ b/src/patch_diff.h
@@ -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
diff --git a/src/patch_parse.c b/src/patch_parse.c
new file mode 100644
index 000000000..e5019fce9
--- /dev/null
+++ b/src/patch_parse.c
@@ -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;
+}