Commit 20302aa4 by Edward Thomson Committed by GitHub

Merge pull request #3223 from ethomson/apply

Reading patch files
parents 8774c47e 1a79cd95
......@@ -264,10 +264,15 @@ typedef enum {
* link, a submodule commit id, or even a tree (although that only if you
* are tracking type changes or ignored/untracked directories).
*
* The `oid` is the `git_oid` of the item. If the entry represents an
* The `id` is the `git_oid` of the item. If the entry represents an
* absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),
* then the oid will be zeroes.
*
* The `id_abbrev` represents the known length of the `id` field, when
* converted to a hex string. It is generally `GIT_OID_HEXSZ`, unless this
* delta was created from reading a patch file, in which case it may be
* abbreviated to something reasonable, like 7 characters.
*
* `path` is the NUL-terminated path to the entry relative to the working
* directory of the repository.
*
......@@ -280,6 +285,7 @@ typedef enum {
*/
typedef struct {
git_oid id;
int id_abbrev;
const char *path;
git_off_t size;
uint32_t flags;
......@@ -448,6 +454,8 @@ typedef int (*git_diff_file_cb)(
float progress,
void *payload);
#define GIT_DIFF_HUNK_HEADER_SIZE 128
/**
* When producing a binary diff, the binary data returned will be
* either the deflated full ("literal") contents of the file, or
......@@ -499,12 +507,12 @@ typedef int(*git_diff_binary_cb)(
* Structure describing a hunk of a diff.
*/
typedef struct {
int old_start; /**< Starting line number in old_file */
int old_lines; /**< Number of lines in old_file */
int new_start; /**< Starting line number in new_file */
int new_lines; /**< Number of lines in new_file */
size_t header_len; /**< Number of bytes in header text */
char header[128]; /**< Header text, NUL-byte terminated */
int old_start; /** Starting line number in old_file */
int old_lines; /** Number of lines in old_file */
int new_start; /** Starting line number in new_file */
int new_lines; /** Number of lines in new_file */
size_t header_len; /** Number of bytes in header text */
char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */
} git_diff_hunk;
/**
......@@ -1046,6 +1054,21 @@ GIT_EXTERN(int) git_diff_print(
git_diff_line_cb print_cb,
void *payload);
/**
* Produce the complete formatted text output from a diff into a
* buffer.
*
* @param out A pointer to a user-allocated git_buf that will
* contain the diff text
* @param diff A git_diff generated by one of the above functions.
* @param format A git_diff_format_t value to pick the text format.
* @return 0 on success or error code
*/
GIT_EXTERN(int) git_diff_to_buf(
git_buf *out,
git_diff *diff,
git_diff_format_t format);
/**@}*/
......@@ -1166,6 +1189,11 @@ GIT_EXTERN(int) git_diff_buffers(
git_diff_line_cb line_cb,
void *payload);
GIT_EXTERN(int) git_diff_from_buffer(
git_diff **out,
const char *content,
size_t content_len);
/**
* This is an opaque structure which is allocated by `git_diff_get_stats`.
* You are responsible for releasing the object memory when done, using the
......
......@@ -98,7 +98,8 @@ typedef enum {
GITERR_CHERRYPICK,
GITERR_DESCRIBE,
GITERR_REBASE,
GITERR_FILESYSTEM
GITERR_FILESYSTEM,
GITERR_PATCH,
} git_error_t;
/**
......
/*
* 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.
*/
#include <assert.h>
#include "git2/patch.h"
#include "git2/filter.h"
#include "array.h"
#include "patch.h"
#include "fileops.h"
#include "apply.h"
#include "delta.h"
#include "zstream.h"
#define apply_err(...) \
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
typedef struct {
/* The lines that we allocate ourself are allocated out of the pool.
* (Lines may have been allocated out of the diff.)
*/
git_pool pool;
git_vector lines;
} patch_image;
static void patch_line_init(
git_diff_line *out,
const char *in,
size_t in_len,
size_t in_offset)
{
out->content = in;
out->content_len = in_len;
out->content_offset = in_offset;
}
#define PATCH_IMAGE_INIT { {0} }
static int patch_image_init_fromstr(
patch_image *out, const char *in, size_t in_len)
{
git_diff_line *line;
const char *start, *end;
memset(out, 0x0, sizeof(patch_image));
git_pool_init(&out->pool, sizeof(git_diff_line));
for (start = in; start < in + in_len; start = end) {
end = memchr(start, '\n', in_len);
if (end < in + in_len)
end++;
line = git_pool_mallocz(&out->pool, 1);
GITERR_CHECK_ALLOC(line);
if (git_vector_insert(&out->lines, line) < 0)
return -1;
patch_line_init(line, start, (end - start), (start - in));
}
return 0;
}
static void patch_image_free(patch_image *image)
{
if (image == NULL)
return;
git_pool_clear(&image->pool);
git_vector_free(&image->lines);
}
static bool match_hunk(
patch_image *image,
patch_image *preimage,
size_t linenum)
{
bool match = 0;
size_t i;
/* Ensure this hunk is within the image boundaries. */
if (git_vector_length(&preimage->lines) + linenum >
git_vector_length(&image->lines))
return 0;
match = 1;
/* Check exact match. */
for (i = 0; i < git_vector_length(&preimage->lines); i++) {
git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
if (preimage_line->content_len != preimage_line->content_len ||
memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
match = 0;
break;
}
}
return match;
}
static bool find_hunk_linenum(
size_t *out,
patch_image *image,
patch_image *preimage,
size_t linenum)
{
size_t max = git_vector_length(&image->lines);
bool match;
if (linenum > max)
linenum = max;
match = match_hunk(image, preimage, linenum);
*out = linenum;
return match;
}
static int update_hunk(
patch_image *image,
unsigned int linenum,
patch_image *preimage,
patch_image *postimage)
{
size_t postlen = git_vector_length(&postimage->lines);
size_t prelen = git_vector_length(&preimage->lines);
size_t i;
int error = 0;
if (postlen > prelen)
error = git_vector_insert_null(
&image->lines, linenum, (postlen - prelen));
else if (prelen > postlen)
error = git_vector_remove_range(
&image->lines, linenum, (prelen - postlen));
if (error) {
giterr_set_oom();
return -1;
}
for (i = 0; i < git_vector_length(&postimage->lines); i++) {
image->lines.contents[linenum + i] =
git_vector_get(&postimage->lines, i);
}
return 0;
}
static int apply_hunk(
patch_image *image,
git_patch *patch,
git_patch_hunk *hunk)
{
patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT;
size_t line_num, i;
int error = 0;
for (i = 0; i < hunk->line_count; i++) {
size_t linenum = hunk->line_start + i;
git_diff_line *line = git_array_get(patch->lines, linenum);
if (!line) {
error = apply_err("Preimage does not contain line %d", linenum);
goto done;
}
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_DELETION) {
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
goto done;
}
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_ADDITION) {
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
}
}
line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0;
if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
error = apply_err("Hunk at line %d did not apply",
hunk->hunk.new_start);
goto done;
}
error = update_hunk(image, line_num, &preimage, &postimage);
done:
patch_image_free(&preimage);
patch_image_free(&postimage);
return error;
}
static int apply_hunks(
git_buf *out,
const char *source,
size_t source_len,
git_patch *patch)
{
git_patch_hunk *hunk;
git_diff_line *line;
patch_image image;
size_t i;
int error = 0;
if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
goto done;
git_array_foreach(patch->hunks, i, hunk) {
if ((error = apply_hunk(&image, patch, hunk)) < 0)
goto done;
}
git_vector_foreach(&image.lines, i, line)
git_buf_put(out, line->content, line->content_len);
done:
patch_image_free(&image);
return error;
}
static int apply_binary_delta(
git_buf *out,
const char *source,
size_t source_len,
git_diff_binary_file *binary_file)
{
git_buf inflated = GIT_BUF_INIT;
int error = 0;
/* no diff means identical contents */
if (binary_file->datalen == 0)
return git_buf_put(out, source, source_len);
error = git_zstream_inflatebuf(&inflated,
binary_file->data, binary_file->datalen);
if (!error && inflated.size != binary_file->inflatedlen) {
error = apply_err("inflated delta does not match expected length");
git_buf_free(out);
}
if (error < 0)
goto done;
if (binary_file->type == GIT_DIFF_BINARY_DELTA) {
void *data;
size_t data_len;
error = git_delta_apply(&data, &data_len, (void *)source, source_len,
(void *)inflated.ptr, inflated.size);
out->ptr = data;
out->size = data_len;
out->asize = data_len;
}
else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) {
git_buf_swap(out, &inflated);
}
else {
error = apply_err("unknown binary delta type");
goto done;
}
done:
git_buf_free(&inflated);
return error;
}
static int apply_binary(
git_buf *out,
const char *source,
size_t source_len,
git_patch *patch)
{
git_buf reverse = GIT_BUF_INIT;
int error;
/* first, apply the new_file delta to the given source */
if ((error = apply_binary_delta(out, source, source_len,
&patch->binary.new_file)) < 0)
goto done;
/* second, apply the old_file delta to sanity check the result */
if ((error = apply_binary_delta(&reverse, out->ptr, out->size,
&patch->binary.old_file)) < 0)
goto done;
if (source_len != reverse.size ||
memcmp(source, reverse.ptr, source_len) != 0) {
error = apply_err("binary patch did not apply cleanly");
goto done;
}
done:
if (error < 0)
git_buf_free(out);
git_buf_free(&reverse);
return error;
}
int git_apply__patch(
git_buf *contents_out,
char **filename_out,
unsigned int *mode_out,
const char *source,
size_t source_len,
git_patch *patch)
{
char *filename = NULL;
unsigned int mode = 0;
int error = 0;
assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
*filename_out = NULL;
*mode_out = 0;
if (patch->delta->status != GIT_DELTA_DELETED) {
const git_diff_file *newfile = &patch->delta->new_file;
filename = git__strdup(newfile->path);
mode = newfile->mode ?
newfile->mode : GIT_FILEMODE_BLOB;
}
if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
error = apply_binary(contents_out, source, source_len, patch);
else if (patch->hunks.size)
error = apply_hunks(contents_out, source, source_len, patch);
else
error = git_buf_put(contents_out, source, source_len);
if (error)
goto done;
if (patch->delta->status == GIT_DELTA_DELETED &&
git_buf_len(contents_out) > 0) {
error = apply_err("removal patch leaves file contents");
goto done;
}
*filename_out = filename;
*mode_out = mode;
done:
if (error < 0)
git__free(filename);
return error;
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_apply_h__
#define INCLUDE_apply_h__
#include "git2/patch.h"
#include "buffer.h"
extern int git_apply__patch(
git_buf *out,
char **filename,
unsigned int *mode,
const char *source,
size_t source_len,
git_patch *patch);
#endif
......@@ -85,7 +85,6 @@ on_oom:
#define git_array_foreach(a, i, element) \
for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
GIT_INLINE(int) git_array__search(
size_t *out,
void *array_ptr,
......
......@@ -273,42 +273,51 @@ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
return 0;
}
/* The inverse of base64_encode, offset by '+' == 43. */
/* The inverse of base64_encode */
static const int8_t base64_decode[] = {
62,
-1, -1, -1,
63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
-1, -1, -1, 0, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
-1, -1, -1, -1, -1, -1,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
#define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43])
int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
{
size_t i;
int8_t a, b, c, d;
size_t orig_size = buf->size, new_size;
if (len % 4) {
giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
assert(len % 4 == 0);
GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
for (i = 0; i < len; i += 4) {
if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 ||
(b = BASE64_DECODE_VALUE(base64[i+1])) < 0 ||
(c = BASE64_DECODE_VALUE(base64[i+2])) < 0 ||
(d = BASE64_DECODE_VALUE(base64[i+3])) < 0) {
if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
(b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
(c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
(d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
buf->size = orig_size;
buf->ptr[buf->size] = '\0';
giterr_set(GITERR_INVALID, "Invalid base64 input");
giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
......@@ -321,7 +330,7 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
return 0;
}
static const char b85str[] =
static const char base85_encode[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
......@@ -351,7 +360,7 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
int val = acc % 85;
acc /= 85;
b85[i] = b85str[val];
b85[i] = base85_encode[val];
}
for (i = 0; i < 5; i++)
......@@ -363,6 +372,88 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
return 0;
}
/* The inverse of base85_encode */
static const int8_t base85_decode[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77,
78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
int git_buf_decode_base85(
git_buf *buf,
const char *base85,
size_t base85_len,
size_t output_len)
{
size_t orig_size = buf->size, new_size;
if (base85_len % 5 ||
output_len > base85_len * 4 / 5) {
giterr_set(GITERR_INVALID, "invalid base85 input");
return -1;
}
GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
while (output_len) {
unsigned acc = 0;
int de, cnt = 4;
unsigned char ch;
do {
ch = *base85++;
de = base85_decode[ch];
if (--de < 0)
goto on_error;
acc = acc * 85 + de;
} while (--cnt);
ch = *base85++;
de = base85_decode[ch];
if (--de < 0)
goto on_error;
/* Detect overflow. */
if (0xffffffff / 85 < acc ||
0xffffffff - de < (acc *= 85))
goto on_error;
acc += de;
cnt = (output_len < 4) ? output_len : 4;
output_len -= cnt;
do {
acc = (acc << 8) | (acc >> 24);
buf->ptr[buf->size++] = acc;
} while (--cnt);
}
buf->ptr[buf->size] = 0;
return 0;
on_error:
buf->size = orig_size;
buf->ptr[buf->size] = '\0';
giterr_set(GITERR_INVALID, "invalid base85 input");
return -1;
}
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
size_t expected_size, new_size;
......@@ -766,3 +857,144 @@ int git_buf_splice(
buf->ptr[buf->size] = '\0';
return 0;
}
/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_quote(git_buf *buf)
{
const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
git_buf quoted = GIT_BUF_INIT;
size_t i = 0;
bool quote = false;
int error = 0;
/* walk to the first char that needs quoting */
if (buf->size && buf->ptr[0] == '!')
quote = true;
for (i = 0; !quote && i < buf->size; i++) {
if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
quote = true;
break;
}
}
if (!quote)
goto done;
git_buf_putc(&quoted, '"');
git_buf_put(&quoted, buf->ptr, i);
for (; i < buf->size; i++) {
/* whitespace - use the map above, which is ordered by ascii value */
if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
git_buf_putc(&quoted, '\\');
git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
}
/* double quote and backslash must be escaped */
else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
git_buf_putc(&quoted, '\\');
git_buf_putc(&quoted, buf->ptr[i]);
}
/* escape anything unprintable as octal */
else if (buf->ptr[i] != ' ' &&
(buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
}
/* yay, printable! */
else {
git_buf_putc(&quoted, buf->ptr[i]);
}
}
git_buf_putc(&quoted, '"');
if (git_buf_oom(&quoted)) {
error = -1;
goto done;
}
git_buf_swap(&quoted, buf);
done:
git_buf_free(&quoted);
return error;
}
/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
int git_buf_unquote(git_buf *buf)
{
size_t i, j;
char ch;
git_buf_rtrim(buf);
if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
goto invalid;
for (i = 0, j = 1; j < buf->size-1; i++, j++) {
ch = buf->ptr[j];
if (ch == '\\') {
if (j == buf->size-2)
goto invalid;
ch = buf->ptr[++j];
switch (ch) {
/* \" or \\ simply copy the char in */
case '"': case '\\':
break;
/* add the appropriate escaped char */
case 'a': ch = '\a'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case 'v': ch = '\v'; break;
/* \xyz digits convert to the char*/
case '0': case '1': case '2': case '3':
if (j == buf->size-3) {
giterr_set(GITERR_INVALID,
"Truncated quoted character \\%c", ch);
return -1;
}
if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
giterr_set(GITERR_INVALID,
"Truncated quoted character \\%c%c%c",
buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
return -1;
}
ch = ((buf->ptr[j] - '0') << 6) |
((buf->ptr[j+1] - '0') << 3) |
(buf->ptr[j+2] - '0');
j += 2;
break;
default:
giterr_set(GITERR_INVALID, "Invalid quoted character \\%c", ch);
return -1;
}
}
buf->ptr[i] = ch;
}
buf->ptr[i] = '\0';
buf->size = i;
return 0;
invalid:
giterr_set(GITERR_INVALID, "Invalid quoted line");
return -1;
}
......@@ -173,6 +173,12 @@ void git_buf_rtrim(git_buf *buf);
int git_buf_cmp(const git_buf *a, const git_buf *b);
/* Quote and unquote a buffer as specified in
* http://marc.info/?l=git&m=112927316408690&w=2
*/
int git_buf_quote(git_buf *buf);
int git_buf_unquote(git_buf *buf);
/* Write data as base64 encoded in buffer */
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len);
/* Decode the given bas64 and write the result to the buffer */
......@@ -180,6 +186,8 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len);
/* Write data as "base85" encoded in buffer */
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
/* Decode the given "base85" and write the result to the buffer */
int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len);
/*
* Insert, remove or replace a portion of the buffer.
......
......@@ -26,6 +26,7 @@
#include "filter.h"
#include "blob.h"
#include "diff.h"
#include "diff_generate.h"
#include "pathspec.h"
#include "buf_text.h"
#include "diff_xdiff.h"
......
/*
* 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.
*/
#include "common.h"
#include "git2/odb.h"
#include "delta-apply.h"
/*
* This file was heavily cribbed from BinaryDelta.java in JGit, which
* itself was heavily cribbed from <code>patch-delta.c</code> in the
* GIT project. The original delta patching code was written by
* Nicolas Pitre <nico@cam.org>.
*/
static int hdr_sz(
size_t *size,
const unsigned char **delta,
const unsigned char *end)
{
const unsigned char *d = *delta;
size_t r = 0;
unsigned int c, shift = 0;
do {
if (d == end)
return -1;
c = *d++;
r |= (c & 0x7f) << shift;
shift += 7;
} while (c & 0x80);
*delta = d;
*size = r;
return 0;
}
int git__delta_read_header(
const unsigned char *delta,
size_t delta_len,
size_t *base_sz,
size_t *res_sz)
{
const unsigned char *delta_end = delta + delta_len;
if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
(hdr_sz(res_sz, &delta, delta_end) < 0))
return -1;
return 0;
}
#define DELTA_HEADER_BUFFER_LEN 16
int git__delta_read_header_fromstream(size_t *base_sz, size_t *res_sz, git_packfile_stream *stream)
{
static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN;
unsigned char buffer[DELTA_HEADER_BUFFER_LEN];
const unsigned char *delta, *delta_end;
size_t len;
ssize_t read;
len = read = 0;
while (len < buffer_len) {
read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len);
if (read == 0)
break;
if (read == GIT_EBUFS)
continue;
len += read;
}
delta = buffer;
delta_end = delta + len;
if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
(hdr_sz(res_sz, &delta, delta_end) < 0))
return -1;
return 0;
}
int git__delta_apply(
git_rawobj *out,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len)
{
const unsigned char *delta_end = delta + delta_len;
size_t base_sz, res_sz, alloc_sz;
unsigned char *res_dp;
/* Check that the base size matches the data we were given;
* if not we would underflow while accessing data from the
* base object, resulting in data corruption or segfault.
*/
if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) {
giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
return -1;
}
if (hdr_sz(&res_sz, &delta, delta_end) < 0) {
giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
return -1;
}
GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
res_dp = git__malloc(alloc_sz);
GITERR_CHECK_ALLOC(res_dp);
res_dp[res_sz] = '\0';
out->data = res_dp;
out->len = res_sz;
while (delta < delta_end) {
unsigned char cmd = *delta++;
if (cmd & 0x80) {
/* cmd is a copy instruction; copy from the base.
*/
size_t off = 0, len = 0;
if (cmd & 0x01) off = *delta++;
if (cmd & 0x02) off |= *delta++ << 8UL;
if (cmd & 0x04) off |= *delta++ << 16UL;
if (cmd & 0x08) off |= *delta++ << 24UL;
if (cmd & 0x10) len = *delta++;
if (cmd & 0x20) len |= *delta++ << 8UL;
if (cmd & 0x40) len |= *delta++ << 16UL;
if (!len) len = 0x10000;
if (base_len < off + len || res_sz < len)
goto fail;
memcpy(res_dp, base + off, len);
res_dp += len;
res_sz -= len;
} else if (cmd) {
/* cmd is a literal insert instruction; copy from
* the delta stream itself.
*/
if (delta_end - delta < cmd || res_sz < cmd)
goto fail;
memcpy(res_dp, delta, cmd);
delta += cmd;
res_dp += cmd;
res_sz -= cmd;
} else {
/* cmd == 0 is reserved for future encodings.
*/
goto fail;
}
}
if (delta != delta_end || res_sz)
goto fail;
return 0;
fail:
git__free(out->data);
out->data = NULL;
giterr_set(GITERR_INVALID, "Failed to apply delta");
return -1;
}
/*
* 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_delta_apply_h__
#define INCLUDE_delta_apply_h__
#include "odb.h"
#include "pack.h"
/**
* Apply a git binary delta to recover the original content.
*
* @param out the output buffer to receive the original data.
* Only out->data and out->len are populated, as this is
* the only information available in the delta.
* @param base the base to copy from during copy instructions.
* @param base_len number of bytes available at base.
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return
* - 0 on a successful delta unpack.
* - GIT_ERROR if the delta is corrupt or doesn't match the base.
*/
extern int git__delta_apply(
git_rawobj *out,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len);
/**
* Read the header of a git binary delta.
*
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @param base_sz pointer to store the base size field.
* @param res_sz pointer to store the result size field.
* @return
* - 0 on a successful decoding the header.
* - GIT_ERROR if the delta is corrupt.
*/
extern int git__delta_read_header(
const unsigned char *delta,
size_t delta_len,
size_t *base_sz,
size_t *res_sz);
/**
* Read the header of a git binary delta
*
* This variant reads just enough from the packfile stream to read the
* delta header.
*/
extern int git__delta_read_header_fromstream(
size_t *base_sz,
size_t *res_sz,
git_packfile_stream *stream);
#endif
......@@ -114,7 +114,7 @@ struct index_entry {
struct git_delta_index {
unsigned long memsize;
const void *src_buf;
unsigned long src_size;
size_t src_size;
unsigned int hash_mask;
struct index_entry *hash[GIT_FLEX_ARRAY];
};
......@@ -142,8 +142,8 @@ static int lookup_index_alloc(
return 0;
}
struct git_delta_index *
git_delta_create_index(const void *buf, unsigned long bufsize)
int git_delta_index_init(
git_delta_index **out, const void *buf, size_t bufsize)
{
unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
const unsigned char *data, *buffer = buf;
......@@ -152,8 +152,10 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
void *mem;
unsigned long memsize;
*out = NULL;
if (!buf || !bufsize)
return NULL;
return 0;
/* Determine index hash size. Note that indexing skips the
first byte to allow for optimizing the rabin polynomial
......@@ -172,7 +174,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hmask = hsize - 1;
if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0)
return NULL;
return -1;
index = mem;
mem = index->hash;
......@@ -190,7 +192,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hash_count = git__calloc(hsize, sizeof(*hash_count));
if (!hash_count) {
git__free(index);
return NULL;
return -1;
}
/* then populate the index */
......@@ -243,20 +245,20 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
}
git__free(hash_count);
return index;
*out = index;
return 0;
}
void git_delta_free_index(struct git_delta_index *index)
void git_delta_index_free(git_delta_index *index)
{
git__free(index);
}
unsigned long git_delta_sizeof_index(struct git_delta_index *index)
size_t git_delta_index_size(git_delta_index *index)
{
if (index)
assert(index);
return index->memsize;
else
return 0;
}
/*
......@@ -265,55 +267,57 @@ unsigned long git_delta_sizeof_index(struct git_delta_index *index)
*/
#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
void *
git_delta_create(
int git_delta_create_from_index(
void **out,
size_t *out_len,
const struct git_delta_index *index,
const void *trg_buf,
unsigned long trg_size,
unsigned long *delta_size,
unsigned long max_size)
size_t trg_size,
size_t max_size)
{
unsigned int i, outpos, outsize, moff, msize, val;
unsigned int i, bufpos, bufsize, moff, msize, val;
int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top;
unsigned char *out;
unsigned char *buf;
*out = NULL;
*out_len = 0;
if (!trg_buf || !trg_size)
return NULL;
return 0;
outpos = 0;
outsize = 8192;
if (max_size && outsize >= max_size)
outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
out = git__malloc(outsize);
if (!out)
return NULL;
bufpos = 0;
bufsize = 8192;
if (max_size && bufsize >= max_size)
bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
buf = git__malloc(bufsize);
GITERR_CHECK_ALLOC(buf);
/* store reference buffer size */
i = index->src_size;
while (i >= 0x80) {
out[outpos++] = i | 0x80;
buf[bufpos++] = i | 0x80;
i >>= 7;
}
out[outpos++] = i;
buf[bufpos++] = i;
/* store target buffer size */
i = trg_size;
while (i >= 0x80) {
out[outpos++] = i | 0x80;
buf[bufpos++] = i | 0x80;
i >>= 7;
}
out[outpos++] = i;
buf[bufpos++] = i;
ref_data = index->src_buf;
ref_top = ref_data + index->src_size;
data = trg_buf;
top = (const unsigned char *) trg_buf + trg_size;
outpos++;
bufpos++;
val = 0;
for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
out[outpos++] = *data;
buf[bufpos++] = *data;
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
}
inscnt = i;
......@@ -350,11 +354,11 @@ git_delta_create(
if (msize < 4) {
if (!inscnt)
outpos++;
out[outpos++] = *data++;
bufpos++;
buf[bufpos++] = *data++;
inscnt++;
if (inscnt == 0x7f) {
out[outpos - inscnt - 1] = inscnt;
buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
msize = 0;
......@@ -368,14 +372,14 @@ git_delta_create(
msize++;
moff--;
data--;
outpos--;
bufpos--;
if (--inscnt)
continue;
outpos--; /* remove count slot */
bufpos--; /* remove count slot */
inscnt--; /* make it -1 */
break;
}
out[outpos - inscnt - 1] = inscnt;
buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
......@@ -383,22 +387,22 @@ git_delta_create(
left = (msize < 0x10000) ? 0 : (msize - 0x10000);
msize -= left;
op = out + outpos++;
op = buf + bufpos++;
i = 0x80;
if (moff & 0x000000ff)
out[outpos++] = moff >> 0, i |= 0x01;
buf[bufpos++] = moff >> 0, i |= 0x01;
if (moff & 0x0000ff00)
out[outpos++] = moff >> 8, i |= 0x02;
buf[bufpos++] = moff >> 8, i |= 0x02;
if (moff & 0x00ff0000)
out[outpos++] = moff >> 16, i |= 0x04;
buf[bufpos++] = moff >> 16, i |= 0x04;
if (moff & 0xff000000)
out[outpos++] = moff >> 24, i |= 0x08;
buf[bufpos++] = moff >> 24, i |= 0x08;
if (msize & 0x00ff)
out[outpos++] = msize >> 0, i |= 0x10;
buf[bufpos++] = msize >> 0, i |= 0x10;
if (msize & 0xff00)
out[outpos++] = msize >> 8, i |= 0x20;
buf[bufpos++] = msize >> 8, i |= 0x20;
*op = i;
......@@ -415,29 +419,201 @@ git_delta_create(
}
}
if (outpos >= outsize - MAX_OP_SIZE) {
void *tmp = out;
outsize = outsize * 3 / 2;
if (max_size && outsize >= max_size)
outsize = max_size + MAX_OP_SIZE + 1;
if (max_size && outpos > max_size)
if (bufpos >= bufsize - MAX_OP_SIZE) {
void *tmp = buf;
bufsize = bufsize * 3 / 2;
if (max_size && bufsize >= max_size)
bufsize = max_size + MAX_OP_SIZE + 1;
if (max_size && bufpos > max_size)
break;
out = git__realloc(out, outsize);
if (!out) {
buf = git__realloc(buf, bufsize);
if (!buf) {
git__free(tmp);
return NULL;
return -1;
}
}
}
if (inscnt)
out[outpos - inscnt - 1] = inscnt;
buf[bufpos - inscnt - 1] = inscnt;
if (max_size && bufpos > max_size) {
giterr_set(GITERR_NOMEMORY, "delta would be larger than maximum size");
git__free(buf);
return GIT_EBUFS;
}
*out_len = bufpos;
*out = buf;
return 0;
}
/*
* Delta application was heavily cribbed from BinaryDelta.java in JGit, which
* itself was heavily cribbed from <code>patch-delta.c</code> in the
* GIT project. The original delta patching code was written by
* Nicolas Pitre <nico@cam.org>.
*/
static int hdr_sz(
size_t *size,
const unsigned char **delta,
const unsigned char *end)
{
const unsigned char *d = *delta;
size_t r = 0;
unsigned int c, shift = 0;
do {
if (d == end) {
giterr_set(GITERR_INVALID, "truncated delta");
return -1;
}
c = *d++;
r |= (c & 0x7f) << shift;
shift += 7;
} while (c & 0x80);
*delta = d;
*size = r;
return 0;
}
int git_delta_read_header(
size_t *base_out,
size_t *result_out,
const unsigned char *delta,
size_t delta_len)
{
const unsigned char *delta_end = delta + delta_len;
if ((hdr_sz(base_out, &delta, delta_end) < 0) ||
(hdr_sz(result_out, &delta, delta_end) < 0))
return -1;
return 0;
}
#define DELTA_HEADER_BUFFER_LEN 16
int git_delta_read_header_fromstream(
size_t *base_sz, size_t *res_sz, git_packfile_stream *stream)
{
static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN;
unsigned char buffer[DELTA_HEADER_BUFFER_LEN];
const unsigned char *delta, *delta_end;
size_t len;
ssize_t read;
len = read = 0;
while (len < buffer_len) {
read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len);
if (read == 0)
break;
if (read == GIT_EBUFS)
continue;
len += read;
}
delta = buffer;
delta_end = delta + len;
if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
(hdr_sz(res_sz, &delta, delta_end) < 0))
return -1;
return 0;
}
int git_delta_apply(
void **out,
size_t *out_len,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len)
{
const unsigned char *delta_end = delta + delta_len;
size_t base_sz, res_sz, alloc_sz;
unsigned char *res_dp;
*out = NULL;
*out_len = 0;
/* Check that the base size matches the data we were given;
* if not we would underflow while accessing data from the
* base object, resulting in data corruption or segfault.
*/
if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) {
giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
return -1;
}
if (hdr_sz(&res_sz, &delta, delta_end) < 0) {
giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data");
return -1;
}
if (max_size && outpos > max_size) {
git__free(out);
return NULL;
GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
res_dp = git__malloc(alloc_sz);
GITERR_CHECK_ALLOC(res_dp);
res_dp[res_sz] = '\0';
*out = res_dp;
*out_len = res_sz;
while (delta < delta_end) {
unsigned char cmd = *delta++;
if (cmd & 0x80) {
/* cmd is a copy instruction; copy from the base.
*/
size_t off = 0, len = 0;
if (cmd & 0x01) off = *delta++;
if (cmd & 0x02) off |= *delta++ << 8UL;
if (cmd & 0x04) off |= *delta++ << 16UL;
if (cmd & 0x08) off |= *delta++ << 24UL;
if (cmd & 0x10) len = *delta++;
if (cmd & 0x20) len |= *delta++ << 8UL;
if (cmd & 0x40) len |= *delta++ << 16UL;
if (!len) len = 0x10000;
if (base_len < off + len || res_sz < len)
goto fail;
memcpy(res_dp, base + off, len);
res_dp += len;
res_sz -= len;
}
else if (cmd) {
/* cmd is a literal insert instruction; copy from
* the delta stream itself.
*/
if (delta_end - delta < cmd || res_sz < cmd)
goto fail;
memcpy(res_dp, delta, cmd);
delta += cmd;
res_dp += cmd;
res_sz -= cmd;
}
else {
/* cmd == 0 is reserved for future encodings.
*/
goto fail;
}
}
*delta_size = outpos;
return out;
if (delta != delta_end || res_sz)
goto fail;
return 0;
fail:
git__free(*out);
*out = NULL;
*out_len = 0;
giterr_set(GITERR_INVALID, "Failed to apply delta");
return -1;
}
......@@ -6,12 +6,12 @@
#define INCLUDE_git_delta_h__
#include "common.h"
#include "pack.h"
/* opaque object for delta index */
struct git_delta_index;
typedef struct git_delta_index git_delta_index;
/*
* create_delta_index: compute index data from given buffer
* git_delta_index_init: compute index data from given buffer
*
* This returns a pointer to a struct delta_index that should be passed to
* subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
......@@ -19,22 +19,18 @@ struct git_delta_index;
* before free_delta_index() is called. The returned pointer must be freed
* using free_delta_index().
*/
extern struct git_delta_index *
git_delta_create_index(const void *buf, unsigned long bufsize);
extern int git_delta_index_init(
git_delta_index **out, const void *buf, size_t bufsize);
/*
* free_delta_index: free the index created by create_delta_index()
*
* Given pointer must be what create_delta_index() returned, or NULL.
* Free the index created by git_delta_index_init()
*/
extern void git_delta_free_index(struct git_delta_index *index);
extern void git_delta_index_free(git_delta_index *index);
/*
* sizeof_delta_index: returns memory usage of delta index
*
* Given pointer must be what create_delta_index() returned, or NULL.
* Returns memory usage of delta index.
*/
extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
extern size_t git_delta_index_size(git_delta_index *index);
/*
* create_delta: create a delta from given index for the given buffer
......@@ -46,69 +42,94 @@ extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
* returned and *delta_size is updated with its size. The returned buffer
* must be freed by the caller.
*/
extern void *git_delta_create(
extern int git_delta_create_from_index(
void **out,
size_t *out_size,
const struct git_delta_index *index,
const void *buf,
unsigned long bufsize,
unsigned long *delta_size,
unsigned long max_delta_size);
size_t bufsize,
size_t max_delta_size);
/*
* diff_delta: create a delta from source buffer to target buffer
*
* If max_delta_size is non-zero and the resulting delta is to be larger
* than max_delta_size then NULL is returned. On success, a non-NULL
* than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL
* pointer to the buffer with the delta data is returned and *delta_size is
* updated with its size. The returned buffer must be freed by the caller.
*/
GIT_INLINE(void *) git_delta(
const void *src_buf, unsigned long src_bufsize,
const void *trg_buf, unsigned long trg_bufsize,
unsigned long *delta_size,
unsigned long max_delta_size)
GIT_INLINE(int) git_delta(
void **out, size_t *out_len,
const void *src_buf, size_t src_bufsize,
const void *trg_buf, size_t trg_bufsize,
size_t max_delta_size)
{
struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize);
git_delta_index *index;
int error = 0;
*out = NULL;
*out_len = 0;
if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0)
return error;
if (index) {
void *delta = git_delta_create(
index, trg_buf, trg_bufsize, delta_size, max_delta_size);
git_delta_free_index(index);
return delta;
error = git_delta_create_from_index(out, out_len,
index, trg_buf, trg_bufsize, max_delta_size);
git_delta_index_free(index);
}
return NULL;
}
/*
* patch_delta: recreate target buffer given source buffer and delta data
*
* On success, a non-NULL pointer to the target buffer is returned and
* *trg_bufsize is updated with its size. On failure a NULL pointer is
* returned. The returned buffer must be freed by the caller.
*/
extern void *git_delta_patch(
const void *src_buf, unsigned long src_size,
const void *delta_buf, unsigned long delta_size,
unsigned long *dst_size);
return error;
}
/* the smallest possible delta size is 4 bytes */
#define GIT_DELTA_SIZE_MIN 4
/*
* This must be called twice on the delta data buffer, first to get the
* expected source buffer size, and again to get the target buffer size.
/**
* Apply a git binary delta to recover the original content.
* The caller is responsible for freeing the returned buffer.
*
* @param out the output buffer
* @param out_len the length of the output buffer
* @param base the base to copy from during copy instructions.
* @param base_len number of bytes available at base.
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return 0 on success or an error code
*/
extern int git_delta_apply(
void **out,
size_t *out_len,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len);
/**
* Read the header of a git binary delta.
*
* @param base_out pointer to store the base size field.
* @param result_out pointer to store the result size field.
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return 0 on success or an error code
*/
extern int git_delta_read_header(
size_t *base_out,
size_t *result_out,
const unsigned char *delta,
size_t delta_len);
/**
* Read the header of a git binary delta
*
* This variant reads just enough from the packfile stream to read the
* delta header.
*/
GIT_INLINE(unsigned long) git_delta_get_hdr_size(
const unsigned char **datap, const unsigned char *top)
{
const unsigned char *data = *datap;
unsigned long cmd, size = 0;
int i = 0;
do {
cmd = *data++;
size |= (cmd & 0x7f) << i;
i += 7;
} while (cmd & 0x80 && data < top);
*datap = data;
return size;
}
extern int git_delta_read_header_fromstream(
size_t *base_out,
size_t *result_out,
git_packfile_stream *stream);
#endif
......@@ -4,1476 +4,80 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/version.h"
#include "common.h"
#include "diff.h"
#include "fileops.h"
#include "config.h"
#include "attr_file.h"
#include "filter.h"
#include "pathspec.h"
#include "diff_generate.h"
#include "patch.h"
#include "commit.h"
#include "index.h"
#include "odb.h"
#include "submodule.h"
#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
(((DIFF)->opts.flags & (FLAG)) != 0)
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
(((DIFF)->opts.flags & (FLAG)) == 0)
#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
(VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
static git_diff_delta *diff_delta__alloc(
git_diff *diff,
git_delta_t status,
const char *path)
{
git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
if (!delta)
return NULL;
delta->old_file.path = git_pool_strdup(&diff->pool, path);
if (delta->old_file.path == NULL) {
git__free(delta);
return NULL;
}
delta->new_file.path = delta->old_file.path;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
switch (status) {
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
default: break; /* leave other status values alone */
}
}
delta->status = status;
return delta;
}
static int diff_insert_delta(
git_diff *diff, git_diff_delta *delta, const char *matched_pathspec)
{
int error = 0;
if (diff->opts.notify_cb) {
error = diff->opts.notify_cb(
diff, delta, matched_pathspec, diff->opts.payload);
if (error) {
git__free(delta);
if (error > 0) /* positive value means to skip this delta */
return 0;
else /* negative value means to cancel diff */
return giterr_set_after_callback_function(error, "git_diff");
}
}
if ((error = git_vector_insert(&diff->deltas, delta)) < 0)
git__free(delta);
return error;
}
static bool diff_pathspec_match(
const char **matched_pathspec,
git_diff *diff,
const git_index_entry *entry)
{
bool disable_pathspec_match =
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH);
/* If we're disabling fnmatch, then the iterator has already applied
* the filters to the files for us and we don't have to do anything.
* However, this only applies to *files* - the iterator will include
* directories that we need to recurse into when not autoexpanding,
* so we still need to apply the pathspec match to directories.
*/
if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) &&
disable_pathspec_match) {
*matched_pathspec = entry->path;
return true;
}
return git_pathspec__match(
&diff->pathspec, entry->path, disable_pathspec_match,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
matched_pathspec, NULL);
}
static int diff_delta__from_one(
git_diff *diff,
git_delta_t status,
const git_index_entry *oitem,
const git_index_entry *nitem)
{
const git_index_entry *entry = nitem;
bool has_old = false;
git_diff_delta *delta;
const char *matched_pathspec;
assert((oitem != NULL) ^ (nitem != NULL));
if (oitem) {
entry = oitem;
has_old = true;
}
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
has_old = !has_old;
if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
return 0;
if (status == GIT_DELTA_IGNORED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
return 0;
if (status == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
return 0;
if (status == GIT_DELTA_UNREADABLE &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
return 0;
if (!diff_pathspec_match(&matched_pathspec, diff, entry))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
/* This fn is just for single-sided diffs */
assert(status != GIT_DELTA_MODIFIED);
delta->nfiles = 1;
if (has_old) {
delta->old_file.mode = entry->mode;
delta->old_file.size = entry->file_size;
delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
git_oid_cpy(&delta->old_file.id, &entry->id);
} else /* ADDED, IGNORED, UNTRACKED */ {
delta->new_file.mode = entry->mode;
delta->new_file.size = entry->file_size;
delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
git_oid_cpy(&delta->new_file.id, &entry->id);
}
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
if (has_old || !git_oid_iszero(&delta->new_file.id))
delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
return diff_insert_delta(diff, delta, matched_pathspec);
}
static int diff_delta__from_two(
git_diff *diff,
git_delta_t status,
const git_index_entry *old_entry,
uint32_t old_mode,
const git_index_entry *new_entry,
uint32_t new_mode,
const git_oid *new_id,
const char *matched_pathspec)
{
const git_oid *old_id = &old_entry->id;
git_diff_delta *delta;
const char *canonical_path = old_entry->path;
if (status == GIT_DELTA_UNMODIFIED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 0;
if (!new_id)
new_id = &new_entry->id;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry;
const git_oid *temp_id = old_id;
old_entry = new_entry;
new_entry = temp_entry;
old_mode = new_mode;
new_mode = temp_mode;
old_id = new_id;
new_id = temp_id;
}
delta = diff_delta__alloc(diff, status, canonical_path);
GITERR_CHECK_ALLOC(delta);
delta->nfiles = 2;
if (!git_index_entry_is_conflict(old_entry)) {
delta->old_file.size = old_entry->file_size;
delta->old_file.mode = old_mode;
git_oid_cpy(&delta->old_file.id, old_id);
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
GIT_DIFF_FLAG_EXISTS;
}
if (!git_index_entry_is_conflict(new_entry)) {
git_oid_cpy(&delta->new_file.id, new_id);
delta->new_file.size = new_entry->file_size;
delta->new_file.mode = new_mode;
delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
if (!git_oid_iszero(&new_entry->id))
delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
}
return diff_insert_delta(diff, delta, matched_pathspec);
}
static git_diff_delta *diff_delta__last_for_item(
git_diff *diff,
const git_index_entry *item)
{
git_diff_delta *delta = git_vector_last(&diff->deltas);
if (!delta)
return NULL;
switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
case GIT_DELTA_DELETED:
if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_ADDED:
if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_UNREADABLE:
case GIT_DELTA_UNTRACKED:
if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_MODIFIED:
if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
default:
break;
}
return NULL;
}
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
{
size_t len = strlen(prefix);
/* append '/' at end if needed */
if (len > 0 && prefix[len - 1] != '/')
return git_pool_strcat(pool, prefix, "/");
else
return git_pool_strndup(pool, prefix, len + 1);
}
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
const char *str = delta->old_file.path;
if (!str ||
delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_COPIED)
str = delta->new_file.path;
return str;
}
const char *git_diff_delta__path(const git_diff_delta *delta)
{
return diff_delta__path(delta);
}
int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
{
return delta->old_file.path ?
delta->old_file.path : delta->new_file.path;
}
int git_diff_delta__i2w_cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__i2w_casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta)
{
uint32_t flags = opts ? opts->flags : 0;
if (delta->status == GIT_DELTA_UNMODIFIED &&
(flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true;
if (delta->status == GIT_DELTA_IGNORED &&
(flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true;
if (delta->status == GIT_DELTA_UNTRACKED &&
(flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
if (delta->status == GIT_DELTA_UNREADABLE &&
(flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
return true;
return false;
}
static const char *diff_mnemonic_prefix(
git_iterator_type_t type, bool left_side)
{
const char *pfx = "";
switch (type) {
case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
default: break;
}
/* note: without a deeper look at pathspecs, there is no easy way
* to get the (o)bject / (w)ork tree mnemonics working...
*/
return pfx;
}
static int diff_entry_cmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return strcmp(entry_a->path, entry_b->path);
}
static int diff_entry_icmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return strcasecmp(entry_a->path, entry_b->path);
}
static void diff_set_ignore_case(git_diff *diff, bool ignore_case)
{
if (!ignore_case) {
diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;
diff->strcomp = git__strcmp;
diff->strncomp = git__strncmp;
diff->pfxcomp = git__prefixcmp;
diff->entrycomp = diff_entry_cmp;
git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
} else {
diff->opts.flags |= GIT_DIFF_IGNORE_CASE;
diff->strcomp = git__strcasecmp;
diff->strncomp = git__strncasecmp;
diff->pfxcomp = git__prefixcmp_icase;
diff->entrycomp = diff_entry_icmp;
git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
}
git_vector_sort(&diff->deltas);
}
static git_diff *diff_list_alloc(
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter)
{
git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
git_diff *diff = git__calloc(1, sizeof(git_diff));
if (!diff)
return NULL;
assert(repo && old_iter && new_iter);
GIT_REFCOUNT_INC(diff);
diff->repo = repo;
diff->old_src = old_iter->type;
diff->new_src = new_iter->type;
memcpy(&diff->opts, &dflt, sizeof(diff->opts));
git_pool_init(&diff->pool, 1);
if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0) {
git_diff_free(diff);
return NULL;
}
/* Use case-insensitive compare if either iterator has
* the ignore_case bit set */
diff_set_ignore_case(
diff,
git_iterator_ignore_case(old_iter) ||
git_iterator_ignore_case(new_iter));
return diff;
}
static int diff_list_apply_options(
git_diff *diff,
const git_diff_options *opts)
{
git_config *cfg = NULL;
git_repository *repo = diff->repo;
git_pool *pool = &diff->pool;
int val;
if (opts) {
/* copy user options (except case sensitivity info from iterators) */
bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
memcpy(&diff->opts, opts, sizeof(diff->opts));
DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
/* initialize pathspec from options */
if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
return -1;
}
/* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
/* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
/* load config values that affect diff behavior */
if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
return val;
if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS;
if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT;
if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
!git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS;
if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
/* If not given explicit `opts`, check `diff.xyz` configs */
if (!opts) {
int context = git_config__get_int_force(cfg, "diff.context", 3);
diff->opts.context_lines = context >= 0 ? (uint32_t)context : 3;
/* add other defaults here */
}
/* Reverse src info if diff is reversed */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
git_iterator_type_t tmp_src = diff->old_src;
diff->old_src = diff->new_src;
diff->new_src = tmp_src;
}
/* Unset UPDATE_INDEX unless diffing workdir and index */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
(!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR ||
diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
!(diff->old_src == GIT_ITERATOR_TYPE_INDEX ||
diff->new_src == GIT_ITERATOR_TYPE_INDEX)))
diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
/* if ignore_submodules not explicitly set, check diff config */
if (diff->opts.ignore_submodules <= 0) {
git_config_entry *entry;
git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
if (entry && git_submodule_parse_ignore(
&diff->opts.ignore_submodules, entry->value) < 0)
giterr_clear();
git_config_entry_free(entry);
}
/* if either prefix is not set, figure out appropriate value */
if (!diff->opts.old_prefix || !diff->opts.new_prefix) {
const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
use_old = use_new = "";
else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
use_old = diff_mnemonic_prefix(diff->old_src, true);
use_new = diff_mnemonic_prefix(diff->new_src, false);
}
if (!diff->opts.old_prefix)
diff->opts.old_prefix = use_old;
if (!diff->opts.new_prefix)
diff->opts.new_prefix = use_new;
}
/* strdup prefix from pool so we're not dependent on external data */
diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix);
diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix);
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
const char *tmp_prefix = diff->opts.old_prefix;
diff->opts.old_prefix = diff->opts.new_prefix;
diff->opts.new_prefix = tmp_prefix;
}
git_config_free(cfg);
/* check strdup results for error */
return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0;
}
static void diff_list_free(git_diff *diff)
{
git_vector_free_deep(&diff->deltas);
git_pathspec__vfree(&diff->pathspec);
git_pool_clear(&diff->pool);
git__memzero(diff, sizeof(*diff));
git__free(diff);
}
void git_diff_free(git_diff *diff)
{
if (!diff)
return;
GIT_REFCOUNT_DEC(diff, diff_list_free);
}
void git_diff_addref(git_diff *diff)
{
GIT_REFCOUNT_INC(diff);
}
int git_diff__oid_for_file(
git_oid *out,
git_diff *diff,
const char *path,
uint16_t mode,
git_off_t size)
{
git_index_entry entry;
memset(&entry, 0, sizeof(entry));
entry.mode = mode;
entry.file_size = size;
entry.path = (char *)path;
return git_diff__oid_for_entry(out, diff, &entry, mode, NULL);
}
int git_diff__oid_for_entry(
git_oid *out,
git_diff *diff,
const git_index_entry *src,
uint16_t mode,
const git_oid *update_match)
{
int error = 0;
git_buf full_path = GIT_BUF_INIT;
git_index_entry entry = *src;
git_filter_list *fl = NULL;
memset(out, 0, sizeof(*out));
if (git_buf_joinpath(
&full_path, git_repository_workdir(diff->repo), entry.path) < 0)
return -1;
if (!mode) {
struct stat st;
diff->perf.stat_calls++;
if (p_stat(full_path.ptr, &st) < 0) {
error = git_path_set_error(errno, entry.path, "stat");
git_buf_free(&full_path);
return error;
}
git_index_entry__init_from_stat(
&entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
}
/* calculate OID for file if possible */
if (S_ISGITLINK(mode)) {
git_submodule *sm;
if (!git_submodule_lookup(&sm, diff->repo, entry.path)) {
const git_oid *sm_oid = git_submodule_wd_id(sm);
if (sm_oid)
git_oid_cpy(out, sm_oid);
git_submodule_free(sm);
} else {
/* if submodule lookup failed probably just in an intermediate
* state where some init hasn't happened, so ignore the error
*/
giterr_clear();
}
} else if (S_ISLNK(mode)) {
error = git_odb__hashlink(out, full_path.ptr);
diff->perf.oid_calculations++;
} else if (!git__is_sizet(entry.file_size)) {
giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
entry.path);
error = -1;
} else if (!(error = git_filter_list_load(
&fl, diff->repo, NULL, entry.path,
GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)))
{
int fd = git_futils_open_ro(full_path.ptr);
if (fd < 0)
error = fd;
else {
error = git_odb__hashfd_filtered(
out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
p_close(fd);
diff->perf.oid_calculations++;
}
git_filter_list_free(fl);
}
/* update index for entry if requested */
if (!error && update_match && git_oid_equal(out, update_match)) {
git_index *idx;
git_index_entry updated_entry;
memcpy(&updated_entry, &entry, sizeof(git_index_entry));
updated_entry.mode = mode;
git_oid_cpy(&updated_entry.id, out);
if (!(error = git_repository_index__weakptr(&idx, diff->repo))) {
error = git_index_add(idx, &updated_entry);
diff->index_updated = true;
}
}
git_buf_free(&full_path);
return error;
}
typedef struct {
git_repository *repo;
git_iterator *old_iter;
git_iterator *new_iter;
const git_index_entry *oitem;
const git_index_entry *nitem;
} diff_in_progress;
#define MODE_BITS_MASK 0000777
static int maybe_modified_submodule(
git_delta_t *status,
git_oid *found_oid,
git_diff *diff,
diff_in_progress *info)
{
int error = 0;
git_submodule *sub;
unsigned int sm_status = 0;
git_submodule_ignore_t ign = diff->opts.ignore_submodules;
*status = GIT_DELTA_UNMODIFIED;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
ign == GIT_SUBMODULE_IGNORE_ALL)
return 0;
if ((error = git_submodule_lookup(
&sub, diff->repo, info->nitem->path)) < 0) {
/* GIT_EEXISTS means dir with .git in it was found - ignore it */
if (error == GIT_EEXISTS) {
giterr_clear();
error = 0;
}
return error;
}
if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
/* ignore it */;
else if ((error = git_submodule__status(
&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
/* return error below */;
/* check IS_WD_UNMODIFIED because this case is only used
* when the new side of the diff is the working directory
*/
else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
*status = GIT_DELTA_MODIFIED;
/* now that we have a HEAD OID, check if HEAD moved */
else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
!git_oid_equal(&info->oitem->id, found_oid))
*status = GIT_DELTA_MODIFIED;
git_submodule_free(sub);
return error;
}
static int maybe_modified(
git_diff *diff,
diff_in_progress *info)
{
git_oid noid;
git_delta_t status = GIT_DELTA_MODIFIED;
const git_index_entry *oitem = info->oitem;
const git_index_entry *nitem = info->nitem;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
bool modified_uncertain = false;
const char *matched_pathspec;
int error = 0;
if (!diff_pathspec_match(&matched_pathspec, diff, oitem))
return 0;
memset(&noid, 0, sizeof(noid));
/* on platforms with no symlinks, preserve mode of existing symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = omode;
/* on platforms with no execmode, just preserve old mode */
if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
(nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
new_is_workdir)
nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
/* if one side is a conflict, mark the whole delta as conflicted */
if (git_index_entry_is_conflict(oitem) ||
git_index_entry_is_conflict(nitem)) {
status = GIT_DELTA_CONFLICTED;
/* support "assume unchanged" (poorly, b/c we still stat everything) */
} else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) {
status = GIT_DELTA_UNMODIFIED;
/* support "skip worktree" index bit */
} else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) {
status = GIT_DELTA_UNMODIFIED;
/* if basic type of file changed, then split into delete and add */
} else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) {
status = GIT_DELTA_TYPECHANGE;
}
else if (nmode == GIT_FILEMODE_UNREADABLE) {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem);
return error;
}
else {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
return error;
}
/* if oids and modes match (and are valid), then file is unmodified */
} else if (git_oid_equal(&oitem->id, &nitem->id) &&
omode == nmode &&
!git_oid_iszero(&oitem->id)) {
status = GIT_DELTA_UNMODIFIED;
/* if we have an unknown OID and a workdir iterator, then check some
* circumstances that can accelerate things or need special handling
*/
} else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
git_index *index = git_iterator_index(info->new_iter);
status = GIT_DELTA_UNMODIFIED;
if (S_ISGITLINK(nmode)) {
if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
return error;
}
/* if the stat data looks different, then mark modified - this just
* means that the OID will be recalculated below to confirm change
*/
else if (omode != nmode || oitem->file_size != nitem->file_size) {
status = GIT_DELTA_MODIFIED;
modified_uncertain =
(oitem->file_size <= 0 && nitem->file_size > 0);
}
else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) ||
(use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) ||
oitem->ino != nitem->ino ||
oitem->uid != nitem->uid ||
oitem->gid != nitem->gid ||
git_index_entry_newer_than_index(nitem, index))
{
status = GIT_DELTA_MODIFIED;
modified_uncertain = true;
}
/* if mode is GITLINK and submodules are ignored, then skip */
} else if (S_ISGITLINK(nmode) &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) {
status = GIT_DELTA_UNMODIFIED;
}
/* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now
*/
if (modified_uncertain && git_oid_iszero(&nitem->id)) {
const git_oid *update_check =
DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ?
&oitem->id : NULL;
if ((error = git_diff__oid_for_entry(
&noid, diff, nitem, nmode, update_check)) < 0)
return error;
/* if oid matches, then mark unmodified (except submodules, where
* the filesystem content may be modified even if the oid still
* matches between the index and the workdir HEAD)
*/
if (omode == nmode && !S_ISGITLINK(omode) &&
git_oid_equal(&oitem->id, &noid))
status = GIT_DELTA_UNMODIFIED;
}
/* If we want case changes, then break this into a delete of the old
* and an add of the new so that consumers can act accordingly (eg,
* checkout will update the case on disk.)
*/
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) &&
strcmp(oitem->path, nitem->path) != 0) {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
return error;
}
return diff_delta__from_two(
diff, status, oitem, omode, nitem, nmode,
git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
}
static bool entry_is_prefixed(
git_diff *diff,
const git_index_entry *item,
const git_index_entry *prefix_item)
{
size_t pathlen;
if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0)
return false;
pathlen = strlen(prefix_item->path);
return (prefix_item->path[pathlen - 1] == '/' ||
item->path[pathlen] == '\0' ||
item->path[pathlen] == '/');
}
static int iterator_current(
const git_index_entry **entry,
git_iterator *iterator)
{
int error;
if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance(
const git_index_entry **entry,
git_iterator *iterator)
{
const git_index_entry *prev_entry = *entry;
int cmp, error;
/* if we're looking for conflicts, we only want to report
* one conflict for each file, instead of all three sides.
* so if this entry is a conflict for this file, and the
* previous one was a conflict for the same file, skip it.
*/
while ((error = git_iterator_advance(entry, iterator)) == 0) {
if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) ||
!git_index_entry_is_conflict(prev_entry) ||
!git_index_entry_is_conflict(*entry))
break;
cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ?
strcasecmp(prev_entry->path, (*entry)->path) :
strcmp(prev_entry->path, (*entry)->path);
if (cmp)
break;
}
if (error == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance_into(
const git_index_entry **entry,
git_iterator *iterator)
{
int error;
if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance_over(
const git_index_entry **entry,
git_iterator_status_t *status,
git_iterator *iterator)
{
int error = git_iterator_advance_over(entry, status, iterator);
if (error == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int handle_unmatched_new_item(
git_diff *diff, diff_in_progress *info)
{
int error = 0;
const git_index_entry *nitem = info->nitem;
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
bool contains_oitem;
/* check if this is a prefix of the other side */
contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
/* update delta_type if this item is conflicted */
if (git_index_entry_is_conflict(nitem))
delta_type = GIT_DELTA_CONFLICTED;
/* update delta_type if this item is ignored */
else if (git_iterator_current_is_ignored(info->new_iter))
delta_type = GIT_DELTA_IGNORED;
if (nitem->mode == GIT_FILEMODE_TREE) {
bool recurse_into_dir = contains_oitem;
/* check if user requests recursion into this type of dir */
recurse_into_dir = contains_oitem ||
(delta_type == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
(delta_type == GIT_DELTA_IGNORED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
/* do not advance into directories that contain a .git file */
if (recurse_into_dir && !contains_oitem) {
git_buf *full = NULL;
if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
return -1;
if (full && git_path_contains(full, DOT_GIT)) {
/* TODO: warning if not a valid git repository */
recurse_into_dir = false;
}
}
/* still have to look into untracked directories to match core git -
* with no untracked files, directory is treated as ignored
*/
if (!recurse_into_dir &&
delta_type == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
{
git_diff_delta *last;
git_iterator_status_t untracked_state;
/* attempt to insert record for this directory */
if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
return error;
/* if delta wasn't created (because of rules), just skip ahead */
last = diff_delta__last_for_item(diff, nitem);
if (!last)
return iterator_advance(&info->nitem, info->new_iter);
/* iterate into dir looking for an actual untracked file */
if ((error = iterator_advance_over(
&info->nitem, &untracked_state, info->new_iter)) < 0)
return error;
/* if we found nothing that matched our pathlist filter, exclude */
if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) {
git_vector_pop(&diff->deltas);
git__free(last);
}
/* if we found nothing or just ignored items, update the record */
if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
last->status = GIT_DELTA_IGNORED;
/* remove the record if we don't want ignored records */
if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
git_vector_pop(&diff->deltas);
git__free(last);
}
}
return 0;
}
/* try to advance into directory if necessary */
if (recurse_into_dir) {
error = iterator_advance_into(&info->nitem, info->new_iter);
/* if directory is empty, can't advance into it, so skip it */
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = iterator_advance(&info->nitem, info->new_iter);
}
return error;
}
}
else if (delta_type == GIT_DELTA_IGNORED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
git_iterator_current_tree_is_ignored(info->new_iter))
/* item contained in ignored directory, so skip over it */
return iterator_advance(&info->nitem, info->new_iter);
else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) {
if (delta_type != GIT_DELTA_CONFLICTED)
delta_type = GIT_DELTA_ADDED;
}
else if (nitem->mode == GIT_FILEMODE_COMMIT) {
/* ignore things that are not actual submodules */
if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
giterr_clear();
delta_type = GIT_DELTA_IGNORED;
/* if this contains a tracked item, treat as normal TREE */
if (contains_oitem) {
error = iterator_advance_into(&info->nitem, info->new_iter);
if (error != GIT_ENOTFOUND)
return error;
giterr_clear();
return iterator_advance(&info->nitem, info->new_iter);
}
}
}
else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
delta_type = GIT_DELTA_UNTRACKED;
else
delta_type = GIT_DELTA_UNREADABLE;
}
/* Actually create the record for this item if necessary */
if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
return error;
/* If user requested TYPECHANGE records, then check for that instead of
* just generating an ADDED/UNTRACKED record
*/
if (delta_type != GIT_DELTA_IGNORED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
contains_oitem)
{
/* this entry was prefixed with a tree - make TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
if (last) {
last->status = GIT_DELTA_TYPECHANGE;
last->old_file.mode = GIT_FILEMODE_TREE;
}
}
return iterator_advance(&info->nitem, info->new_iter);
}
static int handle_unmatched_old_item(
git_diff *diff, diff_in_progress *info)
{
git_delta_t delta_type = GIT_DELTA_DELETED;
int error;
/* update delta_type if this item is conflicted */
if (git_index_entry_is_conflict(info->oitem))
delta_type = GIT_DELTA_CONFLICTED;
if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
return error;
/* if we are generating TYPECHANGE records then check for that
* instead of just generating a DELETE record
*/
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
entry_is_prefixed(diff, info->nitem, info->oitem))
{
/* this entry has become a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
if (last) {
last->status = GIT_DELTA_TYPECHANGE;
last->new_file.mode = GIT_FILEMODE_TREE;
}
/* If new_iter is a workdir iterator, then this situation
* will certainly be followed by a series of untracked items.
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
*/
if (S_ISDIR(info->nitem->mode) &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
return iterator_advance(&info->nitem, info->new_iter);
}
return iterator_advance(&info->oitem, info->old_iter);
}
static int handle_matched_item(
git_diff *diff, diff_in_progress *info)
{
int error = 0;
if ((error = maybe_modified(diff, info)) < 0)
return error;
if (!(error = iterator_advance(&info->oitem, info->old_iter)))
error = iterator_advance(&info->nitem, info->new_iter);
return error;
}
int git_diff__from_iterators(
git_diff **diff_ptr,
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter,
const git_diff_options *opts)
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
int error = 0;
diff_in_progress info;
git_diff *diff;
*diff_ptr = NULL;
diff = diff_list_alloc(repo, old_iter, new_iter);
GITERR_CHECK_ALLOC(diff);
info.repo = repo;
info.old_iter = old_iter;
info.new_iter = new_iter;
/* make iterators have matching icase behavior */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
git_iterator_set_ignore_case(old_iter, true);
git_iterator_set_ignore_case(new_iter, true);
}
/* finish initialization */
if ((error = diff_list_apply_options(diff, opts)) < 0)
goto cleanup;
if ((error = iterator_current(&info.oitem, old_iter)) < 0 ||
(error = iterator_current(&info.nitem, new_iter)) < 0)
goto cleanup;
/* run iterators building diffs */
while (!error && (info.oitem || info.nitem)) {
int cmp;
/* report progress */
if (opts && opts->progress_cb) {
if ((error = opts->progress_cb(diff,
info.oitem ? info.oitem->path : NULL,
info.nitem ? info.nitem->path : NULL,
opts->payload)))
break;
}
cmp = info.oitem ?
(info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1;
/* create DELETED records for old items not matched in new */
if (cmp < 0)
error = handle_unmatched_old_item(diff, &info);
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
else if (cmp > 0)
error = handle_unmatched_new_item(diff, &info);
/* otherwise item paths match, so create MODIFIED record
* (or ADDED and DELETED pair if type changed)
*/
else
error = handle_matched_item(diff, &info);
}
diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls;
const char *str = delta->old_file.path;
cleanup:
if (!error)
*diff_ptr = diff;
else
git_diff_free(diff);
if (!str ||
delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_COPIED)
str = delta->new_file.path;
return error;
return str;
}
#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
git_iterator *a = NULL, *b = NULL; \
char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \
git_pathspec_prefix(&opts->pathspec) : NULL; \
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
b_opts = GIT_ITERATOR_OPTIONS_INIT; \
a_opts.flags = FLAGS_FIRST; \
a_opts.start = pfx; \
a_opts.end = pfx; \
b_opts.flags = FLAGS_SECOND; \
b_opts.start = pfx; \
b_opts.end = pfx; \
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \
a_opts.pathlist.strings = opts->pathspec.strings; \
a_opts.pathlist.count = opts->pathspec.count; \
b_opts.pathlist.strings = opts->pathspec.strings; \
b_opts.pathlist.count = opts->pathspec.count; \
} \
if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
error = git_diff__from_iterators(diff, repo, a, b, opts); \
git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
} while (0)
int git_diff_tree_to_tree(
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
git_tree *new_tree,
const git_diff_options *opts)
const char *git_diff_delta__path(const git_diff_delta *delta)
{
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
int error = 0;
assert(diff && repo);
/* for tree to tree diff, be case sensitive even if the index is
* currently case insensitive, unless the user explicitly asked
* for case insensitivity
*/
if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
iflag = GIT_ITERATOR_IGNORE_CASE;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_tree(&b, new_tree, &b_opts), iflag
);
return error;
return diff_delta__path(delta);
}
static int diff_load_index(git_index **index, git_repository *repo)
int git_diff_delta__cmp(const void *a, const void *b)
{
int error = git_repository_index__weakptr(index, repo);
/* reload the repository index when user did not pass one in */
if (!error && git_index_read(*index, false) < 0)
giterr_clear();
return error;
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_tree_to_index(
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
git_index *index,
const git_diff_options *opts)
int git_diff_delta__casecmp(const void *a, const void *b)
{
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
GIT_ITERATOR_INCLUDE_CONFLICTS;
bool index_ignore_case = false;
int error = 0;
assert(diff && repo);
if (!index && (error = diff_load_index(&index, repo)) < 0)
return error;
index_ignore_case = index->ignore_case;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_index(&b, repo, index, &b_opts), iflag
);
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && index_ignore_case)
diff_set_ignore_case(*diff, true);
return error;
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_index_to_workdir(
git_diff **diff,
git_repository *repo,
git_index *index,
const git_diff_options *opts)
int git_diff__entry_cmp(const void *a, const void *b)
{
int error = 0;
assert(diff && repo);
if (!index && (error = diff_load_index(&index, repo)) < 0)
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, index, &a_opts),
GIT_ITERATOR_INCLUDE_CONFLICTS,
git_iterator_for_workdir(&b, repo, index, NULL, &b_opts),
GIT_ITERATOR_DONT_AUTOEXPAND
);
if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX) && (*diff)->index_updated)
error = git_index_write(index);
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return error;
return strcmp(entry_a->path, entry_b->path);
}
int git_diff_tree_to_workdir(
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
const git_diff_options *opts)
int git_diff__entry_icmp(const void *a, const void *b)
{
int error = 0;
git_index *index;
assert(diff && repo);
if ((error = git_repository_index__weakptr(&index, repo)))
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), 0,
git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND
);
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return error;
return strcasecmp(entry_a->path, entry_b->path);
}
int git_diff_tree_to_workdir_with_index(
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
const git_diff_options *opts)
void git_diff_free(git_diff *diff)
{
int error = 0;
git_diff *d1 = NULL, *d2 = NULL;
git_index *index = NULL;
assert(diff && repo);
if ((error = diff_load_index(&index, repo)) < 0)
return error;
if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) &&
!(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
error = git_diff_merge(d1, d2);
git_diff_free(d2);
if (error) {
git_diff_free(d1);
d1 = NULL;
}
if (!diff)
return;
*diff = d1;
return error;
GIT_REFCOUNT_DEC(diff, diff->free_fn);
}
int git_diff_index_to_index(
git_diff **diff,
git_repository *repo,
git_index *old_index,
git_index *new_index,
const git_diff_options *opts)
void git_diff_addref(git_diff *diff)
{
int error = 0;
assert(diff && old_index && new_index);
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE,
git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE
);
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && (old_index->ignore_case || new_index->ignore_case))
diff_set_ignore_case(*diff, true);
return error;
GIT_REFCOUNT_INC(diff);
}
size_t git_diff_num_deltas(const git_diff *diff)
......@@ -1516,137 +120,6 @@ int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
return 0;
}
int git_diff__paired_foreach(
git_diff *head2idx,
git_diff *idx2wd,
int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
void *payload)
{
int cmp, error = 0;
git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max;
int (*strcomp)(const char *, const char *) = git__strcmp;
bool h2i_icase, i2w_icase, icase_mismatch;
i_max = head2idx ? head2idx->deltas.length : 0;
j_max = idx2wd ? idx2wd->deltas.length : 0;
if (!i_max && !j_max)
return 0;
/* At some point, tree-to-index diffs will probably never ignore case,
* even if that isn't true now. Index-to-workdir diffs may or may not
* ignore case, but the index filename for the idx2wd diff should
* still be using the canonical case-preserving name.
*
* Therefore the main thing we need to do here is make sure the diffs
* are traversed in a compatible order. To do this, we temporarily
* resort a mismatched diff to get the order correct.
*
* In order to traverse renames in the index->workdir, we need to
* ensure that we compare the index name on both sides, so we
* always sort by the old name in the i2w list.
*/
h2i_icase = head2idx != NULL &&
(head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
i2w_icase = idx2wd != NULL &&
(idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
icase_mismatch =
(head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
git_vector_sort(&head2idx->deltas);
}
if (i2w_icase && !icase_mismatch) {
strcomp = git__strcasecmp;
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
git_vector_sort(&idx2wd->deltas);
} else if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
git_vector_sort(&idx2wd->deltas);
}
for (i = 0, j = 0; i < i_max || j < j_max; ) {
h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
cmp = !i2w ? -1 : !h2i ? 1 :
strcomp(h2i->new_file.path, i2w->old_file.path);
if (cmp < 0) {
i++; i2w = NULL;
} else if (cmp > 0) {
j++; h2i = NULL;
} else {
i++; j++;
}
if ((error = cb(h2i, i2w, payload)) != 0) {
giterr_set_after_callback(error);
break;
}
}
/* restore case-insensitive delta sort */
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
git_vector_sort(&head2idx->deltas);
}
/* restore idx2wd sort by new path */
if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas,
i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas);
}
return error;
}
int git_diff__commit(
git_diff **diff,
git_repository *repo,
const git_commit *commit,
const git_diff_options *opts)
{
git_commit *parent = NULL;
git_diff *commit_diff = NULL;
git_tree *old_tree = NULL, *new_tree = NULL;
size_t parents;
int error = 0;
if ((parents = git_commit_parentcount(commit)) > 1) {
char commit_oidstr[GIT_OID_HEXSZ + 1];
error = -1;
giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
goto on_error;
}
if (parents > 0)
if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
(error = git_commit_tree(&old_tree, parent)) < 0)
goto on_error;
if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
(error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
goto on_error;
*diff = commit_diff;
on_error:
git_tree_free(new_tree);
git_tree_free(old_tree);
git_commit_free(parent);
return error;
}
int git_diff_format_email__append_header_tobuf(
git_buf *out,
const git_oid *id,
......@@ -1664,7 +137,8 @@ int git_diff_format_email__append_header_tobuf(
git_oid_fmt(idstr, id);
idstr[GIT_OID_HEXSZ] = '\0';
if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), &author->when)) < 0)
if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
&author->when)) < 0)
return error;
error = git_buf_printf(out,
......@@ -1683,7 +157,8 @@ int git_diff_format_email__append_header_tobuf(
if (total_patches == 1) {
error = git_buf_puts(out, "[PATCH] ");
} else {
error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", patch_no, total_patches);
error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
patch_no, total_patches);
}
if (error < 0)
......@@ -1741,16 +216,24 @@ int git_diff_format_email(
assert(out && diff && opts);
assert(opts->summary && opts->id && opts->author);
GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options");
GITERR_CHECK_VERSION(opts,
GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
"git_format_email_options");
if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) {
ignore_marker = (opts->flags &
GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
if (!ignore_marker) {
if (opts->patch_no > opts->total_patches) {
giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches);
giterr_set(GITERR_INVALID,
"patch %"PRIuZ" out of range. max %"PRIuZ,
opts->patch_no, opts->total_patches);
return -1;
}
if (opts->patch_no == 0) {
giterr_set(GITERR_INVALID, "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
giterr_set(GITERR_INVALID,
"invalid patch no %"PRIuZ". should be >0", opts->patch_no);
return -1;
}
}
......@@ -1809,7 +292,8 @@ int git_diff_commit_as_email(
const git_diff_options *diff_opts)
{
git_diff *diff = NULL;
git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
git_diff_format_email_options opts =
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
int error;
assert (out && repo && commit);
......@@ -1854,3 +338,4 @@ int git_diff_format_email_init_options(
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
return 0;
}
......@@ -22,67 +22,30 @@
#define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/"
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
enum {
GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
};
#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
#define GIT_DIFF__VERBOSE (1 << 30)
typedef enum {
GIT_DIFF_TYPE_UNKNOWN = 0,
GIT_DIFF_TYPE_GENERATED = 1,
GIT_DIFF_TYPE_PARSED = 2,
} git_diff_origin_t;
struct git_diff {
git_refcount rc;
git_repository *repo;
git_diff_origin_t type;
git_diff_options opts;
git_vector pathspec;
git_vector deltas; /* vector of git_diff_delta */
git_pool pool;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
uint32_t diffcaps;
git_diff_perfdata perf;
bool index_updated;
int (*strcomp)(const char *, const char *);
int (*strncomp)(const char *, const char *, size_t);
int (*pfxcomp)(const char *str, const char *pfx);
int (*entrycomp)(const void *a, const void *b);
};
extern void git_diff__cleanup_modes(
uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
extern void git_diff_addref(git_diff *diff);
extern int git_diff_delta__cmp(const void *a, const void *b);
extern int git_diff_delta__casecmp(const void *a, const void *b);
extern const char *git_diff_delta__path(const git_diff_delta *delta);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
void (*free_fn)(git_diff *diff);
};
extern int git_diff_delta__format_file_header(
git_buf *out,
......@@ -91,84 +54,11 @@ extern int git_diff_delta__format_file_header(
const char *newpfx,
int oid_strlen);
extern int git_diff__oid_for_file(
git_oid *out, git_diff *, const char *, uint16_t, git_off_t);
extern int git_diff__oid_for_entry(
git_oid *out, git_diff *, const git_index_entry *, uint16_t, const git_oid *update);
extern int git_diff__from_iterators(
git_diff **diff_ptr,
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter,
const git_diff_options *opts);
extern int git_diff__paired_foreach(
git_diff *idx2head,
git_diff *wd2idx,
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
void *payload);
extern int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p);
extern int git_diff_find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload);
extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
/* Merge two `git_diff`s according to the callback given by `cb`. */
typedef git_diff_delta *(*git_diff__merge_cb)(
const git_diff_delta *left,
const git_diff_delta *right,
git_pool *pool);
extern int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
extern git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool);
/* Duplicate a `git_diff_delta` out of the `git_pool` */
extern git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool);
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
* not possible, then it will return the git_odb_object that had to be
* loaded and the caller can use it or dispose of it as needed.
*/
GIT_INLINE(int) git_diff_file__resolve_zero_size(
git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
{
int error;
git_odb *odb;
size_t len;
git_otype type;
if ((error = git_repository_odb(&odb, repo)) < 0)
return error;
error = git_odb__read_header_or_object(
odb_obj, &len, &type, odb, &file->id);
git_odb_free(odb);
if (!error)
file->size = (git_off_t)len;
extern int git_diff_delta__cmp(const void *a, const void *b);
extern int git_diff_delta__casecmp(const void *a, const void *b);
return error;
}
extern int git_diff__entry_cmp(const void *a, const void *b);
extern int git_diff__entry_icmp(const void *a, const void *b);
#endif
......@@ -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"
......
......@@ -8,6 +8,7 @@
#include "git2/blob.h"
#include "git2/submodule.h"
#include "diff.h"
#include "diff_generate.h"
#include "diff_file.h"
#include "odb.h"
#include "fileops.h"
......@@ -149,12 +150,14 @@ int git_diff_file_content__init_from_src(
if (src->blob) {
fc->file->size = git_blob_rawsize(src->blob);
git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = (size_t)fc->file->size;
fc->map.data = (char *)git_blob_rawcontent(src->blob);
} else {
fc->file->size = src->buflen;
git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB);
fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = src->buflen;
fc->map.data = (char *)src->buf;
......
/*
* 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.
*/
#include "common.h"
#include "diff.h"
#include "diff_generate.h"
#include "fileops.h"
#include "config.h"
#include "attr_file.h"
#include "filter.h"
#include "pathspec.h"
#include "index.h"
#include "odb.h"
#include "submodule.h"
#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
(((DIFF)->base.opts.flags & (FLAG)) != 0)
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
(((DIFF)->base.opts.flags & (FLAG)) == 0)
#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \
(VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \
((DIFF)->base.opts.flags & ~(VAL))
typedef struct {
struct git_diff base;
git_vector pathspec;
uint32_t diffcaps;
bool index_updated;
} git_diff_generated;
static git_diff_delta *diff_delta__alloc(
git_diff_generated *diff,
git_delta_t status,
const char *path)
{
git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
if (!delta)
return NULL;
delta->old_file.path = git_pool_strdup(&diff->base.pool, path);
if (delta->old_file.path == NULL) {
git__free(delta);
return NULL;
}
delta->new_file.path = delta->old_file.path;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
switch (status) {
case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
default: break; /* leave other status values alone */
}
}
delta->status = status;
return delta;
}
static int diff_insert_delta(
git_diff_generated *diff,
git_diff_delta *delta,
const char *matched_pathspec)
{
int error = 0;
if (diff->base.opts.notify_cb) {
error = diff->base.opts.notify_cb(
&diff->base, delta, matched_pathspec, diff->base.opts.payload);
if (error) {
git__free(delta);
if (error > 0) /* positive value means to skip this delta */
return 0;
else /* negative value means to cancel diff */
return giterr_set_after_callback_function(error, "git_diff");
}
}
if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0)
git__free(delta);
return error;
}
static bool diff_pathspec_match(
const char **matched_pathspec,
git_diff_generated *diff,
const git_index_entry *entry)
{
bool disable_pathspec_match =
DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH);
/* If we're disabling fnmatch, then the iterator has already applied
* the filters to the files for us and we don't have to do anything.
* However, this only applies to *files* - the iterator will include
* directories that we need to recurse into when not autoexpanding,
* so we still need to apply the pathspec match to directories.
*/
if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) &&
disable_pathspec_match) {
*matched_pathspec = entry->path;
return true;
}
return git_pathspec__match(
&diff->pathspec, entry->path, disable_pathspec_match,
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
matched_pathspec, NULL);
}
static int diff_delta__from_one(
git_diff_generated *diff,
git_delta_t status,
const git_index_entry *oitem,
const git_index_entry *nitem)
{
const git_index_entry *entry = nitem;
bool has_old = false;
git_diff_delta *delta;
const char *matched_pathspec;
assert((oitem != NULL) ^ (nitem != NULL));
if (oitem) {
entry = oitem;
has_old = true;
}
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
has_old = !has_old;
if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
return 0;
if (status == GIT_DELTA_IGNORED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
return 0;
if (status == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
return 0;
if (status == GIT_DELTA_UNREADABLE &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
return 0;
if (!diff_pathspec_match(&matched_pathspec, diff, entry))
return 0;
delta = diff_delta__alloc(diff, status, entry->path);
GITERR_CHECK_ALLOC(delta);
/* This fn is just for single-sided diffs */
assert(status != GIT_DELTA_MODIFIED);
delta->nfiles = 1;
if (has_old) {
delta->old_file.mode = entry->mode;
delta->old_file.size = entry->file_size;
delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
git_oid_cpy(&delta->old_file.id, &entry->id);
delta->old_file.id_abbrev = GIT_OID_HEXSZ;
} else /* ADDED, IGNORED, UNTRACKED */ {
delta->new_file.mode = entry->mode;
delta->new_file.size = entry->file_size;
delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
git_oid_cpy(&delta->new_file.id, &entry->id);
delta->new_file.id_abbrev = GIT_OID_HEXSZ;
}
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
if (has_old || !git_oid_iszero(&delta->new_file.id))
delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
return diff_insert_delta(diff, delta, matched_pathspec);
}
static int diff_delta__from_two(
git_diff_generated *diff,
git_delta_t status,
const git_index_entry *old_entry,
uint32_t old_mode,
const git_index_entry *new_entry,
uint32_t new_mode,
const git_oid *new_id,
const char *matched_pathspec)
{
const git_oid *old_id = &old_entry->id;
git_diff_delta *delta;
const char *canonical_path = old_entry->path;
if (status == GIT_DELTA_UNMODIFIED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
return 0;
if (!new_id)
new_id = &new_entry->id;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
uint32_t temp_mode = old_mode;
const git_index_entry *temp_entry = old_entry;
const git_oid *temp_id = old_id;
old_entry = new_entry;
new_entry = temp_entry;
old_mode = new_mode;
new_mode = temp_mode;
old_id = new_id;
new_id = temp_id;
}
delta = diff_delta__alloc(diff, status, canonical_path);
GITERR_CHECK_ALLOC(delta);
delta->nfiles = 2;
if (!git_index_entry_is_conflict(old_entry)) {
delta->old_file.size = old_entry->file_size;
delta->old_file.mode = old_mode;
git_oid_cpy(&delta->old_file.id, old_id);
delta->old_file.id_abbrev = GIT_OID_HEXSZ;
delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
GIT_DIFF_FLAG_EXISTS;
}
if (!git_index_entry_is_conflict(new_entry)) {
git_oid_cpy(&delta->new_file.id, new_id);
delta->new_file.id_abbrev = GIT_OID_HEXSZ;
delta->new_file.size = new_entry->file_size;
delta->new_file.mode = new_mode;
delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
if (!git_oid_iszero(&new_entry->id))
delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
}
return diff_insert_delta(diff, delta, matched_pathspec);
}
static git_diff_delta *diff_delta__last_for_item(
git_diff_generated *diff,
const git_index_entry *item)
{
git_diff_delta *delta = git_vector_last(&diff->base.deltas);
if (!delta)
return NULL;
switch (delta->status) {
case GIT_DELTA_UNMODIFIED:
case GIT_DELTA_DELETED:
if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_ADDED:
if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_UNREADABLE:
case GIT_DELTA_UNTRACKED:
if (diff->base.strcomp(delta->new_file.path, item->path) == 0 &&
git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
case GIT_DELTA_MODIFIED:
if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
git_oid__cmp(&delta->new_file.id, &item->id) == 0)
return delta;
break;
default:
break;
}
return NULL;
}
static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
{
size_t len = strlen(prefix);
/* append '/' at end if needed */
if (len > 0 && prefix[len - 1] != '/')
return git_pool_strcat(pool, prefix, "/");
else
return git_pool_strndup(pool, prefix, len + 1);
}
GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta)
{
return delta->old_file.path ?
delta->old_file.path : delta->new_file.path;
}
int git_diff_delta__i2w_cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__i2w_casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db));
return val ? val : ((int)da->status - (int)db->status);
}
bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta)
{
uint32_t flags = opts ? opts->flags : 0;
if (delta->status == GIT_DELTA_UNMODIFIED &&
(flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true;
if (delta->status == GIT_DELTA_IGNORED &&
(flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true;
if (delta->status == GIT_DELTA_UNTRACKED &&
(flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
if (delta->status == GIT_DELTA_UNREADABLE &&
(flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0)
return true;
return false;
}
static const char *diff_mnemonic_prefix(
git_iterator_type_t type, bool left_side)
{
const char *pfx = "";
switch (type) {
case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break;
case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break;
case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break;
case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break;
case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break;
default: break;
}
/* note: without a deeper look at pathspecs, there is no easy way
* to get the (o)bject / (w)ork tree mnemonics working...
*/
return pfx;
}
void git_diff__set_ignore_case(git_diff *diff, bool ignore_case)
{
if (!ignore_case) {
diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE;
diff->strcomp = git__strcmp;
diff->strncomp = git__strncmp;
diff->pfxcomp = git__prefixcmp;
diff->entrycomp = git_diff__entry_cmp;
git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp);
} else {
diff->opts.flags |= GIT_DIFF_IGNORE_CASE;
diff->strcomp = git__strcasecmp;
diff->strncomp = git__strncasecmp;
diff->pfxcomp = git__prefixcmp_icase;
diff->entrycomp = git_diff__entry_icmp;
git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp);
}
git_vector_sort(&diff->deltas);
}
static void diff_generated_free(git_diff *d)
{
git_diff_generated *diff = (git_diff_generated *)d;
git_vector_free_deep(&diff->base.deltas);
git_pathspec__vfree(&diff->pathspec);
git_pool_clear(&diff->base.pool);
git__memzero(diff, sizeof(*diff));
git__free(diff);
}
static git_diff_generated *diff_generated_alloc(
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter)
{
git_diff_generated *diff;
git_diff_options dflt = GIT_DIFF_OPTIONS_INIT;
assert(repo && old_iter && new_iter);
if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL)
return NULL;
GIT_REFCOUNT_INC(diff);
diff->base.type = GIT_DIFF_TYPE_GENERATED;
diff->base.repo = repo;
diff->base.old_src = old_iter->type;
diff->base.new_src = new_iter->type;
diff->base.free_fn = diff_generated_free;
memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options));
git_pool_init(&diff->base.pool, 1);
if (git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
git_diff_free(&diff->base);
return NULL;
}
/* Use case-insensitive compare if either iterator has
* the ignore_case bit set */
git_diff__set_ignore_case(
&diff->base,
git_iterator_ignore_case(old_iter) ||
git_iterator_ignore_case(new_iter));
return diff;
}
static int diff_generated_apply_options(
git_diff_generated *diff,
const git_diff_options *opts)
{
git_config *cfg = NULL;
git_repository *repo = diff->base.repo;
git_pool *pool = &diff->base.pool;
int val;
if (opts) {
/* copy user options (except case sensitivity info from iterators) */
bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE);
memcpy(&diff->base.opts, opts, sizeof(diff->base.opts));
DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase);
/* initialize pathspec from options */
if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0)
return -1;
}
/* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES))
diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE;
/* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT))
diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
/* load config values that affect diff behavior */
if ((val = git_repository_config_snapshot(&cfg, repo)) < 0)
return val;
if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val)
diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS;
if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val)
diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT;
if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 &&
!git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val)
diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS;
if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val)
diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME;
/* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */
/* If not given explicit `opts`, check `diff.xyz` configs */
if (!opts) {
int context = git_config__get_int_force(cfg, "diff.context", 3);
diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3;
/* add other defaults here */
}
/* Reverse src info if diff is reversed */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
git_iterator_type_t tmp_src = diff->base.old_src;
diff->base.old_src = diff->base.new_src;
diff->base.new_src = tmp_src;
}
/* Unset UPDATE_INDEX unless diffing workdir and index */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) &&
(!(diff->base.old_src == GIT_ITERATOR_TYPE_WORKDIR ||
diff->base.new_src == GIT_ITERATOR_TYPE_WORKDIR) ||
!(diff->base.old_src == GIT_ITERATOR_TYPE_INDEX ||
diff->base.new_src == GIT_ITERATOR_TYPE_INDEX)))
diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX;
/* if ignore_submodules not explicitly set, check diff config */
if (diff->base.opts.ignore_submodules <= 0) {
git_config_entry *entry;
git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true);
if (entry && git_submodule_parse_ignore(
&diff->base.opts.ignore_submodules, entry->value) < 0)
giterr_clear();
git_config_entry_free(entry);
}
/* if either prefix is not set, figure out appropriate value */
if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) {
const char *use_old = DIFF_OLD_PREFIX_DEFAULT;
const char *use_new = DIFF_NEW_PREFIX_DEFAULT;
if (git_config__get_bool_force(cfg, "diff.noprefix", 0))
use_old = use_new = "";
else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) {
use_old = diff_mnemonic_prefix(diff->base.old_src, true);
use_new = diff_mnemonic_prefix(diff->base.new_src, false);
}
if (!diff->base.opts.old_prefix)
diff->base.opts.old_prefix = use_old;
if (!diff->base.opts.new_prefix)
diff->base.opts.new_prefix = use_new;
}
/* strdup prefix from pool so we're not dependent on external data */
diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix);
diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix);
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
const char *tmp_prefix = diff->base.opts.old_prefix;
diff->base.opts.old_prefix = diff->base.opts.new_prefix;
diff->base.opts.new_prefix = tmp_prefix;
}
git_config_free(cfg);
/* check strdup results for error */
return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0;
}
int git_diff__oid_for_file(
git_oid *out,
git_diff *diff,
const char *path,
uint16_t mode,
git_off_t size)
{
git_index_entry entry;
memset(&entry, 0, sizeof(entry));
entry.mode = mode;
entry.file_size = size;
entry.path = (char *)path;
return git_diff__oid_for_entry(out, diff, &entry, mode, NULL);
}
int git_diff__oid_for_entry(
git_oid *out,
git_diff *d,
const git_index_entry *src,
uint16_t mode,
const git_oid *update_match)
{
git_diff_generated *diff;
git_buf full_path = GIT_BUF_INIT;
git_index_entry entry = *src;
git_filter_list *fl = NULL;
int error = 0;
assert(d->type == GIT_DIFF_TYPE_GENERATED);
diff = (git_diff_generated *)d;
memset(out, 0, sizeof(*out));
if (git_buf_joinpath(&full_path,
git_repository_workdir(diff->base.repo), entry.path) < 0)
return -1;
if (!mode) {
struct stat st;
diff->base.perf.stat_calls++;
if (p_stat(full_path.ptr, &st) < 0) {
error = git_path_set_error(errno, entry.path, "stat");
git_buf_free(&full_path);
return error;
}
git_index_entry__init_from_stat(&entry,
&st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0);
}
/* calculate OID for file if possible */
if (S_ISGITLINK(mode)) {
git_submodule *sm;
if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) {
const git_oid *sm_oid = git_submodule_wd_id(sm);
if (sm_oid)
git_oid_cpy(out, sm_oid);
git_submodule_free(sm);
} else {
/* if submodule lookup failed probably just in an intermediate
* state where some init hasn't happened, so ignore the error
*/
giterr_clear();
}
} else if (S_ISLNK(mode)) {
error = git_odb__hashlink(out, full_path.ptr);
diff->base.perf.oid_calculations++;
} else if (!git__is_sizet(entry.file_size)) {
giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'",
entry.path);
error = -1;
} else if (!(error = git_filter_list_load(&fl,
diff->base.repo, NULL, entry.path,
GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)))
{
int fd = git_futils_open_ro(full_path.ptr);
if (fd < 0)
error = fd;
else {
error = git_odb__hashfd_filtered(
out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl);
p_close(fd);
diff->base.perf.oid_calculations++;
}
git_filter_list_free(fl);
}
/* update index for entry if requested */
if (!error && update_match && git_oid_equal(out, update_match)) {
git_index *idx;
git_index_entry updated_entry;
memcpy(&updated_entry, &entry, sizeof(git_index_entry));
updated_entry.mode = mode;
git_oid_cpy(&updated_entry.id, out);
if (!(error = git_repository_index__weakptr(&idx,
diff->base.repo))) {
error = git_index_add(idx, &updated_entry);
diff->index_updated = true;
}
}
git_buf_free(&full_path);
return error;
}
typedef struct {
git_repository *repo;
git_iterator *old_iter;
git_iterator *new_iter;
const git_index_entry *oitem;
const git_index_entry *nitem;
} diff_in_progress;
#define MODE_BITS_MASK 0000777
static int maybe_modified_submodule(
git_delta_t *status,
git_oid *found_oid,
git_diff_generated *diff,
diff_in_progress *info)
{
int error = 0;
git_submodule *sub;
unsigned int sm_status = 0;
git_submodule_ignore_t ign = diff->base.opts.ignore_submodules;
*status = GIT_DELTA_UNMODIFIED;
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) ||
ign == GIT_SUBMODULE_IGNORE_ALL)
return 0;
if ((error = git_submodule_lookup(
&sub, diff->base.repo, info->nitem->path)) < 0) {
/* GIT_EEXISTS means dir with .git in it was found - ignore it */
if (error == GIT_EEXISTS) {
giterr_clear();
error = 0;
}
return error;
}
if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL)
/* ignore it */;
else if ((error = git_submodule__status(
&sm_status, NULL, NULL, found_oid, sub, ign)) < 0)
/* return error below */;
/* check IS_WD_UNMODIFIED because this case is only used
* when the new side of the diff is the working directory
*/
else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status))
*status = GIT_DELTA_MODIFIED;
/* now that we have a HEAD OID, check if HEAD moved */
else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 &&
!git_oid_equal(&info->oitem->id, found_oid))
*status = GIT_DELTA_MODIFIED;
git_submodule_free(sub);
return error;
}
static int maybe_modified(
git_diff_generated *diff,
diff_in_progress *info)
{
git_oid noid;
git_delta_t status = GIT_DELTA_MODIFIED;
const git_index_entry *oitem = info->oitem;
const git_index_entry *nitem = info->nitem;
unsigned int omode = oitem->mode;
unsigned int nmode = nitem->mode;
bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR);
bool modified_uncertain = false;
const char *matched_pathspec;
int error = 0;
if (!diff_pathspec_match(&matched_pathspec, diff, oitem))
return 0;
memset(&noid, 0, sizeof(noid));
/* on platforms with no symlinks, preserve mode of existing symlinks */
if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir &&
!(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS))
nmode = omode;
/* on platforms with no execmode, just preserve old mode */
if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) &&
(nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) &&
new_is_workdir)
nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK);
/* if one side is a conflict, mark the whole delta as conflicted */
if (git_index_entry_is_conflict(oitem) ||
git_index_entry_is_conflict(nitem)) {
status = GIT_DELTA_CONFLICTED;
/* support "assume unchanged" (poorly, b/c we still stat everything) */
} else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) {
status = GIT_DELTA_UNMODIFIED;
/* support "skip worktree" index bit */
} else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) {
status = GIT_DELTA_UNMODIFIED;
/* if basic type of file changed, then split into delete and add */
} else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) {
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) {
status = GIT_DELTA_TYPECHANGE;
}
else if (nmode == GIT_FILEMODE_UNREADABLE) {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem);
return error;
}
else {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
return error;
}
/* if oids and modes match (and are valid), then file is unmodified */
} else if (git_oid_equal(&oitem->id, &nitem->id) &&
omode == nmode &&
!git_oid_iszero(&oitem->id)) {
status = GIT_DELTA_UNMODIFIED;
/* if we have an unknown OID and a workdir iterator, then check some
* circumstances that can accelerate things or need special handling
*/
} else if (git_oid_iszero(&nitem->id) && new_is_workdir) {
bool use_ctime =
((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0);
git_index *index = git_iterator_index(info->new_iter);
status = GIT_DELTA_UNMODIFIED;
if (S_ISGITLINK(nmode)) {
if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0)
return error;
}
/* if the stat data looks different, then mark modified - this just
* means that the OID will be recalculated below to confirm change
*/
else if (omode != nmode || oitem->file_size != nitem->file_size) {
status = GIT_DELTA_MODIFIED;
modified_uncertain =
(oitem->file_size <= 0 && nitem->file_size > 0);
}
else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) ||
(use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) ||
oitem->ino != nitem->ino ||
oitem->uid != nitem->uid ||
oitem->gid != nitem->gid ||
git_index_entry_newer_than_index(nitem, index))
{
status = GIT_DELTA_MODIFIED;
modified_uncertain = true;
}
/* if mode is GITLINK and submodules are ignored, then skip */
} else if (S_ISGITLINK(nmode) &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) {
status = GIT_DELTA_UNMODIFIED;
}
/* if we got here and decided that the files are modified, but we
* haven't calculated the OID of the new item, then calculate it now
*/
if (modified_uncertain && git_oid_iszero(&nitem->id)) {
const git_oid *update_check =
DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ?
&oitem->id : NULL;
if ((error = git_diff__oid_for_entry(
&noid, &diff->base, nitem, nmode, update_check)) < 0)
return error;
/* if oid matches, then mark unmodified (except submodules, where
* the filesystem content may be modified even if the oid still
* matches between the index and the workdir HEAD)
*/
if (omode == nmode && !S_ISGITLINK(omode) &&
git_oid_equal(&oitem->id, &noid))
status = GIT_DELTA_UNMODIFIED;
}
/* If we want case changes, then break this into a delete of the old
* and an add of the new so that consumers can act accordingly (eg,
* checkout will update the case on disk.)
*/
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) &&
strcmp(oitem->path, nitem->path) != 0) {
if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL)))
error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem);
return error;
}
return diff_delta__from_two(
diff, status, oitem, omode, nitem, nmode,
git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec);
}
static bool entry_is_prefixed(
git_diff_generated *diff,
const git_index_entry *item,
const git_index_entry *prefix_item)
{
size_t pathlen;
if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0)
return false;
pathlen = strlen(prefix_item->path);
return (prefix_item->path[pathlen - 1] == '/' ||
item->path[pathlen] == '\0' ||
item->path[pathlen] == '/');
}
static int iterator_current(
const git_index_entry **entry,
git_iterator *iterator)
{
int error;
if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance(
const git_index_entry **entry,
git_iterator *iterator)
{
const git_index_entry *prev_entry = *entry;
int cmp, error;
/* if we're looking for conflicts, we only want to report
* one conflict for each file, instead of all three sides.
* so if this entry is a conflict for this file, and the
* previous one was a conflict for the same file, skip it.
*/
while ((error = git_iterator_advance(entry, iterator)) == 0) {
if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) ||
!git_index_entry_is_conflict(prev_entry) ||
!git_index_entry_is_conflict(*entry))
break;
cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ?
strcasecmp(prev_entry->path, (*entry)->path) :
strcmp(prev_entry->path, (*entry)->path);
if (cmp)
break;
}
if (error == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance_into(
const git_index_entry **entry,
git_iterator *iterator)
{
int error;
if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int iterator_advance_over(
const git_index_entry **entry,
git_iterator_status_t *status,
git_iterator *iterator)
{
int error = git_iterator_advance_over(entry, status, iterator);
if (error == GIT_ITEROVER) {
*entry = NULL;
error = 0;
}
return error;
}
static int handle_unmatched_new_item(
git_diff_generated *diff, diff_in_progress *info)
{
int error = 0;
const git_index_entry *nitem = info->nitem;
git_delta_t delta_type = GIT_DELTA_UNTRACKED;
bool contains_oitem;
/* check if this is a prefix of the other side */
contains_oitem = entry_is_prefixed(diff, info->oitem, nitem);
/* update delta_type if this item is conflicted */
if (git_index_entry_is_conflict(nitem))
delta_type = GIT_DELTA_CONFLICTED;
/* update delta_type if this item is ignored */
else if (git_iterator_current_is_ignored(info->new_iter))
delta_type = GIT_DELTA_IGNORED;
if (nitem->mode == GIT_FILEMODE_TREE) {
bool recurse_into_dir = contains_oitem;
/* check if user requests recursion into this type of dir */
recurse_into_dir = contains_oitem ||
(delta_type == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) ||
(delta_type == GIT_DELTA_IGNORED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
/* do not advance into directories that contain a .git file */
if (recurse_into_dir && !contains_oitem) {
git_buf *full = NULL;
if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
return -1;
if (full && git_path_contains(full, DOT_GIT)) {
/* TODO: warning if not a valid git repository */
recurse_into_dir = false;
}
}
/* still have to look into untracked directories to match core git -
* with no untracked files, directory is treated as ignored
*/
if (!recurse_into_dir &&
delta_type == GIT_DELTA_UNTRACKED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS))
{
git_diff_delta *last;
git_iterator_status_t untracked_state;
/* attempt to insert record for this directory */
if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
return error;
/* if delta wasn't created (because of rules), just skip ahead */
last = diff_delta__last_for_item(diff, nitem);
if (!last)
return iterator_advance(&info->nitem, info->new_iter);
/* iterate into dir looking for an actual untracked file */
if ((error = iterator_advance_over(
&info->nitem, &untracked_state, info->new_iter)) < 0)
return error;
/* if we found nothing that matched our pathlist filter, exclude */
if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) {
git_vector_pop(&diff->base.deltas);
git__free(last);
}
/* if we found nothing or just ignored items, update the record */
if (untracked_state == GIT_ITERATOR_STATUS_IGNORED ||
untracked_state == GIT_ITERATOR_STATUS_EMPTY) {
last->status = GIT_DELTA_IGNORED;
/* remove the record if we don't want ignored records */
if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) {
git_vector_pop(&diff->base.deltas);
git__free(last);
}
}
return 0;
}
/* try to advance into directory if necessary */
if (recurse_into_dir) {
error = iterator_advance_into(&info->nitem, info->new_iter);
/* if directory is empty, can't advance into it, so skip it */
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = iterator_advance(&info->nitem, info->new_iter);
}
return error;
}
}
else if (delta_type == GIT_DELTA_IGNORED &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) &&
git_iterator_current_tree_is_ignored(info->new_iter))
/* item contained in ignored directory, so skip over it */
return iterator_advance(&info->nitem, info->new_iter);
else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) {
if (delta_type != GIT_DELTA_CONFLICTED)
delta_type = GIT_DELTA_ADDED;
}
else if (nitem->mode == GIT_FILEMODE_COMMIT) {
/* ignore things that are not actual submodules */
if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
giterr_clear();
delta_type = GIT_DELTA_IGNORED;
/* if this contains a tracked item, treat as normal TREE */
if (contains_oitem) {
error = iterator_advance_into(&info->nitem, info->new_iter);
if (error != GIT_ENOTFOUND)
return error;
giterr_clear();
return iterator_advance(&info->nitem, info->new_iter);
}
}
}
else if (nitem->mode == GIT_FILEMODE_UNREADABLE) {
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED))
delta_type = GIT_DELTA_UNTRACKED;
else
delta_type = GIT_DELTA_UNREADABLE;
}
/* Actually create the record for this item if necessary */
if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0)
return error;
/* If user requested TYPECHANGE records, then check for that instead of
* just generating an ADDED/UNTRACKED record
*/
if (delta_type != GIT_DELTA_IGNORED &&
DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
contains_oitem)
{
/* this entry was prefixed with a tree - make TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, nitem);
if (last) {
last->status = GIT_DELTA_TYPECHANGE;
last->old_file.mode = GIT_FILEMODE_TREE;
}
}
return iterator_advance(&info->nitem, info->new_iter);
}
static int handle_unmatched_old_item(
git_diff_generated *diff, diff_in_progress *info)
{
git_delta_t delta_type = GIT_DELTA_DELETED;
int error;
/* update delta_type if this item is conflicted */
if (git_index_entry_is_conflict(info->oitem))
delta_type = GIT_DELTA_CONFLICTED;
if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0)
return error;
/* if we are generating TYPECHANGE records then check for that
* instead of just generating a DELETE record
*/
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) &&
entry_is_prefixed(diff, info->nitem, info->oitem))
{
/* this entry has become a tree! convert to TYPECHANGE */
git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem);
if (last) {
last->status = GIT_DELTA_TYPECHANGE;
last->new_file.mode = GIT_FILEMODE_TREE;
}
/* If new_iter is a workdir iterator, then this situation
* will certainly be followed by a series of untracked items.
* Unless RECURSE_UNTRACKED_DIRS is set, skip over them...
*/
if (S_ISDIR(info->nitem->mode) &&
DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS))
return iterator_advance(&info->nitem, info->new_iter);
}
return iterator_advance(&info->oitem, info->old_iter);
}
static int handle_matched_item(
git_diff_generated *diff, diff_in_progress *info)
{
int error = 0;
if ((error = maybe_modified(diff, info)) < 0)
return error;
if (!(error = iterator_advance(&info->oitem, info->old_iter)))
error = iterator_advance(&info->nitem, info->new_iter);
return error;
}
int git_diff__from_iterators(
git_diff **out,
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter,
const git_diff_options *opts)
{
git_diff_generated *diff;
diff_in_progress info;
int error = 0;
*out = NULL;
diff = diff_generated_alloc(repo, old_iter, new_iter);
GITERR_CHECK_ALLOC(diff);
info.repo = repo;
info.old_iter = old_iter;
info.new_iter = new_iter;
/* make iterators have matching icase behavior */
if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) {
git_iterator_set_ignore_case(old_iter, true);
git_iterator_set_ignore_case(new_iter, true);
}
/* finish initialization */
if ((error = diff_generated_apply_options(diff, opts)) < 0)
goto cleanup;
if ((error = iterator_current(&info.oitem, old_iter)) < 0 ||
(error = iterator_current(&info.nitem, new_iter)) < 0)
goto cleanup;
/* run iterators building diffs */
while (!error && (info.oitem || info.nitem)) {
int cmp;
/* report progress */
if (opts && opts->progress_cb) {
if ((error = opts->progress_cb(&diff->base,
info.oitem ? info.oitem->path : NULL,
info.nitem ? info.nitem->path : NULL,
opts->payload)))
break;
}
cmp = info.oitem ?
(info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1;
/* create DELETED records for old items not matched in new */
if (cmp < 0)
error = handle_unmatched_old_item(diff, &info);
/* create ADDED, TRACKED, or IGNORED records for new items not
* matched in old (and/or descend into directories as needed)
*/
else if (cmp > 0)
error = handle_unmatched_new_item(diff, &info);
/* otherwise item paths match, so create MODIFIED record
* (or ADDED and DELETED pair if type changed)
*/
else
error = handle_matched_item(diff, &info);
}
diff->base.perf.stat_calls +=
old_iter->stat_calls + new_iter->stat_calls;
cleanup:
if (!error)
*out = &diff->base;
else
git_diff_free(&diff->base);
return error;
}
#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \
git_iterator *a = NULL, *b = NULL; \
char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \
git_pathspec_prefix(&opts->pathspec) : NULL; \
git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \
b_opts = GIT_ITERATOR_OPTIONS_INIT; \
a_opts.flags = FLAGS_FIRST; \
a_opts.start = pfx; \
a_opts.end = pfx; \
b_opts.flags = FLAGS_SECOND; \
b_opts.start = pfx; \
b_opts.end = pfx; \
GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \
if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \
a_opts.pathlist.strings = opts->pathspec.strings; \
a_opts.pathlist.count = opts->pathspec.count; \
b_opts.pathlist.strings = opts->pathspec.strings; \
b_opts.pathlist.count = opts->pathspec.count; \
} \
if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \
error = git_diff__from_iterators(&diff, repo, a, b, opts); \
git__free(pfx); git_iterator_free(a); git_iterator_free(b); \
} while (0)
int git_diff_tree_to_tree(
git_diff **out,
git_repository *repo,
git_tree *old_tree,
git_tree *new_tree,
const git_diff_options *opts)
{
git_diff *diff = NULL;
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE;
int error = 0;
assert(out && repo);
*out = NULL;
/* for tree to tree diff, be case sensitive even if the index is
* currently case insensitive, unless the user explicitly asked
* for case insensitivity
*/
if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0)
iflag = GIT_ITERATOR_IGNORE_CASE;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_tree(&b, new_tree, &b_opts), iflag
);
if (!error)
*out = diff;
return error;
}
static int diff_load_index(git_index **index, git_repository *repo)
{
int error = git_repository_index__weakptr(index, repo);
/* reload the repository index when user did not pass one in */
if (!error && git_index_read(*index, false) < 0)
giterr_clear();
return error;
}
int git_diff_tree_to_index(
git_diff **out,
git_repository *repo,
git_tree *old_tree,
git_index *index,
const git_diff_options *opts)
{
git_diff *diff = NULL;
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE |
GIT_ITERATOR_INCLUDE_CONFLICTS;
bool index_ignore_case = false;
int error = 0;
assert(out && repo);
*out = NULL;
if (!index && (error = diff_load_index(&index, repo)) < 0)
return error;
index_ignore_case = index->ignore_case;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
git_iterator_for_index(&b, repo, index, &b_opts), iflag
);
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && index_ignore_case)
git_diff__set_ignore_case(diff, true);
if (!error)
*out = diff;
return error;
}
int git_diff_index_to_workdir(
git_diff **out,
git_repository *repo,
git_index *index,
const git_diff_options *opts)
{
git_diff *diff = NULL;
int error = 0;
assert(out && repo);
*out = NULL;
if (!index && (error = diff_load_index(&index, repo)) < 0)
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, index, &a_opts),
GIT_ITERATOR_INCLUDE_CONFLICTS,
git_iterator_for_workdir(&b, repo, index, NULL, &b_opts),
GIT_ITERATOR_DONT_AUTOEXPAND
);
if (!error && (diff->opts.flags & GIT_DIFF_UPDATE_INDEX) != 0 &&
((git_diff_generated *)diff)->index_updated)
error = git_index_write(index);
if (!error)
*out = diff;
return error;
}
int git_diff_tree_to_workdir(
git_diff **out,
git_repository *repo,
git_tree *old_tree,
const git_diff_options *opts)
{
git_diff *diff = NULL;
git_index *index;
int error = 0;
assert(out && repo);
*out = NULL;
if ((error = git_repository_index__weakptr(&index, repo)))
return error;
DIFF_FROM_ITERATORS(
git_iterator_for_tree(&a, old_tree, &a_opts), 0,
git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND
);
if (!error)
*out = diff;
return error;
}
int git_diff_tree_to_workdir_with_index(
git_diff **out,
git_repository *repo,
git_tree *tree,
const git_diff_options *opts)
{
git_diff *d1 = NULL, *d2 = NULL;
git_index *index = NULL;
int error = 0;
assert(out && repo);
*out = NULL;
if ((error = diff_load_index(&index, repo)) < 0)
return error;
if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) &&
!(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
error = git_diff_merge(d1, d2);
git_diff_free(d2);
if (error) {
git_diff_free(d1);
d1 = NULL;
}
*out = d1;
return error;
}
int git_diff_index_to_index(
git_diff **out,
git_repository *repo,
git_index *old_index,
git_index *new_index,
const git_diff_options *opts)
{
git_diff *diff;
int error = 0;
assert(out && old_index && new_index);
*out = NULL;
DIFF_FROM_ITERATORS(
git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE,
git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE
);
/* if index is in case-insensitive order, re-sort deltas to match */
if (!error && (old_index->ignore_case || new_index->ignore_case))
git_diff__set_ignore_case(diff, true);
if (!error)
*out = diff;
return error;
}
int git_diff__paired_foreach(
git_diff *head2idx,
git_diff *idx2wd,
int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
void *payload)
{
int cmp, error = 0;
git_diff_delta *h2i, *i2w;
size_t i, j, i_max, j_max;
int (*strcomp)(const char *, const char *) = git__strcmp;
bool h2i_icase, i2w_icase, icase_mismatch;
i_max = head2idx ? head2idx->deltas.length : 0;
j_max = idx2wd ? idx2wd->deltas.length : 0;
if (!i_max && !j_max)
return 0;
/* At some point, tree-to-index diffs will probably never ignore case,
* even if that isn't true now. Index-to-workdir diffs may or may not
* ignore case, but the index filename for the idx2wd diff should
* still be using the canonical case-preserving name.
*
* Therefore the main thing we need to do here is make sure the diffs
* are traversed in a compatible order. To do this, we temporarily
* resort a mismatched diff to get the order correct.
*
* In order to traverse renames in the index->workdir, we need to
* ensure that we compare the index name on both sides, so we
* always sort by the old name in the i2w list.
*/
h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx);
i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd);
icase_mismatch =
(head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
git_vector_sort(&head2idx->deltas);
}
if (i2w_icase && !icase_mismatch) {
strcomp = git__strcasecmp;
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
git_vector_sort(&idx2wd->deltas);
} else if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
git_vector_sort(&idx2wd->deltas);
}
for (i = 0, j = 0; i < i_max || j < j_max; ) {
h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
cmp = !i2w ? -1 : !h2i ? 1 :
strcomp(h2i->new_file.path, i2w->old_file.path);
if (cmp < 0) {
i++; i2w = NULL;
} else if (cmp > 0) {
j++; h2i = NULL;
} else {
i++; j++;
}
if ((error = cb(h2i, i2w, payload)) != 0) {
giterr_set_after_callback(error);
break;
}
}
/* restore case-insensitive delta sort */
if (icase_mismatch && h2i_icase) {
git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
git_vector_sort(&head2idx->deltas);
}
/* restore idx2wd sort by new path */
if (idx2wd != NULL) {
git_vector_set_cmp(&idx2wd->deltas,
i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
git_vector_sort(&idx2wd->deltas);
}
return error;
}
int git_diff__commit(
git_diff **out,
git_repository *repo,
const git_commit *commit,
const git_diff_options *opts)
{
git_commit *parent = NULL;
git_diff *commit_diff = NULL;
git_tree *old_tree = NULL, *new_tree = NULL;
size_t parents;
int error = 0;
*out = NULL;
if ((parents = git_commit_parentcount(commit)) > 1) {
char commit_oidstr[GIT_OID_HEXSZ + 1];
error = -1;
giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
goto on_error;
}
if (parents > 0)
if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
(error = git_commit_tree(&old_tree, parent)) < 0)
goto on_error;
if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
(error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
goto on_error;
*out = commit_diff;
on_error:
git_tree_free(new_tree);
git_tree_free(old_tree);
git_commit_free(parent);
return error;
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_generate_h__
#define INCLUDE_diff_generate_h__
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
enum {
GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
};
#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
#define GIT_DIFF__VERBOSE (1 << 30)
extern void git_diff_addref(git_diff *diff);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
extern int git_diff__from_iterators(
git_diff **diff_ptr,
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter,
const git_diff_options *opts);
extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
extern int git_diff__paired_foreach(
git_diff *idx2head,
git_diff *wd2idx,
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
void *payload);
/* Merge two `git_diff`s according to the callback given by `cb`. */
typedef git_diff_delta *(*git_diff__merge_cb)(
const git_diff_delta *left,
const git_diff_delta *right,
git_pool *pool);
extern int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
extern git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool);
/* Duplicate a `git_diff_delta` out of the `git_pool` */
extern git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool);
extern int git_diff__oid_for_file(
git_oid *out,
git_diff *diff,
const char *path,
uint16_t mode,
git_off_t size);
extern int git_diff__oid_for_entry(
git_oid *out,
git_diff *diff,
const git_index_entry *src,
uint16_t mode,
const git_oid *update_match);
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
* not possible, then it will return the git_odb_object that had to be
* loaded and the caller can use it or dispose of it as needed.
*/
GIT_INLINE(int) git_diff_file__resolve_zero_size(
git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
{
int error;
git_odb *odb;
size_t len;
git_otype type;
if ((error = git_repository_odb(&odb, repo)) < 0)
return error;
error = git_odb__read_header_or_object(
odb_obj, &len, &type, odb, &file->id);
git_odb_free(odb);
if (!error)
file->size = (git_off_t)len;
return error;
}
#endif
/*
* 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.
*/
#include "common.h"
#include "diff.h"
#include "patch.h"
#include "patch_parse.h"
typedef struct {
struct git_diff base;
git_vector patches;
} git_diff_parsed;
static void diff_parsed_free(git_diff *d)
{
git_diff_parsed *diff = (git_diff_parsed *)d;
git_patch *patch;
size_t i;
git_vector_foreach(&diff->patches, i, patch)
git_patch_free(patch);
git_vector_free(&diff->patches);
git_vector_free(&diff->base.deltas);
git_pool_clear(&diff->base.pool);
git__memzero(diff, sizeof(*diff));
git__free(diff);
}
static git_diff_parsed *diff_parsed_alloc(void)
{
git_diff_parsed *diff;
if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL)
return NULL;
GIT_REFCOUNT_INC(diff);
diff->base.type = GIT_DIFF_TYPE_PARSED;
diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE;
diff->base.strcomp = git__strcmp;
diff->base.strncomp = git__strncmp;
diff->base.pfxcomp = git__prefixcmp;
diff->base.entrycomp = git_diff__entry_cmp;
diff->base.free_fn = diff_parsed_free;
git_pool_init(&diff->base.pool, 1);
if (git_vector_init(&diff->patches, 0, NULL) < 0 ||
git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
git_diff_free(&diff->base);
return NULL;
}
git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp);
return diff;
}
int git_diff_from_buffer(
git_diff **out,
const char *content,
size_t content_len)
{
git_diff_parsed *diff;
git_patch *patch;
git_patch_parse_ctx *ctx = NULL;
int error = 0;
*out = NULL;
diff = diff_parsed_alloc();
GITERR_CHECK_ALLOC(diff);
ctx = git_patch_parse_ctx_init(content, content_len, NULL);
GITERR_CHECK_ALLOC(ctx);
while (ctx->remain_len) {
if ((error = git_patch_parse(&patch, ctx)) < 0)
break;
git_vector_insert(&diff->patches, patch);
git_vector_insert(&diff->base.deltas, patch->delta);
}
if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) {
giterr_clear();
error = 0;
}
git_patch_parse_ctx_free(ctx);
if (error < 0)
git_diff_free(&diff->base);
else
*out = &diff->base;
return error;
}
......@@ -6,7 +6,8 @@
*/
#include "common.h"
#include "diff.h"
#include "diff_patch.h"
#include "diff_file.h"
#include "patch_generate.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;
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;
const char *old_prefix;
const char *new_prefix;
uint32_t flags;
int id_strlen;
int (*strcomp)(const char *, const char *);
} diff_print_info;
static int diff_print_info_init__common(
......@@ -42,17 +43,15 @@ static int diff_print_info_init__common(
pi->payload = payload;
pi->buf = out;
if (!pi->oid_strlen) {
if (!pi->id_strlen) {
if (!repo)
pi->oid_strlen = GIT_ABBREV_DEFAULT;
else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0)
pi->id_strlen = GIT_ABBREV_DEFAULT;
else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0)
return -1;
}
pi->oid_strlen += 1; /* for NUL byte */
if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
pi->oid_strlen = GIT_OID_HEXSZ + 1;
if (pi->id_strlen > GIT_OID_HEXSZ)
pi->id_strlen = GIT_OID_HEXSZ;
memset(&pi->line, 0, sizeof(pi->line));
pi->line.old_lineno = -1;
......@@ -74,11 +73,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->id_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 +93,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->content_loaded = 1;
pi->ofile = &patch->ofile;
pi->nfile = &patch->nfile;
pi->id_strlen = patch->diff_opts.id_abbrev;
pi->old_prefix = patch->diff_opts.old_prefix;
pi->new_prefix = patch->diff_opts.new_prefix;
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 +166,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);
......@@ -213,6 +206,7 @@ static int diff_print_one_raw(
{
diff_print_info *pi = data;
git_buf *out = pi->buf;
int id_abbrev;
char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
......@@ -223,11 +217,21 @@ static int diff_print_one_raw(
git_buf_clear(out);
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id);
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
delta->new_file.id_abbrev;
if (pi->id_strlen > id_abbrev) {
giterr_set(GITERR_PATCH,
"The patch input contains %d id characters (cannot print %d)",
id_abbrev, pi->id_strlen);
return -1;
}
git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
git_buf_printf(
out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
......@@ -252,53 +256,140 @@ static int diff_print_one_raw(
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_modes(
git_buf *out, const git_diff_delta *delta)
{
git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
return git_buf_oom(out) ? -1 : 0;
}
static int diff_print_oid_range(
git_buf *out, const git_diff_delta *delta, int oid_strlen)
git_buf *out, const git_diff_delta *delta, int id_strlen)
{
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
if (delta->old_file.mode &&
id_strlen > delta->old_file.id_abbrev) {
giterr_set(GITERR_PATCH,
"The patch input contains %d id characters (cannot print %d)",
delta->old_file.id_abbrev, id_strlen);
return -1;
}
if ((delta->new_file.mode &&
id_strlen > delta->new_file.id_abbrev)) {
giterr_set(GITERR_PATCH,
"The patch input contains %d id characters (cannot print %d)",
delta->new_file.id_abbrev, id_strlen);
return -1;
}
git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
/* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode);
} else {
if (delta->old_file.mode == 0) {
if (delta->old_file.mode == 0)
git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
} else if (delta->new_file.mode == 0) {
else if (delta->new_file.mode == 0)
git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
} else {
git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
}
else
diff_print_modes(out, delta);
git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
}
return git_buf_oom(out) ? -1 : 0;
}
static int diff_delta_format_path(
git_buf *out, const char *prefix, const char *filename)
{
if (git_buf_joinpath(out, prefix, filename) < 0)
return -1;
return git_buf_quote(out);
}
static int diff_delta_format_with_paths(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
const char *template)
const char *template,
const char *oldpath,
const char *newpath)
{
const char *oldpath = delta->old_file.path;
const char *newpath = delta->new_file.path;
if (git_oid_iszero(&delta->old_file.id)) {
oldpfx = "";
if (git_oid_iszero(&delta->old_file.id))
oldpath = "/dev/null";
}
if (git_oid_iszero(&delta->new_file.id)) {
newpfx = "";
if (git_oid_iszero(&delta->new_file.id))
newpath = "/dev/null";
return git_buf_printf(out, template, oldpath, newpath);
}
int diff_delta_format_similarity_header(
git_buf *out,
const git_diff_delta *delta)
{
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
const char *type;
int error = 0;
if (delta->similarity > 100) {
giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity);
error = -1;
goto done;
}
return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
if (delta->status == GIT_DELTA_RENAMED)
type = "rename";
else if (delta->status == GIT_DELTA_COPIED)
type = "copy";
else
abort();
if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
(error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
(error = git_buf_quote(&old_path)) < 0 ||
(error = git_buf_quote(&new_path)) < 0)
goto done;
git_buf_printf(out,
"similarity index %d%%\n"
"%s from %s\n"
"%s to %s\n",
delta->similarity,
type, old_path.ptr,
type, new_path.ptr);
if (git_buf_oom(out))
error = -1;
done:
git_buf_free(&old_path);
git_buf_free(&new_path);
return error;
}
static bool delta_is_unchanged(const git_diff_delta *delta)
{
if (git_oid_iszero(&delta->old_file.id) &&
git_oid_iszero(&delta->new_file.id))
return true;
if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
delta->new_file.mode == GIT_FILEMODE_COMMIT)
return false;
if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
return true;
return false;
}
int git_diff_delta__format_file_header(
......@@ -306,27 +397,56 @@ int git_diff_delta__format_file_header(
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen)
int id_strlen)
{
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
bool unchanged = delta_is_unchanged(delta);
int error = 0;
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
if (!oid_strlen)
oid_strlen = GIT_ABBREV_DEFAULT + 1;
if (!id_strlen)
id_strlen = GIT_ABBREV_DEFAULT;
if ((error = diff_delta_format_path(
&old_path, oldpfx, delta->old_file.path)) < 0 ||
(error = diff_delta_format_path(
&new_path, newpfx, delta->new_file.path)) < 0)
goto done;
git_buf_clear(out);
git_buf_printf(out, "diff --git %s%s %s%s\n",
oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
git_buf_printf(out, "diff --git %s %s\n",
old_path.ptr, new_path.ptr);
if (delta->status == GIT_DELTA_RENAMED ||
(delta->status == GIT_DELTA_COPIED && unchanged)) {
if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
goto done;
}
GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
if (!unchanged) {
if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0)
goto done;
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
diff_delta_format_with_paths(
out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
diff_delta_format_with_paths(out, delta,
"--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
}
return git_buf_oom(out) ? -1 : 0;
if (unchanged && delta->old_file.mode != delta->new_file.mode)
diff_print_modes(out, delta);
if (git_buf_oom(out))
error = -1;
done:
git_buf_free(&old_path);
git_buf_free(&new_path);
return error;
}
static int format_binary(
......@@ -367,37 +487,30 @@ static int format_binary(
return 0;
}
static int diff_print_load_content(
diff_print_info *pi,
git_diff_delta *delta)
static int diff_print_patch_file_binary_noshow(
diff_print_info *pi, git_diff_delta *delta,
const char *old_pfx, const char *new_pfx)
{
git_diff_file_content *ofile, *nfile;
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
int error;
assert(pi->diff);
if ((error = diff_delta_format_path(
&old_path, old_pfx, delta->old_file.path)) < 0 ||
(error = diff_delta_format_path(
&new_path, new_pfx, delta->new_file.path)) < 0)
goto done;
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);
pi->line.num_lines = 1;
error = diff_delta_format_with_paths(
pi->buf, delta, "Binary files %s and %s differ\n",
old_path.ptr, new_path.ptr);
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) {
done:
git_buf_free(&old_path);
git_buf_free(&new_path);
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(
......@@ -409,11 +522,11 @@ static int diff_print_patch_file_binary(
int error;
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
goto noshow;
return diff_print_patch_file_binary_noshow(
pi, delta, old_pfx, new_pfx);
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;
pre_binary_size = pi->buf->size;
git_buf_printf(pi->buf, "GIT binary patch\n");
......@@ -427,18 +540,14 @@ static int diff_print_patch_file_binary(
if (error == GIT_EBUFS) {
giterr_clear();
git_buf_truncate(pi->buf, pre_binary_size);
goto noshow;
return diff_print_patch_file_binary_noshow(
pi, delta, old_pfx, new_pfx);
}
}
pi->line.num_lines++;
return error;
noshow:
pi->line.num_lines = 1;
return diff_delta_format_with_paths(
pi->buf, delta, old_pfx, new_pfx,
"Binary files %s%s and %s%s differ\n");
}
static int diff_print_patch_file(
......@@ -447,15 +556,15 @@ 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);
bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
int oid_strlen = binary && show_binary ?
GIT_OID_HEXSZ + 1 : pi->oid_strlen;
int id_strlen = binary && show_binary ?
GIT_OID_HEXSZ : pi->id_strlen;
GIT_UNUSED(progress);
......@@ -468,7 +577,7 @@ static int diff_print_patch_file(
return 0;
if ((error = git_diff_delta__format_file_header(
pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0)
return error;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
......@@ -485,9 +594,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);
......@@ -582,43 +691,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,
......@@ -659,6 +736,46 @@ int git_diff_print_callback__to_file_handle(
return 0;
}
/* print a git_diff to a git_buf */
int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
{
assert(out && diff);
git_buf_sanitize(out);
return git_diff_print(
diff, format, git_diff_print_callback__to_buf, out);
}
/* print a git_patch to an output callback */
int git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf temp = GIT_BUF_INIT;
diff_print_info pi;
assert(patch && print_cb);
if (!(error = diff_print_info_init_frompatch(
&pi, &temp, patch,
GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
{
error = git_patch__invoke_callbacks(
patch,
diff_print_patch_file, diff_print_patch_binary,
diff_print_patch_hunk, diff_print_patch_line,
&pi);
if (error) /* make sure error message is set */
giterr_set_after_callback_function(error, "git_patch_print");
}
git_buf_free(&temp);
return error;
}
/* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch)
{
......
......@@ -7,7 +7,7 @@
#include "common.h"
#include "vector.h"
#include "diff.h"
#include "diff_patch.h"
#include "patch_generate.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);
......
......@@ -11,6 +11,7 @@
#include "git2/sys/hashsig.h"
#include "diff.h"
#include "diff_generate.h"
#include "path.h"
#include "fileops.h"
#include "config.h"
......
/*
* 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_tform_h__
#define INCLUDE_diff_tform_h__
extern int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p);
extern int git_diff_find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload);
#endif
......@@ -8,8 +8,8 @@
#include "common.h"
#include "diff.h"
#include "diff_driver.h"
#include "diff_patch.h"
#include "diff_xdiff.h"
#include "patch_generate.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_generated *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_generated *patch = info->patch;
const git_diff_delta *delta = patch->base.delta;
git_patch_generated_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_generated_output *output, git_patch_generated *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_generated_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_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
......
......@@ -8,20 +8,20 @@
#define INCLUDE_diff_xdiff_h__
#include "diff.h"
#include "diff_patch.h"
#include "xdiff/xdiff.h"
#include "patch_generate.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
* to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
* of the output to use xdiff to generate the diffs.
/* A git_xdiff_output is a git_patch_generate_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_generated_output output;
xdemitconf_t config;
xpparam_t params;
......
......@@ -18,6 +18,8 @@
#include "iterator.h"
#include "refs.h"
#include "diff.h"
#include "diff_generate.h"
#include "diff_tform.h"
#include "checkout.h"
#include "tree.h"
#include "blob.h"
......
......@@ -12,7 +12,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
#include "delta.h"
#include "filter.h"
#include "repository.h"
......
......@@ -12,7 +12,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
#include "delta.h"
#include "filebuf.h"
#include "git2/odb_backend.h"
......
......@@ -13,7 +13,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
#include "delta-apply.h"
#include "delta.h"
#include "sha1_lookup.h"
#include "mwindow.h"
#include "pack.h"
......
......@@ -144,7 +144,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
pb->nr_threads = 1; /* do not spawn any thread by default */
if (git_hash_ctx_init(&pb->ctx) < 0 ||
git_zstream_init(&pb->zstream) < 0 ||
git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 ||
git_repository_odb(&pb->odb, repo) < 0 ||
packbuilder_config(pb) < 0)
goto on_error;
......@@ -274,6 +274,7 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_object *src = NULL, *trg = NULL;
unsigned long delta_size;
void *delta_buf;
int error;
*out = NULL;
......@@ -281,12 +282,15 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_read(&trg, odb, &po->id) < 0)
goto on_error;
delta_buf = git_delta(
git_odb_object_data(src), (unsigned long)git_odb_object_size(src),
git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg),
&delta_size, 0);
error = git_delta(&delta_buf, &delta_size,
git_odb_object_data(src), git_odb_object_size(src),
git_odb_object_data(trg), git_odb_object_size(trg),
0);
if (error < 0 && error != GIT_EBUFS)
goto on_error;
if (!delta_buf || delta_size != po->delta_size) {
if (error == GIT_EBUFS || delta_size != po->delta_size) {
giterr_set(GITERR_INVALID, "Delta size changed");
goto on_error;
}
......@@ -815,16 +819,14 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg,
*mem_usage += sz;
}
if (!src->index) {
src->index = git_delta_create_index(src->data, src_size);
if (!src->index)
if (git_delta_index_init(&src->index, src->data, src_size) < 0)
return 0; /* suboptimal pack - out of memory */
*mem_usage += git_delta_sizeof_index(src->index);
*mem_usage += git_delta_index_size(src->index);
}
delta_buf = git_delta_create(src->index, trg->data, trg_size,
&delta_size, max_size);
if (!delta_buf)
if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size,
max_size) < 0)
return 0;
if (trg_object->delta) {
......@@ -885,9 +887,14 @@ static unsigned int check_delta_limit(git_pobject *me, unsigned int n)
static unsigned long free_unpacked(struct unpacked *n)
{
unsigned long freed_mem = git_delta_sizeof_index(n->index);
git_delta_free_index(n->index);
unsigned long freed_mem = 0;
if (n->index) {
freed_mem += git_delta_index_size(n->index);
git_delta_index_free(n->index);
}
n->index = NULL;
if (n->data) {
freed_mem += (unsigned long)n->object->size;
git__free(n->data);
......
......@@ -8,7 +8,7 @@
#include "common.h"
#include "odb.h"
#include "pack.h"
#include "delta-apply.h"
#include "delta.h"
#include "sha1_lookup.h"
#include "mwindow.h"
#include "fileops.h"
......@@ -505,7 +505,7 @@ int git_packfile_resolve_header(
git_mwindow_close(&w_curs);
if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0)
return error;
error = git__delta_read_header_fromstream(&base_size, size_p, &stream);
error = git_delta_read_header_fromstream(&base_size, size_p, &stream);
git_packfile_stream_free(&stream);
if (error < 0)
return error;
......@@ -730,8 +730,9 @@ int git_packfile_unpack(
obj->len = 0;
obj->type = GIT_OBJ_BAD;
error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len);
error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len);
obj->type = base_type;
/*
* We usually don't want to free the base at this
* point, as we put it into the cache in the previous
......
#include "git2/patch.h"
#include "diff.h"
#include "patch.h"
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) {
git_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;
}
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;
}
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;
}
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);
}
static int patch_error_outofrange(const char *thing)
{
giterr_set(GITERR_INVALID, "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)
{
git_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 patch_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)
{
git_patch_hunk *hunk;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
return patch_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)
{
git_patch_hunk *hunk;
git_diff_line *line;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
if (out) *out = NULL;
return patch_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 patch_error_outofrange("line");
}
if (out) *out = line;
return 0;
}
static void git_patch__free(git_patch *patch)
{
if (patch->free_fn)
patch->free_fn(patch);
}
void git_patch_free(git_patch *patch)
{
if (patch)
GIT_REFCOUNT_DEC(patch, git_patch__free);
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_patch_h__
#define INCLUDE_patch_h__
#include "git2/patch.h"
#include "array.h"
/* cached information about a hunk in a patch */
typedef struct git_patch_hunk {
git_diff_hunk hunk;
size_t line_start;
size_t line_count;
} git_patch_hunk;
struct git_patch {
git_refcount rc;
git_repository *repo; /* may be null */
git_diff_options diff_opts;
git_diff_delta *delta;
git_diff_binary binary;
git_array_t(git_patch_hunk) hunks;
git_array_t(git_diff_line) lines;
size_t header_size;
size_t content_size;
size_t context_size;
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);
/** Options for parsing patch files. */
typedef struct {
/**
* The length of the prefix (in path segments) for the filenames.
* This prefix will be removed when looking for files. The default is 1.
*/
uint32_t prefix_len;
} git_patch_options;
#define GIT_PATCH_OPTIONS_INIT { 1 }
extern void git_patch_free(git_patch *patch);
#endif
......@@ -7,49 +7,78 @@
#include "common.h"
#include "git2/blob.h"
#include "diff.h"
#include "diff_generate.h"
#include "diff_file.h"
#include "diff_driver.h"
#include "diff_patch.h"
#include "patch_generate.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_generated_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_generated_output *, git_patch_generated *);
static void diff_patch_update_binary(git_patch *patch)
static void patch_generated_free(git_patch *p)
{
if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
git_patch_generated *patch = (git_patch_generated *)p;
git_array_clear(patch->base.lines);
git_array_clear(patch->base.hunks);
git__free((char *)patch->base.binary.old_file.data);
git__free((char *)patch->base.binary.new_file.data);
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_GENERATED_ALLOCATED)
git__free(patch);
}
static void patch_generated_update_binary(git_patch_generated *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->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->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}
static void diff_patch_init_common(git_patch *patch)
static void patch_generated_init_common(git_patch_generated *patch)
{
diff_patch_update_binary(patch);
patch->base.free_fn = patch_generated_free;
patch_generated_update_binary(patch);
patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
if (patch->diff)
git_diff_addref(patch->diff);
}
static int diff_patch_normalize_options(
static int patch_generated_normalize_options(
git_diff_options *out,
const git_diff_options *opts)
{
......@@ -75,38 +104,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_generated_init(
git_patch_generated *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->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_generated_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_generated_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_generated_alloc_from_diff(
git_patch_generated **out, git_diff *diff, size_t delta_index)
{
int error;
git_patch *patch = git__calloc(1, sizeof(git_patch));
git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
GITERR_CHECK_ALLOC(patch);
if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
if (!(error = patch_generated_init(patch, diff, delta_index))) {
patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
GIT_REFCOUNT_INC(patch);
} else {
git__free(patch);
......@@ -117,27 +148,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_generated *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_generated_diffable(git_patch_generated *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 +185,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_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
{
int error = 0;
bool incomplete_data;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
return 0;
/* if no hunk and data callbacks and user doesn't care if data looks
......@@ -180,13 +211,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 +225,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 +243,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_generated_update_binary(patch);
if (!error) {
if (diff_patch_diffable(patch))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
if (patch_generated_diffable(patch))
patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED;
patch->flags |= GIT_PATCH_GENERATED_LOADED;
}
return error;
}
static int diff_patch_invoke_file_callback(
git_patch *patch, git_diff_output *output)
static int patch_generated_invoke_file_callback(
git_patch_generated *patch, git_patch_generated_output *output)
{
float progress = patch->diff ?
((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
......@@ -238,7 +269,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");
}
......@@ -270,21 +301,25 @@ static int create_binary(
}
if (a_datalen && b_datalen) {
void *delta_data = git_delta(
a_data, (unsigned long)a_datalen,
b_data, (unsigned long)b_datalen,
&delta_data_len, (unsigned long)deflate.size);
void *delta_data;
error = git_delta(&delta_data, &delta_data_len,
a_data, a_datalen,
b_data, b_datalen,
deflate.size);
if (delta_data) {
if (error == 0) {
error = git_zstream_deflatebuf(
&delta, delta_data, (size_t)delta_data_len);
git__free(delta_data);
} else if (error == GIT_EBUFS) {
error = 0;
}
if (error < 0)
goto done;
}
}
if (delta.size && delta.size < deflate.size) {
*out_type = GIT_DIFF_BINARY_DELTA;
......@@ -305,7 +340,7 @@ done:
return error;
}
static int diff_binary(git_diff_output *output, git_patch *patch)
static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
{
git_diff_binary binary = {{0}};
const char *old_data = patch->ofile.map.data;
......@@ -330,7 +365,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);
......@@ -339,25 +374,27 @@ 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_generated_create(
git_patch_generated *patch,
git_patch_generated_output *output)
{
int error = 0;
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
if ((patch->flags & GIT_PATCH_GENERATED_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_GENERATED_LOADED) == 0 &&
(error = patch_generated_load(patch, output)) < 0)
return error;
if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
if ((patch->flags & GIT_PATCH_GENERATED_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);
}
......@@ -366,33 +403,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_GENERATED_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)
......@@ -412,7 +426,7 @@ int git_diff_foreach(
int error = 0;
git_xdiff_output xo;
size_t idx;
git_patch patch;
git_patch_generated patch;
if ((error = diff_required(diff, "git_diff_foreach")) < 0)
return error;
......@@ -423,24 +437,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_generated_init(&patch, diff, idx)) != 0 ||
(error = patch_generated_load(&patch, &xo.output)) != 0)
return error;
}
if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
if ((error = patch_generated_invoke_file_callback(&patch, &xo.output)) == 0) {
if (binary_cb || hunk_cb || data_cb)
error = diff_patch_generate(&patch, &xo.output);
error = patch_generated_create(&patch, &xo.output);
}
git_patch_free(&patch);
git_patch_free(&patch.base);
if (error)
break;
......@@ -450,15 +464,15 @@ int git_diff_foreach(
}
typedef struct {
git_patch patch;
git_patch_generated patch;
git_diff_delta delta;
char paths[GIT_FLEX_ARRAY];
} diff_patch_with_delta;
} patch_generated_with_delta;
static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
{
int error = 0;
git_patch *patch = &pd->patch;
git_patch_generated *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);
......@@ -469,24 +483,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_generated_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_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
if (!error)
error = diff_patch_generate(patch, (git_diff_output *)xo);
error = patch_generated_create(patch, (git_patch_generated_output *)xo);
return error;
}
static int diff_patch_from_sources(
diff_patch_with_delta *pd,
static int patch_generated_from_sources(
patch_generated_with_delta *pd,
git_xdiff_output *xo,
git_diff_file_content_src *oldsrc,
git_diff_file_content_src *newsrc,
......@@ -499,7 +513,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_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
return error;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
......@@ -507,7 +521,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)
......@@ -530,12 +544,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_generated_with_delta_alloc(
patch_generated_with_delta **out,
const char **old_path,
const char **new_path)
{
diff_patch_with_delta *pd;
patch_generated_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;
......@@ -547,7 +561,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_GENERATED_ALLOCATED;
if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len);
......@@ -575,7 +589,7 @@ static int diff_from_sources(
void *payload)
{
int error = 0;
diff_patch_with_delta pd;
patch_generated_with_delta pd;
git_xdiff_output xo;
memset(&xo, 0, sizeof(xo));
......@@ -585,9 +599,9 @@ static int diff_from_sources(
memset(&pd, 0, sizeof(pd));
error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
git_patch_free(&pd.patch);
git_patch_free(&pd.patch.base);
return error;
}
......@@ -599,13 +613,13 @@ static int patch_from_sources(
const git_diff_options *opts)
{
int error = 0;
diff_patch_with_delta *pd;
patch_generated_with_delta *pd;
git_xdiff_output xo;
assert(out);
*out = NULL;
if ((error = diff_patch_with_delta_alloc(
if ((error = patch_generated_with_delta_alloc(
&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
return error;
......@@ -613,7 +627,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_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
*out = (git_patch *)pd;
else
git_patch_free((git_patch *)pd);
......@@ -738,7 +752,7 @@ int git_patch_from_diff(
int error = 0;
git_xdiff_output xo;
git_diff_delta *delta = NULL;
git_patch *patch = NULL;
git_patch_generated *patch = NULL;
if (patch_ptr) *patch_ptr = NULL;
......@@ -760,17 +774,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_generated_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_generated_invoke_file_callback(patch, &xo.output);
if (!error)
error = diff_patch_generate(patch, &xo.output);
error = patch_generated_create(patch, &xo.output);
if (!error) {
/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
......@@ -778,237 +792,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_generated_driver(git_patch_generated *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_generated_old_data(
char **ptr, size_t *len, git_patch_generated *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_generated_new_data(
char **ptr, size_t *len, git_patch_generated *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_generated_file_cb(
const git_diff_delta *delta,
float progress,
void *payload)
......@@ -1017,7 +828,7 @@ static int diff_patch_file_cb(
return 0;
}
static int diff_patch_binary_cb(
static int patch_generated_binary_cb(
const git_diff_delta *delta,
const git_diff_binary *binary,
void *payload)
......@@ -1047,62 +858,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_generated *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_generated_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_generated *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++;
......@@ -1110,7 +921,7 @@ static int diff_patch_line_cb(
}
static void diff_output_init(
git_diff_output *out,
git_patch_generated_output *out,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
......@@ -1129,14 +940,15 @@ 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_generated_output *out, git_patch_generated *patch)
{
diff_output_init(
out,
NULL,
diff_patch_file_cb,
diff_patch_binary_cb,
diff_patch_hunk_cb,
diff_patch_line_cb,
patch_generated_file_cb,
patch_generated_binary_cb,
git_patch_hunk_cb,
patch_generated_line_cb,
patch);
}
......@@ -4,66 +4,48 @@
* 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__
#ifndef INCLUDE_patch_generate_h__
#define INCLUDE_patch_generate_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;
#include "patch.h"
enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2),
GIT_PATCH_GENERATED_ALLOCATED = (1 << 0),
GIT_PATCH_GENERATED_INITIALIZED = (1 << 1),
GIT_PATCH_GENERATED_LOADED = (1 << 2),
/* the two sides are different */
GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
GIT_PATCH_GENERATED_DIFFABLE = (1 << 3),
/* the difference between the two sides has been computed */
GIT_DIFF_PATCH_DIFFED = (1 << 4),
GIT_DIFF_PATCH_FLATTENED = (1 << 5),
GIT_PATCH_GENERATED_DIFFED = (1 << 4),
GIT_PATCH_GENERATED_FLATTENED = (1 << 5),
};
struct git_patch {
git_refcount rc;
struct git_patch_generated {
struct git_patch base;
git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
git_diff_options diff_opts;
git_diff_delta *delta;
size_t delta_index;
git_diff_file_content ofile;
git_diff_file_content nfile;
uint32_t flags;
git_diff_binary binary;
git_array_t(diff_patch_hunk) hunks;
git_array_t(git_diff_line) lines;
size_t content_size, context_size, header_size;
git_pool flattened;
};
extern git_diff *git_patch__diff(git_patch *);
typedef struct git_patch_generated git_patch_generated;
extern git_diff_driver *git_patch__driver(git_patch *);
extern git_diff_driver *git_patch_generated_driver(git_patch_generated *);
extern void git_patch__old_data(char **, size_t *, git_patch *);
extern void git_patch__new_data(char **, size_t *, git_patch *);
extern void git_patch_generated_old_data(
char **, size_t *, git_patch_generated *);
extern void git_patch_generated_new_data(
char **, size_t *, git_patch_generated *);
extern int git_patch__invoke_callbacks(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload);
typedef struct git_patch_generated_output git_patch_generated_output;
typedef struct git_diff_output git_diff_output;
struct git_diff_output {
struct git_patch_generated_output {
/* these callbacks are issued with the diff data */
git_diff_file_cb file_cb;
git_diff_binary_cb binary_cb;
......@@ -77,7 +59,8 @@ struct git_diff_output {
/* this callback is used to do the diff and drive the other callbacks.
* see diff_xdiff.h for how to use this in practice for now.
*/
int (*diff_cb)(git_diff_output *output, git_patch *patch);
int (*diff_cb)(git_patch_generated_output *output,
git_patch_generated *patch);
};
#endif
/*
* 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.
*/
#include "git2/patch.h"
#include "patch.h"
#include "patch_parse.h"
#include "path.h"
#define parse_err(...) \
( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 )
typedef struct {
git_patch base;
git_patch_parse_ctx *ctx;
/* the paths from the `diff --git` header, these will be used if this is not
* a rename (and rename paths are specified) or if no `+++`/`---` line specify
* the paths.
*/
char *header_old_path, *header_new_path;
/* renamed paths are precise and are not prefixed */
char *rename_old_path, *rename_new_path;
/* the paths given in `---` and `+++` lines */
char *old_path, *new_path;
/* the prefixes from the old/new paths */
char *old_prefix, *new_prefix;
} git_patch_parsed;
GIT_INLINE(bool) parse_ctx_contains(
git_patch_parse_ctx *ctx, const char *str, size_t len)
{
return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0);
}
#define parse_ctx_contains_s(ctx, str) \
parse_ctx_contains(ctx, str, sizeof(str) - 1)
static void parse_advance_line(git_patch_parse_ctx *ctx)
{
ctx->line += ctx->line_len;
ctx->remain_len -= ctx->line_len;
ctx->line_len = git__linenlen(ctx->line, ctx->remain_len);
ctx->line_num++;
}
static void parse_advance_chars(git_patch_parse_ctx *ctx, size_t char_cnt)
{
ctx->line += char_cnt;
ctx->remain_len -= char_cnt;
ctx->line_len -= char_cnt;
}
static int parse_advance_expected(
git_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;
}
#define parse_advance_expected_s(ctx, str) \
parse_advance_expected(ctx, str, sizeof(str) - 1)
static int parse_advance_ws(git_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_len--;
ret = 0;
}
return ret;
}
static int parse_advance_nl(git_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(git_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, git_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, git_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, git_patch_parse_ctx *ctx)
{
return parse_header_path(&patch->old_path, ctx);
}
static int parse_header_git_newpath(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
return parse_header_path(&patch->new_path, ctx);
}
static int parse_header_mode(uint16_t *mode, git_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,
int *oid_len,
git_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 = (int)len;
return 0;
}
static int parse_header_git_index(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
if (parse_header_oid(&patch->base.delta->old_file.id,
&patch->base.delta->old_file.id_abbrev, ctx) < 0 ||
parse_advance_expected_s(ctx, "..") < 0 ||
parse_header_oid(&patch->base.delta->new_file.id,
&patch->base.delta->new_file.id_abbrev, 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, git_patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
}
static int parse_header_git_newmode(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
}
static int parse_header_git_deletedfilemode(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
git__free((char *)patch->base.delta->old_file.path);
patch->base.delta->old_file.path = NULL;
patch->base.delta->status = GIT_DELTA_DELETED;
patch->base.delta->nfiles = 1;
return parse_header_mode(&patch->base.delta->old_file.mode, ctx);
}
static int parse_header_git_newfilemode(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
git__free((char *)patch->base.delta->new_file.path);
patch->base.delta->new_file.path = NULL;
patch->base.delta->status = GIT_DELTA_ADDED;
patch->base.delta->nfiles = 1;
return parse_header_mode(&patch->base.delta->new_file.mode, ctx);
}
static int parse_header_rename(
char **out,
git_patch_parse_ctx *ctx)
{
git_buf path = GIT_BUF_INIT;
if (parse_header_path_buf(&path, ctx) < 0)
return -1;
/* Note: the `rename from` and `rename to` lines include the literal
* filename. They do *not* include the prefix. (Who needs consistency?)
*/
*out = git_buf_detach(&path);
return 0;
}
static int parse_header_renamefrom(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
patch->base.delta->status = GIT_DELTA_RENAMED;
return parse_header_rename(&patch->rename_old_path, ctx);
}
static int parse_header_renameto(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
patch->base.delta->status = GIT_DELTA_RENAMED;
return parse_header_rename(&patch->rename_new_path, ctx);
}
static int parse_header_copyfrom(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
patch->base.delta->status = GIT_DELTA_COPIED;
return parse_header_rename(&patch->rename_old_path, ctx);
}
static int parse_header_copyto(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
patch->base.delta->status = GIT_DELTA_COPIED;
return parse_header_rename(&patch->rename_new_path, ctx);
}
static int parse_header_percent(uint16_t *out, git_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_s(ctx, "%") < 0)
return -1;
if (val > 100)
return -1;
*out = val;
return 0;
}
static int parse_header_similarity(
git_patch_parsed *patch, git_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, git_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 *, git_patch_parse_ctx *);
} header_git_op;
static const header_git_op header_git_ops[] = {
{ "diff --git ", NULL },
{ "@@ -", 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 },
{ "copy from ", parse_header_copyfrom },
{ "copy to ", parse_header_copyto },
{ "similarity index ", parse_header_similarity },
{ "dissimilarity index ", parse_header_dissimilarity },
};
static int parse_header_git(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
size_t i;
int error = 0;
/* Parse the diff --git line */
if (parse_advance_expected_s(ctx, "diff --git ") < 0)
return parse_err("corrupt git diff header at line %d", ctx->line_num);
if (parse_header_path(&patch->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(&patch->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_len > 0;
parse_advance_line(ctx)) {
bool found = false;
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_s(ctx, "\n");
if (ctx->line_len > 0) {
error = parse_err("trailing data at line %d", ctx->line_num);
goto done;
}
found = true;
break;
}
if (!found) {
error = parse_err("invalid patch header at line %d",
ctx->line_num);
goto done;
}
}
done:
return error;
}
static int parse_number(git_off_t *out, git_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, git_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,
git_patch_parse_ctx *ctx)
{
const char *header_start = ctx->line;
hunk->hunk.old_lines = 1;
hunk->hunk.new_lines = 1;
if (parse_advance_expected_s(ctx, "@@ -") < 0 ||
parse_int(&hunk->hunk.old_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected_s(ctx, ",") < 0 ||
parse_int(&hunk->hunk.old_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected_s(ctx, " +") < 0 ||
parse_int(&hunk->hunk.new_start, ctx) < 0)
goto fail;
if (ctx->line_len > 0 && ctx->line[0] == ',') {
if (parse_advance_expected_s(ctx, ",") < 0 ||
parse_int(&hunk->hunk.new_lines, ctx) < 0)
goto fail;
}
if (parse_advance_expected_s(ctx, " @@") < 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,
git_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_len > 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_len;
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 (parse_ctx_contains_s(ctx, "\\ ") &&
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 parse_patch_header(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
int error = 0;
for (ctx->line = ctx->remain;
ctx->remain_len > 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 (parse_ctx_contains_s(ctx, "@@ -")) {
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_len < ctx->line_len + 6)
break;
/* A proper git patch */
if (parse_ctx_contains_s(ctx, "diff --git ")) {
error = parse_header_git(patch, ctx);
goto done;
}
error = 0;
continue;
}
giterr_set(GITERR_PATCH, "no patch found");
error = GIT_ENOTFOUND;
done:
return error;
}
static int parse_patch_binary_side(
git_diff_binary_file *binary,
git_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 (parse_ctx_contains_s(ctx, "literal ")) {
type = GIT_DIFF_BINARY_LITERAL;
parse_advance_chars(ctx, 8);
} else if (parse_ctx_contains_s(ctx, "delta ")) {
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 parse_patch_binary(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
int error;
if (parse_advance_expected_s(ctx, "GIT binary patch") < 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->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 = parse_patch_binary_side(
&patch->base.binary.old_file, ctx)) < 0)
return error;
if (parse_advance_nl(ctx) < 0)
return parse_err("corrupt git binary patch separator at line %d",
ctx->line_num);
patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
return 0;
}
static int parse_patch_hunks(
git_patch_parsed *patch,
git_patch_parse_ctx *ctx)
{
git_patch_hunk *hunk;
int error = 0;
while (parse_ctx_contains_s(ctx, "@@ -")) {
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;
}
patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
done:
return error;
}
static int parse_patch_body(
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
{
if (parse_ctx_contains_s(ctx, "GIT binary patch"))
return parse_patch_binary(patch, ctx);
else
return parse_patch_hunks(patch, ctx);
}
int check_header_names(
const char *one,
const char *two,
const char *old_or_new,
bool two_null)
{
if (!one || !two)
return 0;
if (two_null && strcmp(two, "/dev/null") != 0)
return parse_err("expected %s path of '/dev/null'", old_or_new);
else if (!two_null && strcmp(one, two) != 0)
return parse_err("mismatched %s path names", old_or_new);
return 0;
}
static int check_prefix(
char **out,
size_t *out_len,
git_patch_parsed *patch,
const char *path_start)
{
const char *path = path_start;
size_t prefix_len = patch->ctx->opts.prefix_len;
size_t remain_len = prefix_len;
*out = NULL;
*out_len = 0;
if (prefix_len == 0)
goto done;
/* leading slashes do not count as part of the prefix in git apply */
while (*path == '/')
path++;
while (*path && remain_len) {
if (*path == '/')
remain_len--;
path++;
}
if (remain_len || !*path)
return parse_err(
"header filename does not contain %d path components",
prefix_len);
done:
*out_len = (path - path_start);
*out = git__strndup(path_start, *out_len);
return (out == NULL) ? -1 : 0;
}
static int check_filenames(git_patch_parsed *patch)
{
const char *prefixed_new, *prefixed_old;
size_t old_prefixlen = 0, new_prefixlen = 0;
bool added = (patch->base.delta->status == GIT_DELTA_ADDED);
bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED);
if (patch->old_path && !patch->new_path)
return parse_err("missing new path");
if (!patch->old_path && patch->new_path)
return parse_err("missing old path");
/* Ensure (non-renamed) paths match */
if (check_header_names(
patch->header_old_path, patch->old_path, "old", added) < 0 ||
check_header_names(
patch->header_new_path, patch->new_path, "new", deleted) < 0)
return -1;
prefixed_old = (!added && patch->old_path) ? patch->old_path :
patch->header_old_path;
prefixed_new = (!deleted && patch->new_path) ? patch->new_path :
patch->header_new_path;
if (check_prefix(
&patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0 ||
check_prefix(
&patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0)
return -1;
/* Prefer the rename filenames as they are unambiguous and unprefixed */
if (patch->rename_old_path)
patch->base.delta->old_file.path = patch->rename_old_path;
else
patch->base.delta->old_file.path = prefixed_old + old_prefixlen;
if (patch->rename_new_path)
patch->base.delta->new_file.path = patch->rename_new_path;
else
patch->base.delta->new_file.path = prefixed_new + new_prefixlen;
if (!patch->base.delta->old_file.path &&
!patch->base.delta->new_file.path)
return parse_err("git diff header lacks old / new paths");
return 0;
}
static int check_patch(git_patch_parsed *patch)
{
git_diff_delta *delta = patch->base.delta;
if (check_filenames(patch) < 0)
return -1;
if (delta->old_file.path &&
delta->status != GIT_DELTA_DELETED &&
!delta->new_file.mode)
delta->new_file.mode = delta->old_file.mode;
if (delta->status == GIT_DELTA_MODIFIED &&
!(delta->flags & GIT_DIFF_FLAG_BINARY) &&
delta->new_file.mode == delta->old_file.mode &&
git_array_size(patch->base.hunks) == 0)
return parse_err("patch with no hunks");
if (delta->status == GIT_DELTA_ADDED) {
memset(&delta->old_file.id, 0x0, sizeof(git_oid));
delta->old_file.id_abbrev = 0;
}
if (delta->status == GIT_DELTA_DELETED) {
memset(&delta->new_file.id, 0x0, sizeof(git_oid));
delta->new_file.id_abbrev = 0;
}
return 0;
}
git_patch_parse_ctx *git_patch_parse_ctx_init(
const char *content,
size_t content_len,
const git_patch_options *opts)
{
git_patch_parse_ctx *ctx;
git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT;
if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL)
return NULL;
if (content_len) {
if ((ctx->content = git__malloc(content_len)) == NULL)
return NULL;
memcpy((char *)ctx->content, content, content_len);
}
ctx->content_len = content_len;
ctx->remain = ctx->content;
ctx->remain_len = ctx->content_len;
if (opts)
memcpy(&ctx->opts, opts, sizeof(git_patch_options));
else
memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options));
GIT_REFCOUNT_INC(ctx);
return ctx;
}
static void patch_parse_ctx_free(git_patch_parse_ctx *ctx)
{
if (!ctx)
return;
git__free((char *)ctx->content);
git__free(ctx);
}
void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx)
{
GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free);
}
static void patch_parsed__free(git_patch *p)
{
git_patch_parsed *patch = (git_patch_parsed *)p;
if (!patch)
return;
git_patch_parse_ctx_free(patch->ctx);
git__free((char *)patch->base.binary.old_file.data);
git__free((char *)patch->base.binary.new_file.data);
git_array_clear(patch->base.hunks);
git_array_clear(patch->base.lines);
git__free(patch->base.delta);
git__free(patch->old_prefix);
git__free(patch->new_prefix);
git__free(patch->header_old_path);
git__free(patch->header_new_path);
git__free(patch->rename_old_path);
git__free(patch->rename_new_path);
git__free(patch->old_path);
git__free(patch->new_path);
git__free(patch);
}
int git_patch_parse(
git_patch **out,
git_patch_parse_ctx *ctx)
{
git_patch_parsed *patch;
size_t start, used;
int error = 0;
assert(out && ctx);
*out = NULL;
patch = git__calloc(1, sizeof(git_patch_parsed));
GITERR_CHECK_ALLOC(patch);
patch->ctx = ctx;
GIT_REFCOUNT_INC(patch->ctx);
patch->base.free_fn = patch_parsed__free;
patch->base.delta = git__calloc(1, sizeof(git_diff_delta));
GITERR_CHECK_ALLOC(patch->base.delta);
patch->base.delta->status = GIT_DELTA_MODIFIED;
patch->base.delta->nfiles = 2;
start = ctx->remain_len;
if ((error = parse_patch_header(patch, ctx)) < 0 ||
(error = parse_patch_body(patch, ctx)) < 0 ||
(error = check_patch(patch)) < 0)
goto done;
used = start - ctx->remain_len;
ctx->remain += used;
patch->base.diff_opts.old_prefix = patch->old_prefix;
patch->base.diff_opts.new_prefix = patch->new_prefix;
patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY;
GIT_REFCOUNT_INC(patch);
*out = &patch->base;
done:
if (error < 0)
patch_parsed__free(&patch->base);
return error;
}
int git_patch_from_buffer(
git_patch **out,
const char *content,
size_t content_len,
const git_patch_options *opts)
{
git_patch_parse_ctx *ctx;
int error;
ctx = git_patch_parse_ctx_init(content, content_len, opts);
GITERR_CHECK_ALLOC(ctx);
error = git_patch_parse(out, ctx);
git_patch_parse_ctx_free(ctx);
return error;
}
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_patch_parse_h__
#define INCLUDE_patch_parse_h__
typedef struct {
git_refcount rc;
/* Original content buffer */
const char *content;
size_t content_len;
git_patch_options opts;
/* The remaining (unparsed) buffer */
const char *remain;
size_t remain_len;
const char *line;
size_t line_len;
size_t line_num;
} git_patch_parse_ctx;
extern git_patch_parse_ctx *git_patch_parse_ctx_init(
const char *content,
size_t content_len,
const git_patch_options *opts);
extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx);
/**
* Create a patch for a single file from the contents of a patch buffer.
*
* @param out The patch to be created
* @param contents The contents of a patch file
* @param contents_len The length of the patch file
* @param opts The git_patch_options
* @return 0 on success, <0 on failure.
*/
extern int git_patch_from_buffer(
git_patch **out,
const char *contents,
size_t contents_len,
const git_patch_options *opts);
extern int git_patch_parse(
git_patch **out,
git_patch_parse_ctx *ctx);
#endif
......@@ -306,6 +306,25 @@ int git_path_join_unrooted(
return 0;
}
void git_path_squash_slashes(git_buf *path)
{
char *p, *q;
if (path->size == 0)
return;
for (p = path->ptr, q = path->ptr; *q; p++, q++) {
*p = *q;
while (*q == '/' && *(q+1) == '/') {
path->size--;
q++;
}
}
*p = '\0';
}
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{
char buf[GIT_PATH_MAX];
......
......@@ -244,6 +244,12 @@ extern int git_path_join_unrooted(
git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
/**
* Removes multiple occurrences of '/' in a row, squashing them into a
* single '/'.
*/
extern void git_path_squash_slashes(git_buf *path);
/**
* Clean up path, prepending base if it is not already rooted.
*/
extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
......
......@@ -23,6 +23,7 @@
#include "iterator.h"
#include "merge.h"
#include "diff.h"
#include "diff_generate.h"
static int create_error(int error, const char *msg)
{
......
......@@ -19,6 +19,7 @@
#include "git2/diff.h"
#include "diff.h"
#include "diff_generate.h"
static unsigned int index_delta2status(const git_diff_delta *head2idx)
{
......
......@@ -66,6 +66,12 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base)
{
return git__strntol64(result, nptr, (size_t)-1, endptr, base);
}
int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
{
const char *p;
int64_t n, nn;
int c, ovfl, v, neg, ndig;
......@@ -111,7 +117,7 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba
/*
* Non-empty sequence of digits
*/
for (;; p++,ndig++) {
for (; nptr_len > 0; p++,ndig++,nptr_len--) {
c = *p;
v = base;
if ('0'<=c && c<='9')
......@@ -148,11 +154,17 @@ Return:
int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base)
{
return git__strntol32(result, nptr, (size_t)-1, endptr, base);
}
int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
{
int error;
int32_t tmp_int;
int64_t tmp_long;
if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0)
if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0)
return error;
tmp_int = tmp_long & 0xFFFFFFFF;
......@@ -321,6 +333,12 @@ char *git__strsep(char **end, const char *sep)
return NULL;
}
size_t git__linenlen(const char *buffer, size_t buffer_len)
{
char *nl = memchr(buffer, '\n', buffer_len);
return nl ? (size_t)(nl - buffer) + 1 : buffer_len;
}
void git__hexdump(const char *buffer, size_t len)
{
static const size_t LINE_WIDTH = 16;
......
......@@ -263,7 +263,10 @@ GIT_INLINE(int) git__signum(int val)
}
extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base);
extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
extern void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
......@@ -290,6 +293,8 @@ GIT_INLINE(int) git__tolower(int c)
# define git__tolower(a) tolower(a)
#endif
extern size_t git__linenlen(const char *buffer, size_t buffer_len);
GIT_INLINE(const char *) git__next_line(const char *s)
{
while (*s && *s != '\n') s++;
......@@ -466,6 +471,11 @@ GIT_INLINE(bool) git__iswildcard(int c)
return (c == '*' || c == '?' || c == '[');
}
GIT_INLINE(bool) git__isxdigit(int c)
{
return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
}
/*
* Parse a string value as a boolean, just like Core Git does.
*
......
......@@ -7,6 +7,7 @@
#include "common.h"
#include "vector.h"
#include "integer.h"
/* In elements, not bytes */
#define MIN_ALLOCSIZE 8
......@@ -330,6 +331,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length)
return 0;
}
int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len)
{
size_t new_length;
assert(insert_len > 0 && idx <= v->length);
GITERR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len);
if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0)
return -1;
memmove(&v->contents[idx + insert_len], &v->contents[idx],
sizeof(void *) * (v->length - idx));
memset(&v->contents[idx], 0, sizeof(void *) * insert_len);
v->length = new_length;
return 0;
}
int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len)
{
size_t new_length = v->length - remove_len;
size_t end_idx = 0;
assert(remove_len > 0);
if (git__add_sizet_overflow(&end_idx, idx, remove_len))
assert(0);
assert(end_idx <= v->length);
if (end_idx < v->length)
memmove(&v->contents[idx], &v->contents[end_idx],
sizeof(void *) * (v->length - end_idx));
memset(&v->contents[new_length], 0, sizeof(void *) * remove_len);
v->length = new_length;
return 0;
}
int git_vector_set(void **old, git_vector *v, size_t position, void *value)
{
if (position + 1 > v->length) {
......
......@@ -93,6 +93,9 @@ void git_vector_remove_matching(
void *payload);
int git_vector_resize_to(git_vector *v, size_t new_length);
int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len);
int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len);
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
/** Check if vector is sorted */
......
......@@ -28,19 +28,30 @@ static int zstream_seterr(git_zstream *zs)
return -1;
}
int git_zstream_init(git_zstream *zstream)
int git_zstream_init(git_zstream *zstream, git_zstream_t type)
{
zstream->type = type;
if (zstream->type == GIT_ZSTREAM_INFLATE)
zstream->zerr = inflateInit(&zstream->z);
else
zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
return zstream_seterr(zstream);
}
void git_zstream_free(git_zstream *zstream)
{
if (zstream->type == GIT_ZSTREAM_INFLATE)
inflateEnd(&zstream->z);
else
deflateEnd(&zstream->z);
}
void git_zstream_reset(git_zstream *zstream)
{
if (zstream->type == GIT_ZSTREAM_INFLATE)
inflateReset(&zstream->z);
else
deflateReset(&zstream->z);
zstream->in = NULL;
zstream->in_len = 0;
......@@ -75,6 +86,11 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
int zflush = Z_FINISH;
size_t out_remain = *out_len;
if (zstream->in_len && zstream->zerr == Z_STREAM_END) {
giterr_set(GITERR_ZLIB, "zlib input had trailing garbage");
return -1;
}
while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
size_t out_queued, in_queued, out_used, in_used;
......@@ -97,6 +113,9 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
out_queued = (size_t)zstream->z.avail_out;
/* compress next chunk */
if (zstream->type == GIT_ZSTREAM_INFLATE)
zstream->zerr = inflate(&zstream->z, zflush);
else
zstream->zerr = deflate(&zstream->z, zflush);
if (zstream->zerr == Z_STREAM_ERROR)
......@@ -120,12 +139,12 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
return 0;
}
int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
static int zstream_buf(git_buf *out, const void *in, size_t in_len, git_zstream_t type)
{
git_zstream zs = GIT_ZSTREAM_INIT;
int error = 0;
if ((error = git_zstream_init(&zs)) < 0)
if ((error = git_zstream_init(&zs, type)) < 0)
return error;
if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
......@@ -154,3 +173,13 @@ done:
git_zstream_free(&zs);
return error;
}
int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
{
return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE);
}
int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len)
{
return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE);
}
......@@ -12,8 +12,14 @@
#include "common.h"
#include "buffer.h"
typedef enum {
GIT_ZSTREAM_INFLATE,
GIT_ZSTREAM_DEFLATE,
} git_zstream_t;
typedef struct {
z_stream z;
git_zstream_t type;
const char *in;
size_t in_len;
int zerr;
......@@ -21,7 +27,7 @@ typedef struct {
#define GIT_ZSTREAM_INIT {{0}}
int git_zstream_init(git_zstream *zstream);
int git_zstream_init(git_zstream *zstream, git_zstream_t type);
void git_zstream_free(git_zstream *zstream);
int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len);
......@@ -35,5 +41,6 @@ bool git_zstream_done(git_zstream *zstream);
void git_zstream_reset(git_zstream *zstream);
int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len);
int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len);
#endif /* INCLUDE_zstream_h__ */
#include "clar_libgit2.h"
#include "git2/sys/repository.h"
#include "apply.h"
#include "repository.h"
#include "buf_text.h"
#include "../patch/patch_common.h"
static git_repository *repo = NULL;
static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT;
void test_apply_fromdiff__initialize(void)
{
repo = cl_git_sandbox_init("renames");
binary_opts.flags |= GIT_DIFF_SHOW_BINARY;
}
void test_apply_fromdiff__cleanup(void)
{
cl_git_sandbox_cleanup();
}
static int apply_gitbuf(
const git_buf *old,
const char *oldname,
const git_buf *new,
const char *newname,
const char *patch_expected,
const git_diff_options *diff_opts)
{
git_patch *patch;
git_buf result = GIT_BUF_INIT;
git_buf patchbuf = GIT_BUF_INIT;
char *filename;
unsigned int mode;
int error;
cl_git_pass(git_patch_from_buffers(&patch,
old ? old->ptr : NULL, old ? old->size : 0,
oldname,
new ? new->ptr : NULL, new ? new->size : 0,
newname,
diff_opts));
if (patch_expected) {
cl_git_pass(git_patch_to_buf(&patchbuf, patch));
cl_assert_equal_s(patch_expected, patchbuf.ptr);
}
error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch);
if (error == 0 && new == NULL) {
cl_assert_equal_i(0, result.size);
cl_assert_equal_p(NULL, filename);
cl_assert_equal_i(0, mode);
}
else if (error == 0) {
cl_assert_equal_s(new->ptr, result.ptr);
cl_assert_equal_s(newname ? newname : oldname, filename);
cl_assert_equal_i(0100644, mode);
}
git__free(filename);
git_buf_free(&result);
git_buf_free(&patchbuf);
git_patch_free(patch);
return error;
}
static int apply_buf(
const char *old,
const char *oldname,
const char *new,
const char *newname,
const char *patch_expected,
const git_diff_options *diff_opts)
{
git_buf o = GIT_BUF_INIT, n = GIT_BUF_INIT,
*optr = NULL, *nptr = NULL;
if (old) {
o.ptr = (char *)old;
o.size = strlen(old);
optr = &o;
}
if (new) {
n.ptr = (char *)new;
n.size = strlen(new);
nptr = &n;
}
return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts);
}
void test_apply_fromdiff__change_middle(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_CHANGE_MIDDLE, "file.txt",
PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL));
}
void test_apply_fromdiff__change_middle_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_CHANGE_MIDDLE, "file.txt",
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__change_firstline(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_CHANGE_FIRSTLINE, "file.txt",
PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL));
}
void test_apply_fromdiff__lastline(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_CHANGE_LASTLINE, "file.txt",
PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL));
}
void test_apply_fromdiff__prepend(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND, "file.txt",
PATCH_ORIGINAL_TO_PREPEND, NULL));
}
void test_apply_fromdiff__prepend_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND, "file.txt",
PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__append(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_APPEND, "file.txt",
PATCH_ORIGINAL_TO_APPEND, NULL));
}
void test_apply_fromdiff__append_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_APPEND, "file.txt",
PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__prepend_and_append(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_APPEND, "file.txt",
PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL));
}
void test_apply_fromdiff__to_empty_file(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
"", NULL,
PATCH_ORIGINAL_TO_EMPTY_FILE, NULL));
}
void test_apply_fromdiff__from_empty_file(void)
{
cl_git_pass(apply_buf(
"", NULL,
FILE_ORIGINAL, "file.txt",
PATCH_EMPTY_FILE_TO_ORIGINAL, NULL));
}
void test_apply_fromdiff__add(void)
{
cl_git_pass(apply_buf(
NULL, NULL,
FILE_ORIGINAL, "file.txt",
PATCH_ADD_ORIGINAL, NULL));
}
void test_apply_fromdiff__delete(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
NULL, NULL,
PATCH_DELETE_ORIGINAL, NULL));
}
void test_apply_fromdiff__no_change(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_ORIGINAL, "file.txt",
"", NULL));
}
void test_apply_fromdiff__binary_add(void)
{
git_buf newfile = GIT_BUF_INIT;
newfile.ptr = FILE_BINARY_DELTA_MODIFIED;
newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN;
cl_git_pass(apply_gitbuf(
NULL, NULL,
&newfile, "binary.bin",
NULL, &binary_opts));
}
void test_apply_fromdiff__binary_no_change(void)
{
git_buf original = GIT_BUF_INIT;
original.ptr = FILE_BINARY_DELTA_ORIGINAL;
original.size = FILE_BINARY_DELTA_ORIGINAL_LEN;
cl_git_pass(apply_gitbuf(
&original, "binary.bin",
&original, "binary.bin",
"", &binary_opts));
}
void test_apply_fromdiff__binary_change_delta(void)
{
git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT;
original.ptr = FILE_BINARY_DELTA_ORIGINAL;
original.size = FILE_BINARY_DELTA_ORIGINAL_LEN;
modified.ptr = FILE_BINARY_DELTA_MODIFIED;
modified.size = FILE_BINARY_DELTA_MODIFIED_LEN;
cl_git_pass(apply_gitbuf(
&original, "binary.bin",
&modified, "binary.bin",
NULL, &binary_opts));
}
void test_apply_fromdiff__binary_change_literal(void)
{
git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT;
original.ptr = FILE_BINARY_LITERAL_ORIGINAL;
original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN;
modified.ptr = FILE_BINARY_LITERAL_MODIFIED;
modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN;
cl_git_pass(apply_gitbuf(
&original, "binary.bin",
&modified, "binary.bin",
NULL, &binary_opts));
}
void test_apply_fromdiff__binary_delete(void)
{
git_buf original = GIT_BUF_INIT;
original.ptr = FILE_BINARY_DELTA_MODIFIED;
original.size = FILE_BINARY_DELTA_MODIFIED_LEN;
cl_git_pass(apply_gitbuf(
&original, "binary.bin",
NULL, NULL,
NULL, &binary_opts));
}
#include "clar_libgit2.h"
#include "git2/sys/repository.h"
#include "apply.h"
#include "patch.h"
#include "patch_parse.h"
#include "repository.h"
#include "buf_text.h"
#include "../patch/patch_common.h"
static git_repository *repo = NULL;
void test_apply_fromfile__initialize(void)
{
repo = cl_git_sandbox_init("renames");
}
void test_apply_fromfile__cleanup(void)
{
cl_git_sandbox_cleanup();
}
static int apply_patchfile(
const char *old,
size_t old_len,
const char *new,
size_t new_len,
const char *patchfile,
const char *filename_expected,
unsigned int mode_expected)
{
git_patch *patch;
git_buf result = GIT_BUF_INIT;
git_buf patchbuf = GIT_BUF_INIT;
char *filename;
unsigned int mode;
int error;
cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL));
error = git_apply__patch(&result, &filename, &mode, old, old_len, patch);
if (error == 0) {
cl_assert_equal_i(new_len, result.size);
cl_assert(memcmp(new, result.ptr, new_len) == 0);
cl_assert_equal_s(filename_expected, filename);
cl_assert_equal_i(mode_expected, mode);
}
git__free(filename);
git_buf_free(&result);
git_buf_free(&patchbuf);
git_patch_free(patch);
return error;
}
static int validate_and_apply_patchfile(
const char *old,
size_t old_len,
const char *new,
size_t new_len,
const char *patchfile,
const git_diff_options *diff_opts,
const char *filename_expected,
unsigned int mode_expected)
{
git_patch *patch_fromdiff;
git_buf validated = GIT_BUF_INIT;
int error;
cl_git_pass(git_patch_from_buffers(&patch_fromdiff,
old, old_len, "file.txt",
new, new_len, "file.txt",
diff_opts));
cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff));
cl_assert_equal_s(patchfile, validated.ptr);
error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected);
git_buf_free(&validated);
git_patch_free(patch_fromdiff);
return error;
}
void test_apply_fromfile__change_middle(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__change_middle_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT,
&diff_opts, "file.txt", 0100644));
}
void test_apply_fromfile__change_firstline(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE),
PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__lastline(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE),
PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__change_middle_shrink(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK),
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__change_middle_shrink_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK),
PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts,
"file.txt", 0100644));
}
void test_apply_fromfile__change_middle_grow(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW),
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__change_middle_grow_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW),
PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts,
"file.txt", 0100644));
}
void test_apply_fromfile__prepend(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_PREPEND, strlen(FILE_PREPEND),
PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__prepend_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_PREPEND, strlen(FILE_PREPEND),
PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts,
"file.txt", 0100644));
}
void test_apply_fromfile__append(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_APPEND, strlen(FILE_APPEND),
PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__append_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_APPEND, strlen(FILE_APPEND),
PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts,
"file.txt", 0100644));
}
void test_apply_fromfile__prepend_and_append(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND),
PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL,
"file.txt", 0100644));
}
void test_apply_fromfile__to_empty_file(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
"", 0,
PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__from_empty_file(void)
{
cl_git_pass(validate_and_apply_patchfile(
"", 0,
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__add(void)
{
cl_git_pass(validate_and_apply_patchfile(
NULL, 0,
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__delete(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
NULL, 0,
PATCH_DELETE_ORIGINAL, NULL, NULL, 0));
}
void test_apply_fromfile__rename_exact(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
PATCH_RENAME_EXACT, "newfile.txt", 0100644));
}
void test_apply_fromfile__rename_similar(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_RENAME_SIMILAR, "newfile.txt", 0100644));
}
void test_apply_fromfile__rename_similar_quotedname(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644));
}
void test_apply_fromfile__modechange(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755));
}
void test_apply_fromfile__modechange_with_modification(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755));
}
void test_apply_fromfile__noisy(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_NOISY, "file.txt", 0100644));
}
void test_apply_fromfile__noisy_nocontext(void)
{
cl_git_pass(apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE),
PATCH_NOISY_NOCONTEXT, "file.txt", 0100644));
}
void test_apply_fromfile__fail_truncated_1(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1,
strlen(PATCH_TRUNCATED_1), NULL));
}
void test_apply_fromfile__fail_truncated_2(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2,
strlen(PATCH_TRUNCATED_2), NULL));
}
void test_apply_fromfile__fail_truncated_3(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3,
strlen(PATCH_TRUNCATED_3), NULL));
}
void test_apply_fromfile__fail_corrupt_githeader(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER,
strlen(PATCH_CORRUPT_GIT_HEADER), NULL));
}
void test_apply_fromfile__empty_context(void)
{
cl_git_pass(apply_patchfile(
FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL),
FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED),
PATCH_EMPTY_CONTEXT,
"file.txt", 0100644));
}
void test_apply_fromfile__append_no_nl(void)
{
cl_git_pass(validate_and_apply_patchfile(
FILE_ORIGINAL, strlen(FILE_ORIGINAL),
FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL),
PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644));
}
void test_apply_fromfile__fail_missing_new_file(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_NEW_FILE,
strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL));
}
void test_apply_fromfile__fail_missing_old_file(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_OLD_FILE,
strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL));
}
void test_apply_fromfile__fail_no_changes(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch,
PATCH_CORRUPT_NO_CHANGES,
strlen(PATCH_CORRUPT_NO_CHANGES), NULL));
}
void test_apply_fromfile__fail_missing_hunk_header(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_HUNK_HEADER,
strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL));
}
void test_apply_fromfile__fail_not_a_patch(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH,
strlen(PATCH_NOT_A_PATCH), NULL));
}
void test_apply_fromfile__binary_add(void)
{
cl_git_pass(apply_patchfile(
NULL, 0,
FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN,
PATCH_BINARY_ADD, "binary.bin", 0100644));
}
void test_apply_fromfile__binary_change_delta(void)
{
cl_git_pass(apply_patchfile(
FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN,
FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN,
PATCH_BINARY_DELTA, "binary.bin", 0100644));
}
void test_apply_fromfile__binary_change_literal(void)
{
cl_git_pass(apply_patchfile(
FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN,
FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN,
PATCH_BINARY_LITERAL, "binary.bin", 0100644));
}
void test_apply_fromfile__binary_delete(void)
{
cl_git_pass(apply_patchfile(
FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN,
NULL, 0,
PATCH_BINARY_DELETE, NULL, 0));
}
void test_apply_fromfile__binary_change_does_not_apply(void)
{
/* try to apply patch backwards, ensure it does not apply */
cl_git_fail(apply_patchfile(
FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN,
FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN,
PATCH_BINARY_DELTA, "binary.bin", 0100644));
}
void test_apply_fromfile__binary_change_must_be_reversible(void)
{
cl_git_fail(apply_patchfile(
FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN,
NULL, 0,
PATCH_BINARY_NOT_REVERSIBLE, NULL, 0));
}
void test_apply_fromfile__empty_file_not_allowed(void)
{
git_patch *patch;
cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL));
cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL));
}
#include "clar_libgit2.h"
#include "buffer.h"
static void expect_quote_pass(const char *expected, const char *str)
{
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_puts(&buf, str));
cl_git_pass(git_buf_quote(&buf));
cl_assert_equal_s(expected, git_buf_cstr(&buf));
cl_assert_equal_i(strlen(expected), git_buf_len(&buf));
git_buf_free(&buf);
}
void test_buf_quote__quote_succeeds(void)
{
expect_quote_pass("", "");
expect_quote_pass("foo", "foo");
expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c");
expect_quote_pass("foo bar", "foo bar");
expect_quote_pass("\"\\\"leading quote\"", "\"leading quote");
expect_quote_pass("\"slash\\\\y\"", "slash\\y");
expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar");
expect_quote_pass("\"foo\\177bar\"", "foo\177bar");
expect_quote_pass("\"foo\\001bar\"", "foo\001bar");
expect_quote_pass("\"foo\\377bar\"", "foo\377bar");
}
static void expect_unquote_pass(const char *expected, const char *quoted)
{
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_puts(&buf, quoted));
cl_git_pass(git_buf_unquote(&buf));
cl_assert_equal_s(expected, git_buf_cstr(&buf));
cl_assert_equal_i(strlen(expected), git_buf_len(&buf));
git_buf_free(&buf);
}
static void expect_unquote_fail(const char *quoted)
{
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_puts(&buf, quoted));
cl_git_fail(git_buf_unquote(&buf));
git_buf_free(&buf);
}
void test_buf_quote__unquote_succeeds(void)
{
expect_unquote_pass("", "\"\"");
expect_unquote_pass(" ", "\" \"");
expect_unquote_pass("foo", "\"foo\"");
expect_unquote_pass("foo bar", "\"foo bar\"");
expect_unquote_pass("foo\"bar", "\"foo\\\"bar\"");
expect_unquote_pass("foo\\bar", "\"foo\\\\bar\"");
expect_unquote_pass("foo\tbar", "\"foo\\tbar\"");
expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\"");
expect_unquote_pass("foo\nbar", "\"foo\\012bar\"");
expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\"");
expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\"");
expect_unquote_pass("newline: \n", "\"newline: \\012\"");
expect_unquote_pass("0xff: \377", "\"0xff: \\377\"");
}
void test_buf_quote__unquote_fails(void)
{
expect_unquote_fail("no quotes at all");
expect_unquote_fail("\"no trailing quote");
expect_unquote_fail("no leading quote\"");
expect_unquote_fail("\"invalid \\z escape char\"");
expect_unquote_fail("\"\\q invalid escape char\"");
expect_unquote_fail("\"invalid escape char \\p\"");
expect_unquote_fail("\"invalid \\1 escape char \"");
expect_unquote_fail("\"invalid \\14 escape char \"");
expect_unquote_fail("\"invalid \\280 escape char\"");
expect_unquote_fail("\"invalid \\378 escape char\"");
expect_unquote_fail("\"invalid \\380 escape char\"");
expect_unquote_fail("\"invalid \\411 escape char\"");
expect_unquote_fail("\"truncated escape char \\\"");
expect_unquote_fail("\"truncated escape char \\0\"");
expect_unquote_fail("\"truncated escape char \\01\"");
}
......@@ -813,6 +813,44 @@ void test_core_buffer__encode_base85(void)
git_buf_free(&buf);
}
void test_core_buffer__decode_base85(void)
{
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_buf_decode_base85(&buf, "bZBXF", 5, 4));
cl_assert_equal_sz(4, buf.size);
cl_assert_equal_s("this", buf.ptr);
git_buf_clear(&buf);
cl_git_pass(git_buf_decode_base85(&buf, "ba!tca&BaE", 10, 8));
cl_assert_equal_sz(8, buf.size);
cl_assert_equal_s("two rnds", buf.ptr);
git_buf_clear(&buf);
cl_git_pass(git_buf_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23));
cl_assert_equal_sz(23, buf.size);
cl_assert_equal_s("this is base 85 encoded", buf.ptr);
git_buf_clear(&buf);
git_buf_free(&buf);
}
void test_core_buffer__decode_base85_fails_gracefully(void)
{
git_buf buf = GIT_BUF_INIT;
git_buf_puts(&buf, "foobar");
cl_git_fail(git_buf_decode_base85(&buf, "invalid charsZZ", 15, 42));
cl_git_fail(git_buf_decode_base85(&buf, "invalidchars__ ", 15, 42));
cl_git_fail(git_buf_decode_base85(&buf, "overflowZZ~~~~~", 15, 42));
cl_git_fail(git_buf_decode_base85(&buf, "truncated", 9, 42));
cl_assert_equal_sz(6, buf.size);
cl_assert_equal_s("foobar", buf.ptr);
git_buf_free(&buf);
}
void test_core_buffer__classify_with_utf8(void)
{
char *data0 = "Simple text\n";
......
......@@ -274,3 +274,105 @@ void test_core_vector__remove_matching(void)
git_vector_free(&x);
}
static void assert_vector(git_vector *x, void *expected[], size_t len)
{
size_t i;
cl_assert_equal_i(len, x->length);
for (i = 0; i < len; i++)
cl_assert(expected[i] == x->contents[i]);
}
void test_core_vector__grow_and_shrink(void)
{
git_vector x = GIT_VECTOR_INIT;
void *expected1[] = {
(void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05,
(void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09,
(void *)0x0a
};
void *expected2[] = {
(void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
(void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a
};
void *expected3[] = {
(void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
(void *)0x0a
};
void *expected4[] = {
(void *)0x02, (void *)0x04, (void *)0x05
};
void *expected5[] = {
(void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
(void *)0x05
};
void *expected6[] = {
(void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
(void *)0x05, (void *)0x00
};
void *expected7[] = {
(void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
(void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05,
(void *)0x00
};
void *expected8[] = {
(void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00,
(void *)0x05, (void *)0x00
};
void *expected9[] = {
(void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00
};
void *expectedA[] = { (void *)0x04, (void *)0x00 };
void *expectedB[] = { (void *)0x04 };
git_vector_insert(&x, (void *)0x01);
git_vector_insert(&x, (void *)0x02);
git_vector_insert(&x, (void *)0x03);
git_vector_insert(&x, (void *)0x04);
git_vector_insert(&x, (void *)0x05);
git_vector_insert(&x, (void *)0x06);
git_vector_insert(&x, (void *)0x07);
git_vector_insert(&x, (void *)0x08);
git_vector_insert(&x, (void *)0x09);
git_vector_insert(&x, (void *)0x0a);
git_vector_remove_range(&x, 0, 1);
assert_vector(&x, expected1, ARRAY_SIZE(expected1));
git_vector_remove_range(&x, 1, 1);
assert_vector(&x, expected2, ARRAY_SIZE(expected2));
git_vector_remove_range(&x, 4, 3);
assert_vector(&x, expected3, ARRAY_SIZE(expected3));
git_vector_remove_range(&x, 3, 2);
assert_vector(&x, expected4, ARRAY_SIZE(expected4));
git_vector_insert_null(&x, 0, 2);
assert_vector(&x, expected5, ARRAY_SIZE(expected5));
git_vector_insert_null(&x, 5, 1);
assert_vector(&x, expected6, ARRAY_SIZE(expected6));
git_vector_insert_null(&x, 4, 3);
assert_vector(&x, expected7, ARRAY_SIZE(expected7));
git_vector_remove_range(&x, 0, 3);
assert_vector(&x, expected8, ARRAY_SIZE(expected8));
git_vector_remove_range(&x, 1, 2);
assert_vector(&x, expected9, ARRAY_SIZE(expected9));
git_vector_remove_range(&x, 2, 2);
assert_vector(&x, expectedA, ARRAY_SIZE(expectedA));
git_vector_remove_range(&x, 1, 1);
assert_vector(&x, expectedB, ARRAY_SIZE(expectedB));
git_vector_remove_range(&x, 0, 1);
assert_vector(&x, NULL, 0);
git_vector_free(&x);
}
......@@ -48,7 +48,7 @@ void test_core_zstream__basic(void)
char out[128];
size_t outlen = sizeof(out);
cl_git_pass(git_zstream_init(&z));
cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE));
cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1));
cl_git_pass(git_zstream_get_output(out, &outlen, &z));
cl_assert(git_zstream_done(&z));
......@@ -58,6 +58,25 @@ void test_core_zstream__basic(void)
assert_zlib_equal(data, strlen(data) + 1, out, outlen);
}
void test_core_zstream__fails_on_trailing_garbage(void)
{
git_buf deflated = GIT_BUF_INIT, inflated = GIT_BUF_INIT;
size_t i = 0;
/* compress a simple string */
git_zstream_deflatebuf(&deflated, "foobar!!", 8);
/* append some garbage */
for (i = 0; i < 10; i++) {
git_buf_putc(&deflated, i);
}
cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size));
git_buf_free(&deflated);
git_buf_free(&inflated);
}
void test_core_zstream__buffer(void)
{
git_buf out = GIT_BUF_INIT;
......@@ -68,9 +87,10 @@ void test_core_zstream__buffer(void)
#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long"
static void compress_input_various_ways(git_buf *input)
static void compress_and_decompress_input_various_ways(git_buf *input)
{
git_buf out1 = GIT_BUF_INIT, out2 = GIT_BUF_INIT;
git_buf inflated = GIT_BUF_INIT;
size_t i, fixed_size = max(input->size / 2, 256);
char *fixed = git__malloc(fixed_size);
cl_assert(fixed);
......@@ -93,7 +113,7 @@ static void compress_input_various_ways(git_buf *input)
}
cl_assert(use_fixed_size <= fixed_size);
cl_git_pass(git_zstream_init(&zs));
cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE));
cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size));
while (!git_zstream_done(&zs)) {
......@@ -112,7 +132,12 @@ static void compress_input_various_ways(git_buf *input)
git_buf_free(&out2);
}
cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size));
cl_assert_equal_i(input->size, inflated.size);
cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0);
git_buf_free(&out1);
git_buf_free(&inflated);
git__free(fixed);
}
......@@ -129,14 +154,14 @@ void test_core_zstream__big_data(void)
cl_git_pass(
git_buf_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART)));
compress_input_various_ways(&in);
compress_and_decompress_input_various_ways(&in);
/* make a big string that's hard to compress */
srand(0xabad1dea);
for (scan = 0; scan < in.size; ++scan)
in.ptr[scan] = (char)rand();
compress_input_various_ways(&in);
compress_and_decompress_input_various_ways(&in);
}
git_buf_free(&in);
......
......@@ -241,3 +241,76 @@ void diff_print_raw(FILE *fp, git_diff *diff)
git_diff_print(diff, GIT_DIFF_FORMAT_RAW,
git_diff_print_callback__to_file_handle, fp ? fp : stderr));
}
static size_t num_modified_deltas(git_diff *diff)
{
const git_diff_delta *delta;
size_t i, cnt = 0;
for (i = 0; i < git_diff_num_deltas(diff); i++) {
delta = git_diff_get_delta(diff, i);
if (delta->status != GIT_DELTA_UNMODIFIED)
cnt++;
}
return cnt;
}
void diff_assert_equal(git_diff *a, git_diff *b)
{
const git_diff_delta *ad, *bd;
size_t i, j;
assert(a && b);
cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b));
for (i = 0, j = 0;
i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) {
ad = git_diff_get_delta(a, i);
bd = git_diff_get_delta(b, j);
if (ad->status == GIT_DELTA_UNMODIFIED) {
i++;
continue;
}
if (bd->status == GIT_DELTA_UNMODIFIED) {
j++;
continue;
}
cl_assert_equal_i(ad->status, bd->status);
cl_assert_equal_i(ad->flags, bd->flags);
cl_assert_equal_i(ad->similarity, bd->similarity);
cl_assert_equal_i(ad->nfiles, bd->nfiles);
/* Don't examine the size or the flags of the deltas;
* computed deltas have sizes (parsed deltas do not) and
* computed deltas will have flags of `VALID_ID` and
* `EXISTS` (parsed deltas will not query the ODB.)
*/
/* an empty id indicates that it wasn't presented, because
* the diff was identical. (eg, pure rename, mode change only, etc)
*/
if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) {
cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev);
cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id);
cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode);
}
cl_assert_equal_s(ad->old_file.path, bd->old_file.path);
if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) {
cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id);
cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev);
cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode);
}
cl_assert_equal_s(ad->new_file.path, bd->new_file.path);
i++;
j++;
}
}
......@@ -68,3 +68,6 @@ extern int diff_foreach_via_iterator(
extern void diff_print(FILE *fp, git_diff *diff);
extern void diff_print_raw(FILE *fp, git_diff *diff);
extern void diff_assert_equal(git_diff *a, git_diff *b);
......@@ -4,6 +4,7 @@
#include "buffer.h"
#include "commit.h"
#include "diff.h"
#include "diff_generate.h"
static git_repository *repo;
......@@ -350,9 +351,6 @@ void test_diff_format_email__mode_change(void)
"diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
"old mode 100644\n" \
"new mode 100755\n" \
"index a97157a..a97157a\n" \
"--- a/file1.txt.renamed\n" \
"+++ b/file1.txt.renamed\n" \
"--\n" \
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
......
#include "clar_libgit2.h"
#include "patch.h"
#include "patch_parse.h"
#include "diff_helpers.h"
#include "../patch/patch_common.h"
void test_diff_parse__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_diff_parse__nonpatches_fail_with_notfound(void)
{
git_diff *diff;
const char *not = PATCH_NOT_A_PATCH;
const char *not_with_leading = "Leading text.\n" PATCH_NOT_A_PATCH;
const char *not_with_trailing = PATCH_NOT_A_PATCH "Trailing text.\n";
const char *not_with_both = "Lead.\n" PATCH_NOT_A_PATCH "Trail.\n";
cl_git_fail_with(GIT_ENOTFOUND,
git_diff_from_buffer(&diff,
not,
strlen(not)));
cl_git_fail_with(GIT_ENOTFOUND,
git_diff_from_buffer(&diff,
not_with_leading,
strlen(not_with_leading)));
cl_git_fail_with(GIT_ENOTFOUND,
git_diff_from_buffer(&diff,
not_with_trailing,
strlen(not_with_trailing)));
cl_git_fail_with(GIT_ENOTFOUND,
git_diff_from_buffer(&diff,
not_with_both,
strlen(not_with_both)));
}
static void test_parse_invalid_diff(const char *invalid_diff)
{
git_diff *diff;
git_buf buf = GIT_BUF_INIT;
/* throw some random (legitimate) diffs in with the given invalid
* one.
*/
git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE);
git_buf_puts(&buf, PATCH_BINARY_DELTA);
git_buf_puts(&buf, invalid_diff);
git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_MIDDLE);
git_buf_puts(&buf, PATCH_BINARY_LITERAL);
cl_git_fail_with(GIT_ERROR,
git_diff_from_buffer(&diff, buf.ptr, buf.size));
git_buf_free(&buf);
}
void test_diff_parse__invalid_patches_fails(void)
{
test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE);
test_parse_invalid_diff(PATCH_CORRUPT_MISSING_OLD_FILE);
test_parse_invalid_diff(PATCH_CORRUPT_NO_CHANGES);
test_parse_invalid_diff(PATCH_CORRUPT_MISSING_HUNK_HEADER);
}
static void test_tree_to_tree_computed_to_parsed(
const char *sandbox, const char *a_id, const char *b_id,
uint32_t diff_flags, uint32_t find_flags)
{
git_repository *repo;
git_diff *computed, *parsed;
git_tree *a, *b;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
git_buf computed_buf = GIT_BUF_INIT;
repo = cl_git_sandbox_init(sandbox);
opts.id_abbrev = GIT_OID_HEXSZ;
opts.flags = GIT_DIFF_SHOW_BINARY | diff_flags;
findopts.flags = find_flags;
cl_assert((a = resolve_commit_oid_to_tree(repo, a_id)) != NULL);
cl_assert((b = resolve_commit_oid_to_tree(repo, b_id)) != NULL);
cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts));
if (find_flags)
cl_git_pass(git_diff_find_similar(computed, &findopts));
cl_git_pass(git_diff_to_buf(&computed_buf,
computed, GIT_DIFF_FORMAT_PATCH));
cl_git_pass(git_diff_from_buffer(&parsed,
computed_buf.ptr, computed_buf.size));
diff_assert_equal(computed, parsed);
git_tree_free(a);
git_tree_free(b);
git_diff_free(computed);
git_diff_free(parsed);
git_buf_free(&computed_buf);
cl_git_sandbox_cleanup();
}
void test_diff_parse__can_parse_generated_diff(void)
{
test_tree_to_tree_computed_to_parsed(
"diff", "d70d245e", "7a9e0b02", 0, 0);
test_tree_to_tree_computed_to_parsed(
"unsymlinked.git", "806999", "a8595c", 0, 0);
test_tree_to_tree_computed_to_parsed("diff",
"d70d245ed97ed2aa596dd1af6536e4bfdb047b69",
"7a9e0b02e63179929fed24f0a3e0f19168114d10", 0, 0);
test_tree_to_tree_computed_to_parsed(
"unsymlinked.git", "7fccd7", "806999", 0, 0);
test_tree_to_tree_computed_to_parsed(
"unsymlinked.git", "7fccd7", "a8595c", 0, 0);
test_tree_to_tree_computed_to_parsed(
"attr", "605812a", "370fe9ec22", 0, 0);
test_tree_to_tree_computed_to_parsed(
"attr", "f5b0af1fb4f5c", "370fe9ec22", 0, 0);
test_tree_to_tree_computed_to_parsed(
"diff", "d70d245e", "d70d245e", 0, 0);
test_tree_to_tree_computed_to_parsed("diff_format_email",
"873806f6f27e631eb0b23e4b56bea2bfac14a373",
"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
GIT_DIFF_SHOW_BINARY, 0);
test_tree_to_tree_computed_to_parsed("diff_format_email",
"897d3af16ca9e420cd071b1c4541bd2b91d04c8c",
"873806f6f27e631eb0b23e4b56bea2bfac14a373",
GIT_DIFF_SHOW_BINARY, 0);
test_tree_to_tree_computed_to_parsed("renames",
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
0, GIT_DIFF_FIND_RENAMES);
test_tree_to_tree_computed_to_parsed("renames",
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
GIT_DIFF_INCLUDE_UNMODIFIED,
0);
test_tree_to_tree_computed_to_parsed("renames",
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
GIT_DIFF_INCLUDE_UNMODIFIED,
GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | GIT_DIFF_FIND_EXACT_MATCH_ONLY);
}
......@@ -1702,3 +1702,49 @@ void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void
expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
}
/* test that 100% renames and copies emit the correct patch file
* git diff --find-copies-harder -M100 -B100 \
* 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
* 2bc7f351d20b53f1c72c16c4b036e491c478c49a
*/
void test_diff_rename__identical(void)
{
const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
git_tree *old_tree, *new_tree;
git_diff *diff;
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
git_buf diff_buf = GIT_BUF_INIT;
const char *expected =
"diff --git a/serving.txt b/sixserving.txt\n"
"similarity index 100%\n"
"rename from serving.txt\n"
"rename to sixserving.txt\n"
"diff --git a/sevencities.txt b/songofseven.txt\n"
"similarity index 100%\n"
"copy from sevencities.txt\n"
"copy to songofseven.txt\n";
old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
GIT_DIFF_FIND_EXACT_MATCH_ONLY;
cl_git_pass(git_diff_tree_to_tree(&diff,
g_repo, old_tree, new_tree, &diff_opts));
cl_git_pass(git_diff_find_similar(diff, &find_opts));
cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
cl_assert_equal_s(expected, diff_buf.ptr);
git_buf_free(&diff_buf);
git_diff_free(diff);
git_tree_free(old_tree);
git_tree_free(new_tree);
}
......@@ -4,6 +4,7 @@
#include "buffer.h"
#include "commit.h"
#include "diff.h"
#include "diff_generate.h"
static git_repository *_repo;
static git_diff_stats *_stats;
......
......@@ -3,6 +3,7 @@
#include "merge.h"
#include "../merge_helpers.h"
#include "diff.h"
#include "diff_tform.h"
#include "git2/sys/hashsig.h"
static git_repository *repo;
......
#include "clar_libgit2.h"
#include "patch.h"
#include "patch_parse.h"
#include "patch_common.h"
static void ensure_patch_validity(git_patch *patch)
{
const git_diff_delta *delta;
char idstr[GIT_OID_HEXSZ+1] = {0};
cl_assert((delta = git_patch_get_delta(patch)) != NULL);
cl_assert_equal_i(2, delta->nfiles);
cl_assert_equal_s(delta->old_file.path, "file.txt");
cl_assert(delta->old_file.mode == GIT_FILEMODE_BLOB);
cl_assert_equal_i(7, delta->old_file.id_abbrev);
git_oid_nfmt(idstr, delta->old_file.id_abbrev, &delta->old_file.id);
cl_assert_equal_s(idstr, "9432026");
cl_assert_equal_i(0, delta->old_file.size);
cl_assert_equal_s(delta->new_file.path, "file.txt");
cl_assert(delta->new_file.mode == GIT_FILEMODE_BLOB);
cl_assert_equal_i(7, delta->new_file.id_abbrev);
git_oid_nfmt(idstr, delta->new_file.id_abbrev, &delta->new_file.id);
cl_assert_equal_s(idstr, "cd8fd12");
cl_assert_equal_i(0, delta->new_file.size);
}
void test_patch_parse__original_to_change_middle(void)
{
git_patch *patch;
cl_git_pass(git_patch_from_buffer(
&patch, PATCH_ORIGINAL_TO_CHANGE_MIDDLE,
strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE), NULL));
ensure_patch_validity(patch);
git_patch_free(patch);
}
void test_patch_parse__leading_and_trailing_garbage(void)
{
git_patch *patch;
const char *leading = "This is some leading garbage.\n"
"Maybe it's email headers?\n"
"\n"
PATCH_ORIGINAL_TO_CHANGE_MIDDLE;
const char *trailing = PATCH_ORIGINAL_TO_CHANGE_MIDDLE
"\n"
"This is some trailing garbage.\n"
"Maybe it's an email signature?\n";
const char *both = "Here's some leading garbage\n"
PATCH_ORIGINAL_TO_CHANGE_MIDDLE
"And here's some trailing.\n";
cl_git_pass(git_patch_from_buffer(&patch, leading, strlen(leading),
NULL));
ensure_patch_validity(patch);
git_patch_free(patch);
cl_git_pass(git_patch_from_buffer(&patch, trailing, strlen(trailing),
NULL));
ensure_patch_validity(patch);
git_patch_free(patch);
cl_git_pass(git_patch_from_buffer(&patch, both, strlen(both),
NULL));
ensure_patch_validity(patch);
git_patch_free(patch);
}
void test_patch_parse__nonpatches_fail_with_notfound(void)
{
git_patch *patch;
cl_git_fail_with(GIT_ENOTFOUND,
git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH,
strlen(PATCH_NOT_A_PATCH), NULL));
}
void test_patch_parse__invalid_patches_fails(void)
{
git_patch *patch;
cl_git_fail_with(GIT_ERROR,
git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER,
strlen(PATCH_CORRUPT_GIT_HEADER), NULL));
cl_git_fail_with(GIT_ERROR,
git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_NEW_FILE,
strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL));
cl_git_fail_with(GIT_ERROR,
git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_OLD_FILE,
strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL));
cl_git_fail_with(GIT_ERROR,
git_patch_from_buffer(&patch, PATCH_CORRUPT_NO_CHANGES,
strlen(PATCH_CORRUPT_NO_CHANGES), NULL));
cl_git_fail_with(GIT_ERROR,
git_patch_from_buffer(&patch,
PATCH_CORRUPT_MISSING_HUNK_HEADER,
strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL));
}
/* The original file contents */
#define FILE_ORIGINAL \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
/* A change in the middle of the file (and the resultant patch) */
#define FILE_CHANGE_MIDDLE \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(THIS line is changed!)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -6 +6 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
/* A change of the first line (and the resultant patch) */
#define FILE_CHANGE_FIRSTLINE \
"hey, change in head!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..c81df1d 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,4 +1,4 @@\n" \
"-hey!\n" \
"+hey, change in head!\n" \
" this is some context!\n" \
" around some lines\n" \
" that will change\n"
/* A change of the last line (and the resultant patch) */
#define FILE_CHANGE_LASTLINE \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"change to the last line.\n"
#define PATCH_ORIGINAL_TO_CHANGE_LASTLINE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..f70db1c 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -6,4 +6,4 @@ yes it is!\n" \
" (this line is changed)\n" \
" and this\n" \
" is additional context\n" \
"-below it!\n" \
"+change to the last line.\n"
/* A change of the middle where we remove many lines */
#define FILE_CHANGE_MIDDLE_SHRINK \
"hey!\n" \
"i've changed a lot, but left the line\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..629cd35 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,9 +1,3 @@\n" \
" hey!\n" \
"-this is some context!\n" \
"-around some lines\n" \
"-that will change\n" \
"-yes it is!\n" \
"-(this line is changed)\n" \
"-and this\n" \
"-is additional context\n" \
"+i've changed a lot, but left the line\n" \
" below it!\n"
#define PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..629cd35 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -2,7 +2 @@ hey!\n" \
"-this is some context!\n" \
"-around some lines\n" \
"-that will change\n" \
"-yes it is!\n" \
"-(this line is changed)\n" \
"-and this\n" \
"-is additional context\n" \
"+i've changed a lot, but left the line\n"
/* A change to the middle where we grow many lines */
#define FILE_CHANGE_MIDDLE_GROW \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"this line is changed\n" \
"and this line is added\n" \
"so is this\n" \
"(this too)\n" \
"whee...\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..207ebca 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,11 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+this line is changed\n" \
"+and this line is added\n" \
"+so is this\n" \
"+(this too)\n" \
"+whee...\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..207ebca 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -6 +6,5 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+this line is changed\n" \
"+and this line is added\n" \
"+so is this\n" \
"+(this too)\n" \
"+whee...\n"
/* An insertion at the beginning of the file (and the resultant patch) */
#define FILE_PREPEND \
"insert at front\n" \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_PREPEND \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..0f39b9a 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,3 +1,4 @@\n" \
"+insert at front\n" \
" hey!\n" \
" this is some context!\n" \
" around some lines\n"
#define PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..0f39b9a 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +1 @@\n" \
"+insert at front\n"
/* An insertion at the end of the file (and the resultant patch) */
#define FILE_APPEND \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n" \
"insert at end\n"
#define PATCH_ORIGINAL_TO_APPEND \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..72788bb 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -7,3 +7,4 @@ yes it is!\n" \
" and this\n" \
" is additional context\n" \
" below it!\n" \
"+insert at end\n"
#define PATCH_ORIGINAL_TO_APPEND_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..72788bb 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -9,0 +10 @@ below it!\n" \
"+insert at end\n"
/* An insertion at the beginning and end of file (and the resultant patch) */
#define FILE_PREPEND_AND_APPEND \
"first and\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"last lines\n"
#define PATCH_ORIGINAL_TO_PREPEND_AND_APPEND \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..f282430 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,4 +1,4 @@\n" \
"-hey!\n" \
"+first and\n" \
" this is some context!\n" \
" around some lines\n" \
" that will change\n" \
"@@ -6,4 +6,4 @@ yes it is!\n" \
" (this line is changed)\n" \
" and this\n" \
" is additional context\n" \
"-below it!\n" \
"+last lines\n"
#define PATCH_ORIGINAL_TO_EMPTY_FILE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..e69de29 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,9 +0,0 @@\n" \
"-hey!\n" \
"-this is some context!\n" \
"-around some lines\n" \
"-that will change\n" \
"-yes it is!\n" \
"-(this line is changed)\n" \
"-and this\n" \
"-is additional context\n" \
"-below it!\n"
#define PATCH_EMPTY_FILE_TO_ORIGINAL \
"diff --git a/file.txt b/file.txt\n" \
"index e69de29..9432026 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +1,9 @@\n" \
"+hey!\n" \
"+this is some context!\n" \
"+around some lines\n" \
"+that will change\n" \
"+yes it is!\n" \
"+(this line is changed)\n" \
"+and this\n" \
"+is additional context\n" \
"+below it!\n"
#define PATCH_ADD_ORIGINAL \
"diff --git a/file.txt b/file.txt\n" \
"new file mode 100644\n" \
"index 0000000..9432026\n" \
"--- /dev/null\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +1,9 @@\n" \
"+hey!\n" \
"+this is some context!\n" \
"+around some lines\n" \
"+that will change\n" \
"+yes it is!\n" \
"+(this line is changed)\n" \
"+and this\n" \
"+is additional context\n" \
"+below it!\n"
#define PATCH_DELETE_ORIGINAL \
"diff --git a/file.txt b/file.txt\n" \
"deleted file mode 100644\n" \
"index 9432026..0000000\n" \
"--- a/file.txt\n" \
"+++ /dev/null\n" \
"@@ -1,9 +0,0 @@\n" \
"-hey!\n" \
"-this is some context!\n" \
"-around some lines\n" \
"-that will change\n" \
"-yes it is!\n" \
"-(this line is changed)\n" \
"-and this\n" \
"-is additional context\n" \
"-below it!\n"
#define PATCH_RENAME_EXACT \
"diff --git a/file.txt b/newfile.txt\n" \
"similarity index 100%\n" \
"rename from file.txt\n" \
"rename to newfile.txt\n"
#define PATCH_RENAME_SIMILAR \
"diff --git a/file.txt b/newfile.txt\n" \
"similarity index 77%\n" \
"rename from file.txt\n" \
"rename to newfile.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/newfile.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_RENAME_EXACT_QUOTEDNAME \
"diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \
"similarity index 100%\n" \
"rename from file.txt\n" \
"rename to \"foo\\\"bar.txt\"\n"
#define PATCH_RENAME_SIMILAR_QUOTEDNAME \
"diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \
"similarity index 77%\n" \
"rename from file.txt\n" \
"rename to \"foo\\\"bar.txt\"\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ \"b/foo\\\"bar.txt\"\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_MODECHANGE_UNCHANGED \
"diff --git a/file.txt b/file.txt\n" \
"old mode 100644\n" \
"new mode 100755\n"
#define PATCH_MODECHANGE_MODIFIED \
"diff --git a/file.txt b/file.txt\n" \
"old mode 100644\n" \
"new mode 100755\n" \
"index 9432026..cd8fd12\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_NOISY \
"This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \
"but actually isn't and should parse ok\n" \
PATCH_ORIGINAL_TO_CHANGE_MIDDLE \
"plus some trailing garbage for good measure\n"
#define PATCH_NOISY_NOCONTEXT \
"This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \
"but actually isn't and should parse ok\n" \
PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \
"plus some trailing garbage for good measure\n"
#define PATCH_TRUNCATED_1 \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n"
#define PATCH_TRUNCATED_2 \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_TRUNCATED_3 \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -3,7 +3,7 @@ this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define FILE_EMPTY_CONTEXT_ORIGINAL \
"this\nhas\nan\n\nempty\ncontext\nline\n"
#define FILE_EMPTY_CONTEXT_MODIFIED \
"this\nhas\nan\n\nempty...\ncontext\nline\n"
#define PATCH_EMPTY_CONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 398d2df..bb15234 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -2,6 +2,6 @@ this\n" \
" has\n" \
" an\n" \
"\n" \
"-empty\n" \
"+empty...\n" \
" context\n" \
" line\n"
#define FILE_APPEND_NO_NL \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n" \
"added line with no nl"
#define PATCH_APPEND_NO_NL \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..83759c0 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -7,3 +7,4 @@ yes it is!\n" \
" and this\n" \
" is additional context\n" \
" below it!\n" \
"+added line with no nl\n" \
"\\ No newline at end of file\n"
#define PATCH_CORRUPT_GIT_HEADER \
"diff --git a/file.txt\n" \
"index 9432026..0f39b9a 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +1 @@\n" \
"+insert at front\n"
#define PATCH_CORRUPT_MISSING_NEW_FILE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"@@ -6 +6 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
#define PATCH_CORRUPT_MISSING_OLD_FILE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"+++ b/file.txt\n" \
"@@ -6 +6 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
#define PATCH_CORRUPT_NO_CHANGES \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +0,0 @@ yes it is!\n"
#define PATCH_CORRUPT_MISSING_HUNK_HEADER \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..cd8fd12 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
#define PATCH_NOT_A_PATCH \
"+++this is not\n" \
"--actually even\n" \
" a legitimate \n" \
"+patch file\n" \
"-it's something else\n" \
" entirely!"
/* binary contents */
#define FILE_BINARY_LITERAL_ORIGINAL "\x00\x00\x0a"
#define FILE_BINARY_LITERAL_ORIGINAL_LEN 3
#define FILE_BINARY_LITERAL_MODIFIED "\x00\x00\x01\x02\x0a"
#define FILE_BINARY_LITERAL_MODIFIED_LEN 5
#define PATCH_BINARY_LITERAL \
"diff --git a/binary.bin b/binary.bin\n" \
"index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \
"GIT binary patch\n" \
"literal 5\n" \
"Mc${NkU}WL~000&M4gdfE\n" \
"\n" \
"literal 3\n" \
"Kc${Nk-~s>u4FC%O\n\n"
#define FILE_BINARY_DELTA_ORIGINAL \
"\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x54\x68\x69" \
"\x73\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \
"\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \
"\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \
"\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \
"\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \
"\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \
"\x6f\x66\x20\x69\x74\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \
"\x00\x01\x02\x0a\x53\x6f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \
"\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \
"\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \
"\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \
"\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \
"\x0a"
#define FILE_BINARY_DELTA_ORIGINAL_LEN 209
#define FILE_BINARY_DELTA_MODIFIED \
"\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x5a\x5a\x5a" \
"\x5a\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \
"\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \
"\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \
"\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \
"\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \
"\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \
"\x6f\x66\x20\x49\x54\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \
"\x00\x01\x02\x0a\x53\x4f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \
"\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \
"\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \
"\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \
"\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \
"\x0a"
#define FILE_BINARY_DELTA_MODIFIED_LEN 209
#define PATCH_BINARY_DELTA \
"diff --git a/binary.bin b/binary.bin\n" \
"index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \
"GIT binary patch\n" \
"delta 48\n" \
"kc$~Y)c#%<%fq{_;hPk4EV4`4>uxE%K7m7r%|HL+L0In7XGynhq\n" \
"\n" \
"delta 48\n" \
"mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n"
#define PATCH_BINARY_ADD \
"diff --git a/binary.bin b/binary.bin\n" \
"new file mode 100644\n" \
"index 0000000000000000000000000000000000000000..7c94f9e60bf366033d98e0d551ae37d30faef74a\n" \
"GIT binary patch\n" \
"literal 209\n" \
"zc${60u?oUK5JXSQe8qG&;(u6KC<u0&+$Ohh?#kUJlD{_rLCL^0!@QXgcKh&k^H>C_\n" \
"zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \
"kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n" \
"\n" \
"literal 0\n" \
"Hc$@<O00001\n\n"
#define PATCH_BINARY_DELETE \
"diff --git a/binary.bin b/binary.bin\n" \
"deleted file mode 100644\n" \
"index 7c94f9e60bf366033d98e0d551ae37d30faef74a..0000000000000000000000000000000000000000\n" \
"GIT binary patch\n" \
"literal 0\n" \
"Hc$@<O00001\n" \
"\n" \
"literal 209\n" \
"zc${60u?oUK5JXSQe8qG&;(u6KC<u0&+$Ohh?#kUJlD{_rLCL^0!@QXgcKh&k^H>C_\n" \
"zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \
"kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n\n"
/* contains an old side that does not match the expected source */
#define PATCH_BINARY_NOT_REVERSIBLE \
"diff --git a/binary.bin b/binary.bin\n" \
"index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \
"GIT binary patch\n" \
"literal 5\n" \
"Mc${NkU}WL~000&M4gdfE\n" \
"\n" \
"delta 48\n" \
"mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n"
#include "clar_libgit2.h"
#include "patch.h"
#include "patch_parse.h"
#include "patch_common.h"
/* sanity check the round-trip of patch parsing: ensure that we can parse
* and then print a variety of patch files.
*/
void patch_print_from_patchfile(const char *data, size_t len)
{
git_patch *patch;
git_buf buf = GIT_BUF_INIT;
cl_git_pass(git_patch_from_buffer(&patch, data, len, NULL));
cl_git_pass(git_patch_to_buf(&buf, patch));
cl_assert_equal_s(data, buf.ptr);
git_patch_free(patch);
git_buf_free(&buf);
}
void test_patch_print__change_middle(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE,
strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE));
}
void test_patch_print__change_middle_nocontext(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT,
strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT));
}
void test_patch_print__change_firstline(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE,
strlen(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE));
}
void test_patch_print__change_lastline(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_LASTLINE,
strlen(PATCH_ORIGINAL_TO_CHANGE_LASTLINE));
}
void test_patch_print__prepend(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND,
strlen(PATCH_ORIGINAL_TO_PREPEND));
}
void test_patch_print__prepend_nocontext(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT,
strlen(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT));
}
void test_patch_print__append(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND,
strlen(PATCH_ORIGINAL_TO_APPEND));
}
void test_patch_print__append_nocontext(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT,
strlen(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT));
}
void test_patch_print__prepend_and_append(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND,
strlen(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND));
}
void test_patch_print__to_empty_file(void)
{
patch_print_from_patchfile(PATCH_ORIGINAL_TO_EMPTY_FILE,
strlen(PATCH_ORIGINAL_TO_EMPTY_FILE));
}
void test_patch_print__from_empty_file(void)
{
patch_print_from_patchfile(PATCH_EMPTY_FILE_TO_ORIGINAL,
strlen(PATCH_EMPTY_FILE_TO_ORIGINAL));
}
void test_patch_print__add(void)
{
patch_print_from_patchfile(PATCH_ADD_ORIGINAL,
strlen(PATCH_ADD_ORIGINAL));
}
void test_patch_print__delete(void)
{
patch_print_from_patchfile(PATCH_DELETE_ORIGINAL,
strlen(PATCH_DELETE_ORIGINAL));
}
void test_patch_print__rename_exact(void)
{
patch_print_from_patchfile(PATCH_RENAME_EXACT,
strlen(PATCH_RENAME_EXACT));
}
void test_patch_print__rename_similar(void)
{
patch_print_from_patchfile(PATCH_RENAME_SIMILAR,
strlen(PATCH_RENAME_SIMILAR));
}
void test_patch_print__rename_exact_quotedname(void)
{
patch_print_from_patchfile(PATCH_RENAME_EXACT_QUOTEDNAME,
strlen(PATCH_RENAME_EXACT_QUOTEDNAME));
}
void test_patch_print__rename_similar_quotedname(void)
{
patch_print_from_patchfile(PATCH_RENAME_SIMILAR_QUOTEDNAME,
strlen(PATCH_RENAME_SIMILAR_QUOTEDNAME));
}
void test_patch_print__modechange_unchanged(void)
{
patch_print_from_patchfile(PATCH_MODECHANGE_UNCHANGED,
strlen(PATCH_MODECHANGE_UNCHANGED));
}
void test_patch_print__modechange_modified(void)
{
patch_print_from_patchfile(PATCH_MODECHANGE_MODIFIED,
strlen(PATCH_MODECHANGE_MODIFIED));
}
void test_patch_print__binary_literal(void)
{
patch_print_from_patchfile(PATCH_BINARY_LITERAL,
strlen(PATCH_BINARY_LITERAL));
}
void test_patch_print__binary_delta(void)
{
patch_print_from_patchfile(PATCH_BINARY_DELTA,
strlen(PATCH_BINARY_DELTA));
}
void test_patch_print__binary_add(void)
{
patch_print_from_patchfile(PATCH_BINARY_ADD,
strlen(PATCH_BINARY_ADD));
}
void test_patch_print__binary_delete(void)
{
patch_print_from_patchfile(PATCH_BINARY_DELETE,
strlen(PATCH_BINARY_DELETE));
}
void test_patch_print__not_reversible(void)
{
patch_print_from_patchfile(PATCH_BINARY_NOT_REVERSIBLE,
strlen(PATCH_BINARY_NOT_REVERSIBLE));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment