|
Packit |
ae9e2a |
/*
|
|
Packit |
ae9e2a |
* Copyright (C) the libgit2 contributors. All rights reserved.
|
|
Packit |
ae9e2a |
*
|
|
Packit |
ae9e2a |
* This file is part of libgit2, distributed under the GNU GPL v2 with
|
|
Packit |
ae9e2a |
* a Linking Exception. For full terms see the included COPYING file.
|
|
Packit |
ae9e2a |
*/
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
#include <assert.h>
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
#include "git2/patch.h"
|
|
Packit |
ae9e2a |
#include "git2/filter.h"
|
|
Packit |
ae9e2a |
#include "array.h"
|
|
Packit |
ae9e2a |
#include "patch.h"
|
|
Packit |
ae9e2a |
#include "fileops.h"
|
|
Packit |
ae9e2a |
#include "apply.h"
|
|
Packit |
ae9e2a |
#include "delta.h"
|
|
Packit |
ae9e2a |
#include "zstream.h"
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
#define apply_err(...) \
|
|
Packit |
ae9e2a |
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
typedef struct {
|
|
Packit |
ae9e2a |
/* The lines that we allocate ourself are allocated out of the pool.
|
|
Packit |
ae9e2a |
* (Lines may have been allocated out of the diff.)
|
|
Packit |
ae9e2a |
*/
|
|
Packit |
ae9e2a |
git_pool pool;
|
|
Packit |
ae9e2a |
git_vector lines;
|
|
Packit |
ae9e2a |
} patch_image;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static void patch_line_init(
|
|
Packit |
ae9e2a |
git_diff_line *out,
|
|
Packit |
ae9e2a |
const char *in,
|
|
Packit |
ae9e2a |
size_t in_len,
|
|
Packit |
ae9e2a |
size_t in_offset)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
out->content = in;
|
|
Packit |
ae9e2a |
out->content_len = in_len;
|
|
Packit |
ae9e2a |
out->content_offset = in_offset;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT }
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int patch_image_init_fromstr(
|
|
Packit |
ae9e2a |
patch_image *out, const char *in, size_t in_len)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
git_diff_line *line;
|
|
Packit |
ae9e2a |
const char *start, *end;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
memset(out, 0x0, sizeof(patch_image));
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
git_pool_init(&out->pool, sizeof(git_diff_line));
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
for (start = in; start < in + in_len; start = end) {
|
|
Packit |
ae9e2a |
end = memchr(start, '\n', in_len);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (end == NULL)
|
|
Packit |
ae9e2a |
end = in + in_len;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
else if (end < in + in_len)
|
|
Packit |
ae9e2a |
end++;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
line = git_pool_mallocz(&out->pool, 1);
|
|
Packit |
ae9e2a |
GITERR_CHECK_ALLOC(line);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (git_vector_insert(&out->lines, line) < 0)
|
|
Packit |
ae9e2a |
return -1;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
patch_line_init(line, start, (end - start), (start - in));
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return 0;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static void patch_image_free(patch_image *image)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
if (image == NULL)
|
|
Packit |
ae9e2a |
return;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
git_pool_clear(&image->pool);
|
|
Packit |
ae9e2a |
git_vector_free(&image->lines);
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static bool match_hunk(
|
|
Packit |
ae9e2a |
patch_image *image,
|
|
Packit |
ae9e2a |
patch_image *preimage,
|
|
Packit |
ae9e2a |
size_t linenum)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
bool match = 0;
|
|
Packit |
ae9e2a |
size_t i;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
/* Ensure this hunk is within the image boundaries. */
|
|
Packit |
ae9e2a |
if (git_vector_length(&preimage->lines) + linenum >
|
|
Packit |
ae9e2a |
git_vector_length(&image->lines))
|
|
Packit |
ae9e2a |
return 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
match = 1;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
/* Check exact match. */
|
|
Packit |
ae9e2a |
for (i = 0; i < git_vector_length(&preimage->lines); i++) {
|
|
Packit |
ae9e2a |
git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
|
|
Packit |
ae9e2a |
git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (preimage_line->content_len != image_line->content_len ||
|
|
Packit |
ae9e2a |
memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
|
|
Packit |
ae9e2a |
match = 0;
|
|
Packit |
ae9e2a |
break;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return match;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static bool find_hunk_linenum(
|
|
Packit |
ae9e2a |
size_t *out,
|
|
Packit |
ae9e2a |
patch_image *image,
|
|
Packit |
ae9e2a |
patch_image *preimage,
|
|
Packit |
ae9e2a |
size_t linenum)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
size_t max = git_vector_length(&image->lines);
|
|
Packit |
ae9e2a |
bool match;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (linenum > max)
|
|
Packit |
ae9e2a |
linenum = max;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
match = match_hunk(image, preimage, linenum);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
*out = linenum;
|
|
Packit |
ae9e2a |
return match;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int update_hunk(
|
|
Packit |
ae9e2a |
patch_image *image,
|
|
Packit |
ae9e2a |
unsigned int linenum,
|
|
Packit |
ae9e2a |
patch_image *preimage,
|
|
Packit |
ae9e2a |
patch_image *postimage)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
size_t postlen = git_vector_length(&postimage->lines);
|
|
Packit |
ae9e2a |
size_t prelen = git_vector_length(&preimage->lines);
|
|
Packit |
ae9e2a |
size_t i;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (postlen > prelen)
|
|
Packit |
ae9e2a |
error = git_vector_insert_null(
|
|
Packit |
ae9e2a |
&image->lines, linenum, (postlen - prelen));
|
|
Packit |
ae9e2a |
else if (prelen > postlen)
|
|
Packit |
ae9e2a |
error = git_vector_remove_range(
|
|
Packit |
ae9e2a |
&image->lines, linenum, (prelen - postlen));
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (error) {
|
|
Packit |
ae9e2a |
giterr_set_oom();
|
|
Packit |
ae9e2a |
return -1;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
for (i = 0; i < git_vector_length(&postimage->lines); i++) {
|
|
Packit |
ae9e2a |
image->lines.contents[linenum + i] =
|
|
Packit |
ae9e2a |
git_vector_get(&postimage->lines, i);
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return 0;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int apply_hunk(
|
|
Packit |
ae9e2a |
patch_image *image,
|
|
Packit |
ae9e2a |
git_patch *patch,
|
|
Packit |
ae9e2a |
git_patch_hunk *hunk)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT;
|
|
Packit |
ae9e2a |
size_t line_num, i;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
for (i = 0; i < hunk->line_count; i++) {
|
|
Packit |
ae9e2a |
size_t linenum = hunk->line_start + i;
|
|
Packit |
ae9e2a |
git_diff_line *line = git_array_get(patch->lines, linenum);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (!line) {
|
|
Packit |
ae9e2a |
error = apply_err("preimage does not contain line %"PRIuZ, linenum);
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
|
|
Packit |
ae9e2a |
line->origin == GIT_DIFF_LINE_DELETION) {
|
|
Packit |
ae9e2a |
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
|
|
Packit |
ae9e2a |
line->origin == GIT_DIFF_LINE_ADDITION) {
|
|
Packit |
ae9e2a |
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
|
|
Packit |
ae9e2a |
error = apply_err("hunk at line %d did not apply",
|
|
Packit |
ae9e2a |
hunk->hunk.new_start);
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
error = update_hunk(image, line_num, &preimage, &postimage);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
done:
|
|
Packit |
ae9e2a |
patch_image_free(&preimage);
|
|
Packit |
ae9e2a |
patch_image_free(&postimage);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return error;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int apply_hunks(
|
|
Packit |
ae9e2a |
git_buf *out,
|
|
Packit |
ae9e2a |
const char *source,
|
|
Packit |
ae9e2a |
size_t source_len,
|
|
Packit |
ae9e2a |
git_patch *patch)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
git_patch_hunk *hunk;
|
|
Packit |
ae9e2a |
git_diff_line *line;
|
|
Packit |
ae9e2a |
patch_image image;
|
|
Packit |
ae9e2a |
size_t i;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
git_array_foreach(patch->hunks, i, hunk) {
|
|
Packit |
ae9e2a |
if ((error = apply_hunk(&image, patch, hunk)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
git_vector_foreach(&image.lines, i, line)
|
|
Packit |
ae9e2a |
git_buf_put(out, line->content, line->content_len);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
done:
|
|
Packit |
ae9e2a |
patch_image_free(&image);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return error;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int apply_binary_delta(
|
|
Packit |
ae9e2a |
git_buf *out,
|
|
Packit |
ae9e2a |
const char *source,
|
|
Packit |
ae9e2a |
size_t source_len,
|
|
Packit |
ae9e2a |
git_diff_binary_file *binary_file)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
git_buf inflated = GIT_BUF_INIT;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
/* no diff means identical contents */
|
|
Packit |
ae9e2a |
if (binary_file->datalen == 0)
|
|
Packit |
ae9e2a |
return git_buf_put(out, source, source_len);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
error = git_zstream_inflatebuf(&inflated,
|
|
Packit |
ae9e2a |
binary_file->data, binary_file->datalen);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (!error && inflated.size != binary_file->inflatedlen) {
|
|
Packit |
ae9e2a |
error = apply_err("inflated delta does not match expected length");
|
|
Packit |
ae9e2a |
git_buf_free(out);
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (error < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (binary_file->type == GIT_DIFF_BINARY_DELTA) {
|
|
Packit |
ae9e2a |
void *data;
|
|
Packit |
ae9e2a |
size_t data_len;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
error = git_delta_apply(&data, &data_len, (void *)source, source_len,
|
|
Packit |
ae9e2a |
(void *)inflated.ptr, inflated.size);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
out->ptr = data;
|
|
Packit |
ae9e2a |
out->size = data_len;
|
|
Packit |
ae9e2a |
out->asize = data_len;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) {
|
|
Packit |
ae9e2a |
git_buf_swap(out, &inflated);
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
else {
|
|
Packit |
ae9e2a |
error = apply_err("unknown binary delta type");
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
done:
|
|
Packit |
ae9e2a |
git_buf_free(&inflated);
|
|
Packit |
ae9e2a |
return error;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
static int apply_binary(
|
|
Packit |
ae9e2a |
git_buf *out,
|
|
Packit |
ae9e2a |
const char *source,
|
|
Packit |
ae9e2a |
size_t source_len,
|
|
Packit |
ae9e2a |
git_patch *patch)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
git_buf reverse = GIT_BUF_INIT;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (!patch->binary.contains_data) {
|
|
Packit |
ae9e2a |
error = apply_err("patch does not contain binary data");
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
/* first, apply the new_file delta to the given source */
|
|
Packit |
ae9e2a |
if ((error = apply_binary_delta(out, source, source_len,
|
|
Packit |
ae9e2a |
&patch->binary.new_file)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
/* second, apply the old_file delta to sanity check the result */
|
|
Packit |
ae9e2a |
if ((error = apply_binary_delta(&reverse, out->ptr, out->size,
|
|
Packit |
ae9e2a |
&patch->binary.old_file)) < 0)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (source_len != reverse.size ||
|
|
Packit |
ae9e2a |
memcmp(source, reverse.ptr, source_len) != 0) {
|
|
Packit |
ae9e2a |
error = apply_err("binary patch did not apply cleanly");
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
done:
|
|
Packit |
ae9e2a |
if (error < 0)
|
|
Packit |
ae9e2a |
git_buf_free(out);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
git_buf_free(&reverse);
|
|
Packit |
ae9e2a |
return error;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
int git_apply__patch(
|
|
Packit |
ae9e2a |
git_buf *contents_out,
|
|
Packit |
ae9e2a |
char **filename_out,
|
|
Packit |
ae9e2a |
unsigned int *mode_out,
|
|
Packit |
ae9e2a |
const char *source,
|
|
Packit |
ae9e2a |
size_t source_len,
|
|
Packit |
ae9e2a |
git_patch *patch)
|
|
Packit |
ae9e2a |
{
|
|
Packit |
ae9e2a |
char *filename = NULL;
|
|
Packit |
ae9e2a |
unsigned int mode = 0;
|
|
Packit |
ae9e2a |
int error = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
*filename_out = NULL;
|
|
Packit |
ae9e2a |
*mode_out = 0;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (patch->delta->status != GIT_DELTA_DELETED) {
|
|
Packit |
ae9e2a |
const git_diff_file *newfile = &patch->delta->new_file;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
filename = git__strdup(newfile->path);
|
|
Packit |
ae9e2a |
mode = newfile->mode ?
|
|
Packit |
ae9e2a |
newfile->mode : GIT_FILEMODE_BLOB;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
|
|
Packit |
ae9e2a |
error = apply_binary(contents_out, source, source_len, patch);
|
|
Packit |
ae9e2a |
else if (patch->hunks.size)
|
|
Packit |
ae9e2a |
error = apply_hunks(contents_out, source, source_len, patch);
|
|
Packit |
ae9e2a |
else
|
|
Packit |
ae9e2a |
error = git_buf_put(contents_out, source, source_len);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (error)
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
if (patch->delta->status == GIT_DELTA_DELETED &&
|
|
Packit |
ae9e2a |
git_buf_len(contents_out) > 0) {
|
|
Packit |
ae9e2a |
error = apply_err("removal patch leaves file contents");
|
|
Packit |
ae9e2a |
goto done;
|
|
Packit |
ae9e2a |
}
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
*filename_out = filename;
|
|
Packit |
ae9e2a |
*mode_out = mode;
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
done:
|
|
Packit |
ae9e2a |
if (error < 0)
|
|
Packit |
ae9e2a |
git__free(filename);
|
|
Packit |
ae9e2a |
|
|
Packit |
ae9e2a |
return error;
|
|
Packit |
ae9e2a |
}
|