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 { ...@@ -264,10 +264,15 @@ typedef enum {
* link, a submodule commit id, or even a tree (although that only if you * link, a submodule commit id, or even a tree (although that only if you
* are tracking type changes or ignored/untracked directories). * 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), * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),
* then the oid will be zeroes. * 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 * `path` is the NUL-terminated path to the entry relative to the working
* directory of the repository. * directory of the repository.
* *
...@@ -280,6 +285,7 @@ typedef enum { ...@@ -280,6 +285,7 @@ typedef enum {
*/ */
typedef struct { typedef struct {
git_oid id; git_oid id;
int id_abbrev;
const char *path; const char *path;
git_off_t size; git_off_t size;
uint32_t flags; uint32_t flags;
...@@ -448,6 +454,8 @@ typedef int (*git_diff_file_cb)( ...@@ -448,6 +454,8 @@ typedef int (*git_diff_file_cb)(
float progress, float progress,
void *payload); void *payload);
#define GIT_DIFF_HUNK_HEADER_SIZE 128
/** /**
* When producing a binary diff, the binary data returned will be * When producing a binary diff, the binary data returned will be
* either the deflated full ("literal") contents of the file, or * either the deflated full ("literal") contents of the file, or
...@@ -499,12 +507,12 @@ typedef int(*git_diff_binary_cb)( ...@@ -499,12 +507,12 @@ typedef int(*git_diff_binary_cb)(
* Structure describing a hunk of a diff. * Structure describing a hunk of a diff.
*/ */
typedef struct { typedef struct {
int old_start; /**< Starting line number in old_file */ int old_start; /** Starting line number in old_file */
int old_lines; /**< Number of lines in old_file */ int old_lines; /** Number of lines in old_file */
int new_start; /**< Starting line number in new_file */ int new_start; /** Starting line number in new_file */
int new_lines; /**< Number of lines in new_file */ int new_lines; /** Number of lines in new_file */
size_t header_len; /**< Number of bytes in header text */ size_t header_len; /** Number of bytes in header text */
char header[128]; /**< Header text, NUL-byte terminated */ char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */
} git_diff_hunk; } git_diff_hunk;
/** /**
...@@ -1046,6 +1054,21 @@ GIT_EXTERN(int) git_diff_print( ...@@ -1046,6 +1054,21 @@ GIT_EXTERN(int) git_diff_print(
git_diff_line_cb print_cb, git_diff_line_cb print_cb,
void *payload); 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( ...@@ -1166,6 +1189,11 @@ GIT_EXTERN(int) git_diff_buffers(
git_diff_line_cb line_cb, git_diff_line_cb line_cb,
void *payload); 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`. * 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 * You are responsible for releasing the object memory when done, using the
......
...@@ -98,7 +98,8 @@ typedef enum { ...@@ -98,7 +98,8 @@ typedef enum {
GITERR_CHERRYPICK, GITERR_CHERRYPICK,
GITERR_DESCRIBE, GITERR_DESCRIBE,
GITERR_REBASE, GITERR_REBASE,
GITERR_FILESYSTEM GITERR_FILESYSTEM,
GITERR_PATCH,
} git_error_t; } 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: ...@@ -85,7 +85,6 @@ on_oom:
#define git_array_foreach(a, i, element) \ #define git_array_foreach(a, i, element) \
for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
GIT_INLINE(int) git_array__search( GIT_INLINE(int) git_array__search(
size_t *out, size_t *out,
void *array_ptr, void *array_ptr,
......
...@@ -273,42 +273,51 @@ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len) ...@@ -273,42 +273,51 @@ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
return 0; return 0;
} }
/* The inverse of base64_encode, offset by '+' == 43. */ /* The inverse of base64_encode */
static const int8_t base64_decode[] = { static const int8_t base64_decode[] = {
62, -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,
63, -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, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
-1, -1, -1, 0, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
-1, -1, -1, -1, -1, -1, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
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
}; };
#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) int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
{ {
size_t i; size_t i;
int8_t a, b, c, d; int8_t a, b, c, d;
size_t orig_size = buf->size, new_size; size_t orig_size = buf->size, new_size;
if (len % 4) {
giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
assert(len % 4 == 0); assert(len % 4 == 0);
GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1); GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size); ENSURE_SIZE(buf, new_size);
for (i = 0; i < len; i += 4) { for (i = 0; i < len; i += 4) {
if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 || if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
(b = BASE64_DECODE_VALUE(base64[i+1])) < 0 || (b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
(c = BASE64_DECODE_VALUE(base64[i+2])) < 0 || (c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
(d = BASE64_DECODE_VALUE(base64[i+3])) < 0) { (d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
buf->size = orig_size; buf->size = orig_size;
buf->ptr[buf->size] = '\0'; buf->ptr[buf->size] = '\0';
giterr_set(GITERR_INVALID, "Invalid base64 input"); giterr_set(GITERR_INVALID, "invalid base64 input");
return -1; return -1;
} }
...@@ -321,7 +330,7 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len) ...@@ -321,7 +330,7 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
return 0; return 0;
} }
static const char b85str[] = static const char base85_encode[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) 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) ...@@ -351,7 +360,7 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
int val = acc % 85; int val = acc % 85;
acc /= 85; acc /= 85;
b85[i] = b85str[val]; b85[i] = base85_encode[val];
} }
for (i = 0; i < 5; i++) for (i = 0; i < 5; i++)
...@@ -363,6 +372,88 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) ...@@ -363,6 +372,88 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
return 0; 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) int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{ {
size_t expected_size, new_size; size_t expected_size, new_size;
...@@ -766,3 +857,144 @@ int git_buf_splice( ...@@ -766,3 +857,144 @@ int git_buf_splice(
buf->ptr[buf->size] = '\0'; buf->ptr[buf->size] = '\0';
return 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); ...@@ -173,6 +173,12 @@ void git_buf_rtrim(git_buf *buf);
int git_buf_cmp(const git_buf *a, const git_buf *b); 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 */ /* Write data as base64 encoded in buffer */
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len); 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 */ /* 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); ...@@ -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 */ /* Write data as "base85" encoded in buffer */
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len); 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. * Insert, remove or replace a portion of the buffer.
......
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
#include "filter.h" #include "filter.h"
#include "blob.h" #include "blob.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
#include "pathspec.h" #include "pathspec.h"
#include "buf_text.h" #include "buf_text.h"
#include "diff_xdiff.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 { ...@@ -114,7 +114,7 @@ struct index_entry {
struct git_delta_index { struct git_delta_index {
unsigned long memsize; unsigned long memsize;
const void *src_buf; const void *src_buf;
unsigned long src_size; size_t src_size;
unsigned int hash_mask; unsigned int hash_mask;
struct index_entry *hash[GIT_FLEX_ARRAY]; struct index_entry *hash[GIT_FLEX_ARRAY];
}; };
...@@ -142,8 +142,8 @@ static int lookup_index_alloc( ...@@ -142,8 +142,8 @@ static int lookup_index_alloc(
return 0; return 0;
} }
struct git_delta_index * int git_delta_index_init(
git_delta_create_index(const void *buf, unsigned long bufsize) git_delta_index **out, const void *buf, size_t bufsize)
{ {
unsigned int i, hsize, hmask, entries, prev_val, *hash_count; unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
const unsigned char *data, *buffer = buf; const unsigned char *data, *buffer = buf;
...@@ -152,8 +152,10 @@ git_delta_create_index(const void *buf, unsigned long bufsize) ...@@ -152,8 +152,10 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
void *mem; void *mem;
unsigned long memsize; unsigned long memsize;
*out = NULL;
if (!buf || !bufsize) if (!buf || !bufsize)
return NULL; return 0;
/* Determine index hash size. Note that indexing skips the /* Determine index hash size. Note that indexing skips the
first byte to allow for optimizing the rabin polynomial first byte to allow for optimizing the rabin polynomial
...@@ -172,7 +174,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize) ...@@ -172,7 +174,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hmask = hsize - 1; hmask = hsize - 1;
if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0)
return NULL; return -1;
index = mem; index = mem;
mem = index->hash; mem = index->hash;
...@@ -190,7 +192,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize) ...@@ -190,7 +192,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hash_count = git__calloc(hsize, sizeof(*hash_count)); hash_count = git__calloc(hsize, sizeof(*hash_count));
if (!hash_count) { if (!hash_count) {
git__free(index); git__free(index);
return NULL; return -1;
} }
/* then populate the index */ /* then populate the index */
...@@ -243,20 +245,20 @@ git_delta_create_index(const void *buf, unsigned long bufsize) ...@@ -243,20 +245,20 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
} }
git__free(hash_count); 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); 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; return index->memsize;
else
return 0;
} }
/* /*
...@@ -265,55 +267,57 @@ unsigned long git_delta_sizeof_index(struct git_delta_index *index) ...@@ -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) #define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
void * int git_delta_create_from_index(
git_delta_create( void **out,
size_t *out_len,
const struct git_delta_index *index, const struct git_delta_index *index,
const void *trg_buf, const void *trg_buf,
unsigned long trg_size, size_t trg_size,
unsigned long *delta_size, size_t max_size)
unsigned long max_size)
{ {
unsigned int i, outpos, outsize, moff, msize, val; unsigned int i, bufpos, bufsize, moff, msize, val;
int inscnt; int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top; 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) if (!trg_buf || !trg_size)
return NULL; return 0;
outpos = 0; bufpos = 0;
outsize = 8192; bufsize = 8192;
if (max_size && outsize >= max_size) if (max_size && bufsize >= max_size)
outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
out = git__malloc(outsize); buf = git__malloc(bufsize);
if (!out) GITERR_CHECK_ALLOC(buf);
return NULL;
/* store reference buffer size */ /* store reference buffer size */
i = index->src_size; i = index->src_size;
while (i >= 0x80) { while (i >= 0x80) {
out[outpos++] = i | 0x80; buf[bufpos++] = i | 0x80;
i >>= 7; i >>= 7;
} }
out[outpos++] = i; buf[bufpos++] = i;
/* store target buffer size */ /* store target buffer size */
i = trg_size; i = trg_size;
while (i >= 0x80) { while (i >= 0x80) {
out[outpos++] = i | 0x80; buf[bufpos++] = i | 0x80;
i >>= 7; i >>= 7;
} }
out[outpos++] = i; buf[bufpos++] = i;
ref_data = index->src_buf; ref_data = index->src_buf;
ref_top = ref_data + index->src_size; ref_top = ref_data + index->src_size;
data = trg_buf; data = trg_buf;
top = (const unsigned char *) trg_buf + trg_size; top = (const unsigned char *) trg_buf + trg_size;
outpos++; bufpos++;
val = 0; val = 0;
for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
out[outpos++] = *data; buf[bufpos++] = *data;
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
} }
inscnt = i; inscnt = i;
...@@ -350,11 +354,11 @@ git_delta_create( ...@@ -350,11 +354,11 @@ git_delta_create(
if (msize < 4) { if (msize < 4) {
if (!inscnt) if (!inscnt)
outpos++; bufpos++;
out[outpos++] = *data++; buf[bufpos++] = *data++;
inscnt++; inscnt++;
if (inscnt == 0x7f) { if (inscnt == 0x7f) {
out[outpos - inscnt - 1] = inscnt; buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0; inscnt = 0;
} }
msize = 0; msize = 0;
...@@ -368,14 +372,14 @@ git_delta_create( ...@@ -368,14 +372,14 @@ git_delta_create(
msize++; msize++;
moff--; moff--;
data--; data--;
outpos--; bufpos--;
if (--inscnt) if (--inscnt)
continue; continue;
outpos--; /* remove count slot */ bufpos--; /* remove count slot */
inscnt--; /* make it -1 */ inscnt--; /* make it -1 */
break; break;
} }
out[outpos - inscnt - 1] = inscnt; buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0; inscnt = 0;
} }
...@@ -383,22 +387,22 @@ git_delta_create( ...@@ -383,22 +387,22 @@ git_delta_create(
left = (msize < 0x10000) ? 0 : (msize - 0x10000); left = (msize < 0x10000) ? 0 : (msize - 0x10000);
msize -= left; msize -= left;
op = out + outpos++; op = buf + bufpos++;
i = 0x80; i = 0x80;
if (moff & 0x000000ff) if (moff & 0x000000ff)
out[outpos++] = moff >> 0, i |= 0x01; buf[bufpos++] = moff >> 0, i |= 0x01;
if (moff & 0x0000ff00) if (moff & 0x0000ff00)
out[outpos++] = moff >> 8, i |= 0x02; buf[bufpos++] = moff >> 8, i |= 0x02;
if (moff & 0x00ff0000) if (moff & 0x00ff0000)
out[outpos++] = moff >> 16, i |= 0x04; buf[bufpos++] = moff >> 16, i |= 0x04;
if (moff & 0xff000000) if (moff & 0xff000000)
out[outpos++] = moff >> 24, i |= 0x08; buf[bufpos++] = moff >> 24, i |= 0x08;
if (msize & 0x00ff) if (msize & 0x00ff)
out[outpos++] = msize >> 0, i |= 0x10; buf[bufpos++] = msize >> 0, i |= 0x10;
if (msize & 0xff00) if (msize & 0xff00)
out[outpos++] = msize >> 8, i |= 0x20; buf[bufpos++] = msize >> 8, i |= 0x20;
*op = i; *op = i;
...@@ -415,29 +419,201 @@ git_delta_create( ...@@ -415,29 +419,201 @@ git_delta_create(
} }
} }
if (outpos >= outsize - MAX_OP_SIZE) { if (bufpos >= bufsize - MAX_OP_SIZE) {
void *tmp = out; void *tmp = buf;
outsize = outsize * 3 / 2; bufsize = bufsize * 3 / 2;
if (max_size && outsize >= max_size) if (max_size && bufsize >= max_size)
outsize = max_size + MAX_OP_SIZE + 1; bufsize = max_size + MAX_OP_SIZE + 1;
if (max_size && outpos > max_size) if (max_size && bufpos > max_size)
break; break;
out = git__realloc(out, outsize); buf = git__realloc(buf, bufsize);
if (!out) { if (!buf) {
git__free(tmp); git__free(tmp);
return NULL; return -1;
} }
} }
} }
if (inscnt) 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) { GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
git__free(out); res_dp = git__malloc(alloc_sz);
return NULL; 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; if (delta != delta_end || res_sz)
return out; 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 @@ ...@@ -6,12 +6,12 @@
#define INCLUDE_git_delta_h__ #define INCLUDE_git_delta_h__
#include "common.h" #include "common.h"
#include "pack.h"
/* opaque object for delta index */ typedef struct git_delta_index git_delta_index;
struct 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 * 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 * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
...@@ -19,22 +19,18 @@ struct git_delta_index; ...@@ -19,22 +19,18 @@ struct git_delta_index;
* before free_delta_index() is called. The returned pointer must be freed * before free_delta_index() is called. The returned pointer must be freed
* using free_delta_index(). * using free_delta_index().
*/ */
extern struct git_delta_index * extern int git_delta_index_init(
git_delta_create_index(const void *buf, unsigned long bufsize); git_delta_index **out, const void *buf, size_t bufsize);
/* /*
* free_delta_index: free the index created by create_delta_index() * Free the index created by git_delta_index_init()
*
* Given pointer must be what create_delta_index() returned, or NULL.
*/ */
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 * Returns memory usage of delta index.
*
* Given pointer must be what create_delta_index() returned, or NULL.
*/ */
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 * 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); ...@@ -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 * returned and *delta_size is updated with its size. The returned buffer
* must be freed by the caller. * 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 struct git_delta_index *index,
const void *buf, const void *buf,
unsigned long bufsize, size_t bufsize,
unsigned long *delta_size, size_t max_delta_size);
unsigned long max_delta_size);
/* /*
* diff_delta: create a delta from source buffer to target buffer * 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 * 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 * 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. * updated with its size. The returned buffer must be freed by the caller.
*/ */
GIT_INLINE(void *) git_delta( GIT_INLINE(int) git_delta(
const void *src_buf, unsigned long src_bufsize, void **out, size_t *out_len,
const void *trg_buf, unsigned long trg_bufsize, const void *src_buf, size_t src_bufsize,
unsigned long *delta_size, const void *trg_buf, size_t trg_bufsize,
unsigned long max_delta_size) 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) { if (index) {
void *delta = git_delta_create( error = git_delta_create_from_index(out, out_len,
index, trg_buf, trg_bufsize, delta_size, max_delta_size); index, trg_buf, trg_bufsize, max_delta_size);
git_delta_free_index(index);
return delta; git_delta_index_free(index);
} }
return NULL;
}
/* return error;
* 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);
/* the smallest possible delta size is 4 bytes */ /* the smallest possible delta size is 4 bytes */
#define GIT_DELTA_SIZE_MIN 4 #define GIT_DELTA_SIZE_MIN 4
/* /**
* This must be called twice on the delta data buffer, first to get the * Apply a git binary delta to recover the original content.
* expected source buffer size, and again to get the target buffer size. * 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( extern int git_delta_read_header_fromstream(
const unsigned char **datap, const unsigned char *top) size_t *base_out,
{ size_t *result_out,
const unsigned char *data = *datap; git_packfile_stream *stream);
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;
}
#endif #endif
...@@ -4,1476 +4,80 @@ ...@@ -4,1476 +4,80 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with * This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "git2/version.h"
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "fileops.h" #include "diff_generate.h"
#include "config.h" #include "patch.h"
#include "attr_file.h" #include "commit.h"
#include "filter.h"
#include "pathspec.h"
#include "index.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_IS_SET(DIFF,FLAG) \
#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) (((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 = \ #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
(VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL)) (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
static git_diff_delta *diff_delta__alloc( GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
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)
{ {
int error = 0; const char *str = delta->old_file.path;
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;
cleanup: if (!str ||
if (!error) delta->status == GIT_DELTA_ADDED ||
*diff_ptr = diff; delta->status == GIT_DELTA_RENAMED ||
else delta->status == GIT_DELTA_COPIED)
git_diff_free(diff); str = delta->new_file.path;
return error; return str;
} }
#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ const char *git_diff_delta__path(const git_diff_delta *delta)
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)
{ {
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; return diff_delta__path(delta);
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;
} }
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); const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__path(da), diff_delta__path(db));
/* reload the repository index when user did not pass one in */ return val ? val : ((int)da->status - (int)db->status);
if (!error && git_index_read(*index, false) < 0)
giterr_clear();
return error;
} }
int git_diff_tree_to_index( int git_diff_delta__casecmp(const void *a, const void *b)
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
git_index *index,
const git_diff_options *opts)
{ {
git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | const git_diff_delta *da = a, *db = b;
GIT_ITERATOR_INCLUDE_CONFLICTS; int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
bool index_ignore_case = false; return val ? val : ((int)da->status - (int)db->status);
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;
} }
int git_diff_index_to_workdir( int git_diff__entry_cmp(const void *a, const void *b)
git_diff **diff,
git_repository *repo,
git_index *index,
const git_diff_options *opts)
{ {
int error = 0; const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
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);
return error; return strcmp(entry_a->path, entry_b->path);
} }
int git_diff_tree_to_workdir( int git_diff__entry_icmp(const void *a, const void *b)
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
const git_diff_options *opts)
{ {
int error = 0; const git_index_entry *entry_a = a;
git_index *index; const git_index_entry *entry_b = b;
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
);
return error; return strcasecmp(entry_a->path, entry_b->path);
} }
int git_diff_tree_to_workdir_with_index( void git_diff_free(git_diff *diff)
git_diff **diff,
git_repository *repo,
git_tree *old_tree,
const git_diff_options *opts)
{ {
int error = 0; if (!diff)
git_diff *d1 = NULL, *d2 = NULL; return;
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;
}
*diff = d1; GIT_REFCOUNT_DEC(diff, diff->free_fn);
return error;
} }
int git_diff_index_to_index( void git_diff_addref(git_diff *diff)
git_diff **diff,
git_repository *repo,
git_index *old_index,
git_index *new_index,
const git_diff_options *opts)
{ {
int error = 0; GIT_REFCOUNT_INC(diff);
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;
} }
size_t git_diff_num_deltas(const git_diff *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) ...@@ -1516,137 +120,6 @@ int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
return 0; 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( int git_diff_format_email__append_header_tobuf(
git_buf *out, git_buf *out,
const git_oid *id, const git_oid *id,
...@@ -1664,7 +137,8 @@ int git_diff_format_email__append_header_tobuf( ...@@ -1664,7 +137,8 @@ int git_diff_format_email__append_header_tobuf(
git_oid_fmt(idstr, id); git_oid_fmt(idstr, id);
idstr[GIT_OID_HEXSZ] = '\0'; 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; return error;
error = git_buf_printf(out, error = git_buf_printf(out,
...@@ -1683,7 +157,8 @@ int git_diff_format_email__append_header_tobuf( ...@@ -1683,7 +157,8 @@ int git_diff_format_email__append_header_tobuf(
if (total_patches == 1) { if (total_patches == 1) {
error = git_buf_puts(out, "[PATCH] "); error = git_buf_puts(out, "[PATCH] ");
} else { } 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) if (error < 0)
...@@ -1741,16 +216,24 @@ int git_diff_format_email( ...@@ -1741,16 +216,24 @@ int git_diff_format_email(
assert(out && diff && opts); assert(out && diff && opts);
assert(opts->summary && opts->id && opts->author); 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) { 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; return -1;
} }
if (opts->patch_no == 0) { 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; return -1;
} }
} }
...@@ -1809,7 +292,8 @@ int git_diff_commit_as_email( ...@@ -1809,7 +292,8 @@ int git_diff_commit_as_email(
const git_diff_options *diff_opts) const git_diff_options *diff_opts)
{ {
git_diff *diff = NULL; 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; int error;
assert (out && repo && commit); assert (out && repo && commit);
...@@ -1854,3 +338,4 @@ int git_diff_format_email_init_options( ...@@ -1854,3 +338,4 @@ int git_diff_format_email_init_options(
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
return 0; return 0;
} }
...@@ -22,67 +22,30 @@ ...@@ -22,67 +22,30 @@
#define DIFF_OLD_PREFIX_DEFAULT "a/" #define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/" #define DIFF_NEW_PREFIX_DEFAULT "b/"
enum { typedef enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ GIT_DIFF_TYPE_UNKNOWN = 0,
GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ GIT_DIFF_TYPE_GENERATED = 1,
GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ GIT_DIFF_TYPE_PARSED = 2,
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ } git_diff_origin_t;
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)
struct git_diff { struct git_diff {
git_refcount rc; git_refcount rc;
git_repository *repo; git_repository *repo;
git_diff_origin_t type;
git_diff_options opts; git_diff_options opts;
git_vector pathspec;
git_vector deltas; /* vector of git_diff_delta */ git_vector deltas; /* vector of git_diff_delta */
git_pool pool; git_pool pool;
git_iterator_type_t old_src; git_iterator_type_t old_src;
git_iterator_type_t new_src; git_iterator_type_t new_src;
uint32_t diffcaps;
git_diff_perfdata perf; git_diff_perfdata perf;
bool index_updated;
int (*strcomp)(const char *, const char *); int (*strcomp)(const char *, const char *);
int (*strncomp)(const char *, const char *, size_t); int (*strncomp)(const char *, const char *, size_t);
int (*pfxcomp)(const char *str, const char *pfx); int (*pfxcomp)(const char *str, const char *pfx);
int (*entrycomp)(const void *a, const void *b); 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); void (*free_fn)(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);
extern int git_diff_delta__format_file_header( extern int git_diff_delta__format_file_header(
git_buf *out, git_buf *out,
...@@ -91,84 +54,11 @@ extern int git_diff_delta__format_file_header( ...@@ -91,84 +54,11 @@ extern int git_diff_delta__format_file_header(
const char *newpfx, const char *newpfx,
int oid_strlen); int oid_strlen);
extern int git_diff__oid_for_file( extern int git_diff_delta__cmp(const void *a, const void *b);
git_oid *out, git_diff *, const char *, uint16_t, git_off_t); extern int git_diff_delta__casecmp(const void *a, const void *b);
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;
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 #endif
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "git2/attr.h" #include "git2/attr.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "strmap.h" #include "strmap.h"
#include "map.h" #include "map.h"
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "git2/blob.h" #include "git2/blob.h"
#include "git2/submodule.h" #include "git2/submodule.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
#include "diff_file.h" #include "diff_file.h"
#include "odb.h" #include "odb.h"
#include "fileops.h" #include "fileops.h"
...@@ -149,12 +150,14 @@ int git_diff_file_content__init_from_src( ...@@ -149,12 +150,14 @@ int git_diff_file_content__init_from_src(
if (src->blob) { if (src->blob) {
fc->file->size = git_blob_rawsize(src->blob); fc->file->size = git_blob_rawsize(src->blob);
git_oid_cpy(&fc->file->id, git_blob_id(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.len = (size_t)fc->file->size;
fc->map.data = (char *)git_blob_rawcontent(src->blob); fc->map.data = (char *)git_blob_rawcontent(src->blob);
} else { } else {
fc->file->size = src->buflen; fc->file->size = src->buflen;
git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB); 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.len = src->buflen;
fc->map.data = (char *)src->buf; 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 @@ ...@@ -6,7 +6,8 @@
*/ */
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h" #include "diff_file.h"
#include "patch_generate.h"
#include "fileops.h" #include "fileops.h"
#include "zstream.h" #include "zstream.h"
#include "blob.h" #include "blob.h"
...@@ -14,19 +15,19 @@ ...@@ -14,19 +15,19 @@
#include "git2/sys/diff.h" #include "git2/sys/diff.h"
typedef struct { typedef struct {
git_diff *diff;
git_diff_format_t format; git_diff_format_t format;
git_diff_line_cb print_cb; git_diff_line_cb print_cb;
void *payload; void *payload;
git_buf *buf; git_buf *buf;
uint32_t flags;
int oid_strlen;
git_diff_line line; git_diff_line line;
unsigned int
content_loaded : 1, const char *old_prefix;
content_allocated : 1; const char *new_prefix;
git_diff_file_content *ofile; uint32_t flags;
git_diff_file_content *nfile; int id_strlen;
int (*strcomp)(const char *, const char *);
} diff_print_info; } diff_print_info;
static int diff_print_info_init__common( static int diff_print_info_init__common(
...@@ -42,17 +43,15 @@ static int diff_print_info_init__common( ...@@ -42,17 +43,15 @@ static int diff_print_info_init__common(
pi->payload = payload; pi->payload = payload;
pi->buf = out; pi->buf = out;
if (!pi->oid_strlen) { if (!pi->id_strlen) {
if (!repo) if (!repo)
pi->oid_strlen = GIT_ABBREV_DEFAULT; pi->id_strlen = GIT_ABBREV_DEFAULT;
else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0) else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0)
return -1; return -1;
} }
pi->oid_strlen += 1; /* for NUL byte */ if (pi->id_strlen > GIT_OID_HEXSZ)
pi->id_strlen = GIT_OID_HEXSZ;
if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
pi->oid_strlen = GIT_OID_HEXSZ + 1;
memset(&pi->line, 0, sizeof(pi->line)); memset(&pi->line, 0, sizeof(pi->line));
pi->line.old_lineno = -1; pi->line.old_lineno = -1;
...@@ -74,11 +73,13 @@ static int diff_print_info_init_fromdiff( ...@@ -74,11 +73,13 @@ static int diff_print_info_init_fromdiff(
memset(pi, 0, sizeof(diff_print_info)); memset(pi, 0, sizeof(diff_print_info));
pi->diff = diff;
if (diff) { if (diff) {
pi->flags = diff->opts.flags; pi->flags = diff->opts.flags;
pi->oid_strlen = diff->opts.id_abbrev; pi->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); return diff_print_info_init__common(pi, out, repo, format, cb, payload);
...@@ -92,24 +93,16 @@ static int diff_print_info_init_frompatch( ...@@ -92,24 +93,16 @@ static int diff_print_info_init_frompatch(
git_diff_line_cb cb, git_diff_line_cb cb,
void *payload) void *payload)
{ {
git_repository *repo;
assert(patch); assert(patch);
repo = patch->diff ? patch->diff->repo : NULL;
memset(pi, 0, sizeof(diff_print_info)); memset(pi, 0, sizeof(diff_print_info));
pi->diff = patch->diff;
pi->flags = patch->diff_opts.flags; pi->flags = patch->diff_opts.flags;
pi->oid_strlen = patch->diff_opts.id_abbrev; pi->id_strlen = patch->diff_opts.id_abbrev;
pi->old_prefix = patch->diff_opts.old_prefix;
pi->content_loaded = 1; pi->new_prefix = patch->diff_opts.new_prefix;
pi->ofile = &patch->ofile;
pi->nfile = &patch->nfile;
return diff_print_info_init__common(pi, out, repo, format, cb, payload); return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
} }
static char diff_pick_suffix(int mode) static char diff_pick_suffix(int mode)
...@@ -173,8 +166,8 @@ static int diff_print_one_name_status( ...@@ -173,8 +166,8 @@ static int diff_print_one_name_status(
diff_print_info *pi = data; diff_print_info *pi = data;
git_buf *out = pi->buf; git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status); char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
int (*strcomp)(const char *, const char *) = int(*strcomp)(const char *, const char *) = pi->strcomp ?
pi->diff ? pi->diff->strcomp : git__strcmp; pi->strcomp : git__strcmp;
GIT_UNUSED(progress); GIT_UNUSED(progress);
...@@ -213,6 +206,7 @@ static int diff_print_one_raw( ...@@ -213,6 +206,7 @@ static int diff_print_one_raw(
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
git_buf *out = pi->buf; git_buf *out = pi->buf;
int id_abbrev;
char code = git_diff_status_char(delta->status); char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
...@@ -223,11 +217,21 @@ static int diff_print_one_raw( ...@@ -223,11 +217,21 @@ static int diff_print_one_raw(
git_buf_clear(out); git_buf_clear(out);
git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id); id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id); 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( 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", ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
...@@ -252,53 +256,140 @@ static int diff_print_one_raw( ...@@ -252,53 +256,140 @@ static int diff_print_one_raw(
return pi->print_cb(delta, NULL, &pi->line, pi->payload); 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( 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]; char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id); if (delta->old_file.mode &&
git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id); 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) { if (delta->old_file.mode == delta->new_file.mode) {
git_buf_printf(out, "index %s..%s %o\n", git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode); start_oid, end_oid, delta->old_file.mode);
} else { } 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); 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); git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
} else { else
git_buf_printf(out, "old mode %o\n", delta->old_file.mode); diff_print_modes(out, delta);
git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
}
git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
} }
return git_buf_oom(out) ? -1 : 0; 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( static int diff_delta_format_with_paths(
git_buf *out, git_buf *out,
const git_diff_delta *delta, const git_diff_delta *delta,
const char *oldpfx, const char *template,
const char *newpfx, const char *oldpath,
const char *template) const char *newpath)
{ {
const char *oldpath = delta->old_file.path; if (git_oid_iszero(&delta->old_file.id))
const char *newpath = delta->new_file.path;
if (git_oid_iszero(&delta->old_file.id)) {
oldpfx = "";
oldpath = "/dev/null"; oldpath = "/dev/null";
}
if (git_oid_iszero(&delta->new_file.id)) { if (git_oid_iszero(&delta->new_file.id))
newpfx = "";
newpath = "/dev/null"; 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( int git_diff_delta__format_file_header(
...@@ -306,27 +397,56 @@ int git_diff_delta__format_file_header( ...@@ -306,27 +397,56 @@ int git_diff_delta__format_file_header(
const git_diff_delta *delta, const git_diff_delta *delta,
const char *oldpfx, const char *oldpfx,
const char *newpfx, 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) if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT; oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx) if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT; newpfx = DIFF_NEW_PREFIX_DEFAULT;
if (!oid_strlen) if (!id_strlen)
oid_strlen = GIT_ABBREV_DEFAULT + 1; 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_clear(out);
git_buf_printf(out, "diff --git %s%s %s%s\n", git_buf_printf(out, "diff --git %s %s\n",
oldpfx, delta->old_file.path, newpfx, delta->new_file.path); 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) if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
diff_delta_format_with_paths( diff_delta_format_with_paths(out, delta,
out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n"); "--- %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( static int format_binary(
...@@ -367,37 +487,30 @@ static int format_binary( ...@@ -367,37 +487,30 @@ static int format_binary(
return 0; return 0;
} }
static int diff_print_load_content( static int diff_print_patch_file_binary_noshow(
diff_print_info *pi, diff_print_info *pi, git_diff_delta *delta,
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; 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); pi->line.num_lines = 1;
GITERR_CHECK_ALLOC(nfile); 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( done:
ofile, pi->diff, delta, true)) < 0 || git_buf_free(&old_path);
(error = git_diff_file_content__init_from_diff( git_buf_free(&new_path);
nfile, pi->diff, delta, true)) < 0) {
git__free(ofile);
git__free(nfile);
return error; return error;
}
pi->content_loaded = 1;
pi->content_allocated = 1;
pi->ofile = ofile;
pi->nfile = nfile;
return 0;
} }
static int diff_print_patch_file_binary( static int diff_print_patch_file_binary(
...@@ -409,11 +522,11 @@ static int diff_print_patch_file_binary( ...@@ -409,11 +522,11 @@ static int diff_print_patch_file_binary(
int error; int error;
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) 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 && if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
(error = diff_print_load_content(pi, delta)) < 0) return 0;
return error;
pre_binary_size = pi->buf->size; pre_binary_size = pi->buf->size;
git_buf_printf(pi->buf, "GIT binary patch\n"); git_buf_printf(pi->buf, "GIT binary patch\n");
...@@ -427,18 +540,14 @@ static int diff_print_patch_file_binary( ...@@ -427,18 +540,14 @@ static int diff_print_patch_file_binary(
if (error == GIT_EBUFS) { if (error == GIT_EBUFS) {
giterr_clear(); giterr_clear();
git_buf_truncate(pi->buf, pre_binary_size); 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++; pi->line.num_lines++;
return error; 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( static int diff_print_patch_file(
...@@ -447,15 +556,15 @@ static int diff_print_patch_file( ...@@ -447,15 +556,15 @@ static int diff_print_patch_file(
int error; int error;
diff_print_info *pi = data; diff_print_info *pi = data;
const char *oldpfx = const char *oldpfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx = const char *newpfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
(pi->flags & GIT_DIFF_FORCE_BINARY); (pi->flags & GIT_DIFF_FORCE_BINARY);
bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
int oid_strlen = binary && show_binary ? int id_strlen = binary && show_binary ?
GIT_OID_HEXSZ + 1 : pi->oid_strlen; GIT_OID_HEXSZ : pi->id_strlen;
GIT_UNUSED(progress); GIT_UNUSED(progress);
...@@ -468,7 +577,7 @@ static int diff_print_patch_file( ...@@ -468,7 +577,7 @@ static int diff_print_patch_file(
return 0; return 0;
if ((error = git_diff_delta__format_file_header( 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; return error;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR; pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
...@@ -485,9 +594,9 @@ static int diff_print_patch_binary( ...@@ -485,9 +594,9 @@ static int diff_print_patch_binary(
{ {
diff_print_info *pi = data; diff_print_info *pi = data;
const char *old_pfx = const char *old_pfx =
pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *new_pfx = const char *new_pfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
int error; int error;
git_buf_clear(pi->buf); git_buf_clear(pi->buf);
...@@ -582,43 +691,11 @@ int git_diff_print( ...@@ -582,43 +691,11 @@ int git_diff_print(
giterr_set_after_callback_function(error, "git_diff_print"); giterr_set_after_callback_function(error, "git_diff_print");
} }
git__free(pi.nfile);
git__free(pi.ofile);
git_buf_free(&buf); git_buf_free(&buf);
return error; return error;
} }
/* print a git_patch to an output callback */
int git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf temp = GIT_BUF_INIT;
diff_print_info pi;
assert(patch && print_cb);
if (!(error = diff_print_info_init_frompatch(
&pi, &temp, patch,
GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
{
error = git_patch__invoke_callbacks(
patch, diff_print_patch_file, diff_print_patch_binary,
diff_print_patch_hunk, diff_print_patch_line, &pi);
if (error) /* make sure error message is set */
giterr_set_after_callback_function(error, "git_patch_print");
}
git_buf_free(&temp);
return error;
}
int git_diff_print_callback__to_buf( int git_diff_print_callback__to_buf(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk, const git_diff_hunk *hunk,
...@@ -659,6 +736,46 @@ int git_diff_print_callback__to_file_handle( ...@@ -659,6 +736,46 @@ int git_diff_print_callback__to_file_handle(
return 0; 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 */ /* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch) int git_patch_to_buf(git_buf *out, git_patch *patch)
{ {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
#include "common.h" #include "common.h"
#include "vector.h" #include "vector.h"
#include "diff.h" #include "diff.h"
#include "diff_patch.h" #include "patch_generate.h"
#define DIFF_RENAME_FILE_SEPARATOR " => " #define DIFF_RENAME_FILE_SEPARATOR " => "
#define STATS_FULL_MIN_SCALE 7 #define STATS_FULL_MIN_SCALE 7
...@@ -190,8 +190,9 @@ int git_diff_get_stats( ...@@ -190,8 +190,9 @@ int git_diff_get_stats(
break; break;
/* keep a count of renames because it will affect formatting */ /* keep a count of renames because it will affect formatting */
delta = git_patch_get_delta(patch); delta = patch->delta;
/* TODO ugh */
namelen = strlen(delta->new_file.path); namelen = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) { if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
namelen += strlen(delta->old_file.path); namelen += strlen(delta->old_file.path);
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "git2/sys/hashsig.h" #include "git2/sys/hashsig.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
#include "path.h" #include "path.h"
#include "fileops.h" #include "fileops.h"
#include "config.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 @@ ...@@ -8,8 +8,8 @@
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "diff_patch.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"
#include "patch_generate.h"
static int git_xdiff_scan_int(const char **str, int *value) static int git_xdiff_scan_int(const char **str, int *value)
{ {
...@@ -56,7 +56,7 @@ fail: ...@@ -56,7 +56,7 @@ fail:
typedef struct { typedef struct {
git_xdiff_output *xo; git_xdiff_output *xo;
git_patch *patch; git_patch_generated *patch;
git_diff_hunk hunk; git_diff_hunk hunk;
int old_lineno, new_lineno; int old_lineno, new_lineno;
mmfile_t xd_old_data, xd_new_data; mmfile_t xd_old_data, xd_new_data;
...@@ -110,9 +110,9 @@ static int diff_update_lines( ...@@ -110,9 +110,9 @@ static int diff_update_lines(
static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
{ {
git_xdiff_info *info = priv; git_xdiff_info *info = priv;
git_patch *patch = info->patch; git_patch_generated *patch = info->patch;
const git_diff_delta *delta = git_patch_get_delta(patch); const git_diff_delta *delta = patch->base.delta;
git_diff_output *output = &info->xo->output; git_patch_generated_output *output = &info->xo->output;
git_diff_line line; git_diff_line line;
if (len == 1) { if (len == 1) {
...@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
return output->error; return output->error;
} }
static int git_xdiff(git_diff_output *output, git_patch *patch) static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch)
{ {
git_xdiff_output *xo = (git_xdiff_output *)output; git_xdiff_output *xo = (git_xdiff_output *)output;
git_xdiff_info info; git_xdiff_info info;
...@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) ...@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
xo->callback.priv = &info; xo->callback.priv = &info;
git_diff_find_context_init( git_diff_find_context_init(
&xo->config.find_func, &findctxt, git_patch__driver(patch)); &xo->config.find_func, &findctxt, git_patch_generated_driver(patch));
xo->config.find_func_priv = &findctxt; xo->config.find_func_priv = &findctxt;
if (xo->config.find_func != NULL) if (xo->config.find_func != NULL)
...@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) ...@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
* updates are needed to xo->params.flags * updates are needed to xo->params.flags
*/ */
git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); git_patch_generated_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_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
......
...@@ -8,20 +8,20 @@ ...@@ -8,20 +8,20 @@
#define INCLUDE_diff_xdiff_h__ #define INCLUDE_diff_xdiff_h__
#include "diff.h" #include "diff.h"
#include "diff_patch.h"
#include "xdiff/xdiff.h" #include "xdiff/xdiff.h"
#include "patch_generate.h"
/* xdiff cannot cope with large files. these files should not be passed to /* xdiff cannot cope with large files. these files should not be passed to
* xdiff. callers should treat these large files as binary. * xdiff. callers should treat these large files as binary.
*/ */
#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023) #define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023)
/* A git_xdiff_output is a git_diff_output with extra fields necessary /* A git_xdiff_output is a git_patch_generate_output with extra fields
* to use libxdiff. Calling git_xdiff_init() will set the diff_cb field * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb
* of the output to use xdiff to generate the diffs. * field of the output to use xdiff to generate the diffs.
*/ */
typedef struct { typedef struct {
git_diff_output output; git_patch_generated_output output;
xdemitconf_t config; xdemitconf_t config;
xpparam_t params; xpparam_t params;
......
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
#include "iterator.h" #include "iterator.h"
#include "refs.h" #include "refs.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
#include "diff_tform.h"
#include "checkout.h" #include "checkout.h"
#include "tree.h" #include "tree.h"
#include "blob.h" #include "blob.h"
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "fileops.h" #include "fileops.h"
#include "hash.h" #include "hash.h"
#include "odb.h" #include "odb.h"
#include "delta-apply.h" #include "delta.h"
#include "filter.h" #include "filter.h"
#include "repository.h" #include "repository.h"
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include "fileops.h" #include "fileops.h"
#include "hash.h" #include "hash.h"
#include "odb.h" #include "odb.h"
#include "delta-apply.h" #include "delta.h"
#include "filebuf.h" #include "filebuf.h"
#include "git2/odb_backend.h" #include "git2/odb_backend.h"
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
#include "fileops.h" #include "fileops.h"
#include "hash.h" #include "hash.h"
#include "odb.h" #include "odb.h"
#include "delta-apply.h" #include "delta.h"
#include "sha1_lookup.h" #include "sha1_lookup.h"
#include "mwindow.h" #include "mwindow.h"
#include "pack.h" #include "pack.h"
......
...@@ -144,7 +144,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) ...@@ -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 */ pb->nr_threads = 1; /* do not spawn any thread by default */
if (git_hash_ctx_init(&pb->ctx) < 0 || 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 || git_repository_odb(&pb->odb, repo) < 0 ||
packbuilder_config(pb) < 0) packbuilder_config(pb) < 0)
goto on_error; goto on_error;
...@@ -274,6 +274,7 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po) ...@@ -274,6 +274,7 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_object *src = NULL, *trg = NULL; git_odb_object *src = NULL, *trg = NULL;
unsigned long delta_size; unsigned long delta_size;
void *delta_buf; void *delta_buf;
int error;
*out = NULL; *out = NULL;
...@@ -281,12 +282,15 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po) ...@@ -281,12 +282,15 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_read(&trg, odb, &po->id) < 0) git_odb_read(&trg, odb, &po->id) < 0)
goto on_error; goto on_error;
delta_buf = git_delta( error = git_delta(&delta_buf, &delta_size,
git_odb_object_data(src), (unsigned long)git_odb_object_size(src), git_odb_object_data(src), git_odb_object_size(src),
git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg), git_odb_object_data(trg), git_odb_object_size(trg),
&delta_size, 0); 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"); giterr_set(GITERR_INVALID, "Delta size changed");
goto on_error; goto on_error;
} }
...@@ -815,16 +819,14 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, ...@@ -815,16 +819,14 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg,
*mem_usage += sz; *mem_usage += sz;
} }
if (!src->index) { if (!src->index) {
src->index = git_delta_create_index(src->data, src_size); if (git_delta_index_init(&src->index, src->data, src_size) < 0)
if (!src->index)
return 0; /* suboptimal pack - out of memory */ 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, if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size,
&delta_size, max_size); max_size) < 0)
if (!delta_buf)
return 0; return 0;
if (trg_object->delta) { if (trg_object->delta) {
...@@ -885,9 +887,14 @@ static unsigned int check_delta_limit(git_pobject *me, unsigned int n) ...@@ -885,9 +887,14 @@ static unsigned int check_delta_limit(git_pobject *me, unsigned int n)
static unsigned long free_unpacked(struct unpacked *n) static unsigned long free_unpacked(struct unpacked *n)
{ {
unsigned long freed_mem = git_delta_sizeof_index(n->index); unsigned long freed_mem = 0;
git_delta_free_index(n->index);
if (n->index) {
freed_mem += git_delta_index_size(n->index);
git_delta_index_free(n->index);
}
n->index = NULL; n->index = NULL;
if (n->data) { if (n->data) {
freed_mem += (unsigned long)n->object->size; freed_mem += (unsigned long)n->object->size;
git__free(n->data); git__free(n->data);
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
#include "common.h" #include "common.h"
#include "odb.h" #include "odb.h"
#include "pack.h" #include "pack.h"
#include "delta-apply.h" #include "delta.h"
#include "sha1_lookup.h" #include "sha1_lookup.h"
#include "mwindow.h" #include "mwindow.h"
#include "fileops.h" #include "fileops.h"
...@@ -505,7 +505,7 @@ int git_packfile_resolve_header( ...@@ -505,7 +505,7 @@ int git_packfile_resolve_header(
git_mwindow_close(&w_curs); git_mwindow_close(&w_curs);
if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0)
return error; 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); git_packfile_stream_free(&stream);
if (error < 0) if (error < 0)
return error; return error;
...@@ -730,8 +730,9 @@ int git_packfile_unpack( ...@@ -730,8 +730,9 @@ int git_packfile_unpack(
obj->len = 0; obj->len = 0;
obj->type = GIT_OBJ_BAD; 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; obj->type = base_type;
/* /*
* We usually don't want to free the base at this * We usually don't want to free the base at this
* point, as we put it into the cache in the previous * 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 @@ ...@@ -7,49 +7,78 @@
#include "common.h" #include "common.h"
#include "git2/blob.h" #include "git2/blob.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
#include "diff_file.h" #include "diff_file.h"
#include "diff_driver.h" #include "diff_driver.h"
#include "diff_patch.h" #include "patch_generate.h"
#include "diff_xdiff.h" #include "diff_xdiff.h"
#include "delta.h" #include "delta.h"
#include "zstream.h" #include "zstream.h"
#include "fileops.h" #include "fileops.h"
static void diff_output_init( static void diff_output_init(
git_diff_output*, const git_diff_options*, git_diff_file_cb, git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
static void diff_output_to_patch(git_diff_output *, git_patch *); static void diff_output_to_patch(
git_patch_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; return;
if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
patch->delta->flags |= GIT_DIFF_FLAG_BINARY; patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
(patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
} }
static void diff_patch_init_common(git_patch *patch) static void patch_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) if (patch->diff)
git_diff_addref(patch->diff); git_diff_addref(patch->diff);
} }
static int diff_patch_normalize_options( static int patch_generated_normalize_options(
git_diff_options *out, git_diff_options *out,
const git_diff_options *opts) const git_diff_options *opts)
{ {
...@@ -75,38 +104,40 @@ static int diff_patch_normalize_options( ...@@ -75,38 +104,40 @@ static int diff_patch_normalize_options(
return 0; return 0;
} }
static int diff_patch_init_from_diff( static int patch_generated_init(
git_patch *patch, git_diff *diff, size_t delta_index) git_patch_generated *patch, git_diff *diff, size_t delta_index)
{ {
int error = 0; int error = 0;
memset(patch, 0, sizeof(*patch)); memset(patch, 0, sizeof(*patch));
patch->diff = diff; patch->diff = diff;
patch->delta = git_vector_get(&diff->deltas, delta_index); patch->base.repo = diff->repo;
patch->base.delta = git_vector_get(&diff->deltas, delta_index);
patch->delta_index = delta_index; patch->delta_index = delta_index;
if ((error = diff_patch_normalize_options( if ((error = patch_generated_normalize_options(
&patch->diff_opts, &diff->opts)) < 0 || &patch->base.diff_opts, &diff->opts)) < 0 ||
(error = git_diff_file_content__init_from_diff( (error = git_diff_file_content__init_from_diff(
&patch->ofile, diff, patch->delta, true)) < 0 || &patch->ofile, diff, patch->base.delta, true)) < 0 ||
(error = git_diff_file_content__init_from_diff( (error = git_diff_file_content__init_from_diff(
&patch->nfile, diff, patch->delta, false)) < 0) &patch->nfile, diff, patch->base.delta, false)) < 0)
return error; return error;
diff_patch_init_common(patch); patch_generated_init_common(patch);
return 0; return 0;
} }
static int diff_patch_alloc_from_diff( static int patch_generated_alloc_from_diff(
git_patch **out, git_diff *diff, size_t delta_index) git_patch_generated **out, git_diff *diff, size_t delta_index)
{ {
int error; 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); GITERR_CHECK_ALLOC(patch);
if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { if (!(error = patch_generated_init(patch, diff, delta_index))) {
patch->flags |= GIT_DIFF_PATCH_ALLOCATED; patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
GIT_REFCOUNT_INC(patch); GIT_REFCOUNT_INC(patch);
} else { } else {
git__free(patch); git__free(patch);
...@@ -117,27 +148,27 @@ static int diff_patch_alloc_from_diff( ...@@ -117,27 +148,27 @@ static int diff_patch_alloc_from_diff(
return error; return error;
} }
GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file) GIT_INLINE(bool) should_skip_binary(git_patch_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 false;
return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
} }
static bool diff_patch_diffable(git_patch *patch) static bool patch_generated_diffable(git_patch_generated *patch)
{ {
size_t olen, nlen; size_t olen, nlen;
if (patch->delta->status == GIT_DELTA_UNMODIFIED) if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
return false; return false;
/* if we've determined this to be binary (and we are not showing binary /* if we've determined this to be binary (and we are not showing binary
* data) then we have skipped loading the map data. instead, query the * data) then we have skipped loading the map data. instead, query the
* file data itself. * file data itself.
*/ */
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
(patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
olen = (size_t)patch->ofile.file->size; olen = (size_t)patch->ofile.file->size;
nlen = (size_t)patch->nfile.file->size; nlen = (size_t)patch->nfile.file->size;
} else { } else {
...@@ -154,12 +185,12 @@ static bool diff_patch_diffable(git_patch *patch) ...@@ -154,12 +185,12 @@ static bool diff_patch_diffable(git_patch *patch)
!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
} }
static int diff_patch_load(git_patch *patch, git_diff_output *output) static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
{ {
int error = 0; int error = 0;
bool incomplete_data; bool incomplete_data;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
return 0; return 0;
/* if no hunk and data callbacks and user doesn't care if data looks /* if no hunk and data callbacks and user doesn't care if data looks
...@@ -180,13 +211,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) ...@@ -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 (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( if ((error = git_diff_file_content__load(
&patch->ofile, &patch->diff_opts)) < 0 || &patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file)) should_skip_binary(patch, patch->ofile.file))
goto cleanup; goto cleanup;
} }
if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( if ((error = git_diff_file_content__load(
&patch->nfile, &patch->diff_opts)) < 0 || &patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file)) should_skip_binary(patch, patch->nfile.file))
goto cleanup; goto cleanup;
} }
...@@ -194,13 +225,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) ...@@ -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 */ /* once workdir has been tried, load other data as needed */
if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( if ((error = git_diff_file_content__load(
&patch->ofile, &patch->diff_opts)) < 0 || &patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file)) should_skip_binary(patch, patch->ofile.file))
goto cleanup; goto cleanup;
} }
if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load( if ((error = git_diff_file_content__load(
&patch->nfile, &patch->diff_opts)) < 0 || &patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file)) should_skip_binary(patch, patch->nfile.file))
goto cleanup; goto cleanup;
} }
...@@ -212,24 +243,24 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) ...@@ -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 == patch->nfile.file->mode &&
patch->ofile.file->mode != GIT_FILEMODE_COMMIT && patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
patch->delta->status = GIT_DELTA_UNMODIFIED; patch->base.delta->status = GIT_DELTA_UNMODIFIED;
cleanup: cleanup:
diff_patch_update_binary(patch); patch_generated_update_binary(patch);
if (!error) { if (!error) {
if (diff_patch_diffable(patch)) if (patch_generated_diffable(patch))
patch->flags |= GIT_DIFF_PATCH_DIFFABLE; patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
patch->flags |= GIT_DIFF_PATCH_LOADED; patch->flags |= GIT_PATCH_GENERATED_LOADED;
} }
return error; return error;
} }
static int diff_patch_invoke_file_callback( static int patch_generated_invoke_file_callback(
git_patch *patch, git_diff_output *output) git_patch_generated *patch, git_patch_generated_output *output)
{ {
float progress = patch->diff ? float progress = patch->diff ?
((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
...@@ -238,7 +269,7 @@ static int diff_patch_invoke_file_callback( ...@@ -238,7 +269,7 @@ static int diff_patch_invoke_file_callback(
return 0; return 0;
return giterr_set_after_callback_function( return giterr_set_after_callback_function(
output->file_cb(patch->delta, progress, output->payload), output->file_cb(patch->base.delta, progress, output->payload),
"git_patch"); "git_patch");
} }
...@@ -270,21 +301,25 @@ static int create_binary( ...@@ -270,21 +301,25 @@ static int create_binary(
} }
if (a_datalen && b_datalen) { if (a_datalen && b_datalen) {
void *delta_data = git_delta( void *delta_data;
a_data, (unsigned long)a_datalen,
b_data, (unsigned long)b_datalen, error = git_delta(&delta_data, &delta_data_len,
&delta_data_len, (unsigned long)deflate.size); a_data, a_datalen,
b_data, b_datalen,
deflate.size);
if (delta_data) { if (error == 0) {
error = git_zstream_deflatebuf( error = git_zstream_deflatebuf(
&delta, delta_data, (size_t)delta_data_len); &delta, delta_data, (size_t)delta_data_len);
git__free(delta_data); git__free(delta_data);
} else if (error == GIT_EBUFS) {
error = 0;
}
if (error < 0) if (error < 0)
goto done; goto done;
} }
}
if (delta.size && delta.size < deflate.size) { if (delta.size && delta.size < deflate.size) {
*out_type = GIT_DIFF_BINARY_DELTA; *out_type = GIT_DIFF_BINARY_DELTA;
...@@ -305,7 +340,7 @@ done: ...@@ -305,7 +340,7 @@ done:
return error; 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}}; git_diff_binary binary = {{0}};
const char *old_data = patch->ofile.map.data; const char *old_data = patch->ofile.map.data;
...@@ -330,7 +365,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch) ...@@ -330,7 +365,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error; return error;
error = giterr_set_after_callback_function( error = giterr_set_after_callback_function(
output->binary_cb(patch->delta, &binary, output->payload), output->binary_cb(patch->base.delta, &binary, output->payload),
"git_patch"); "git_patch");
git__free((char *) binary.old_file.data); git__free((char *) binary.old_file.data);
...@@ -339,25 +374,27 @@ static int diff_binary(git_diff_output *output, git_patch *patch) ...@@ -339,25 +374,27 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error; return error;
} }
static int diff_patch_generate(git_patch *patch, git_diff_output *output) static int patch_generated_create(
git_patch_generated *patch,
git_patch_generated_output *output)
{ {
int error = 0; int error = 0;
if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
return 0; return 0;
/* if we are not looking at the binary or text data, don't do the diff */ /* if we are not looking at the binary or text data, don't do the diff */
if (!output->binary_cb && !output->hunk_cb && !output->data_cb) if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
return 0; return 0;
if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
(error = diff_patch_load(patch, output)) < 0) (error = patch_generated_load(patch, output)) < 0)
return error; return error;
if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0) if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
return 0; return 0;
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (output->binary_cb) if (output->binary_cb)
error = diff_binary(output, patch); error = diff_binary(output, patch);
} }
...@@ -366,33 +403,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output) ...@@ -366,33 +403,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output)
error = output->diff_cb(output, patch); error = output->diff_cb(output, patch);
} }
patch->flags |= GIT_DIFF_PATCH_DIFFED; patch->flags |= GIT_PATCH_GENERATED_DIFFED;
return error; return error;
} }
static void diff_patch_free(git_patch *patch)
{
git_diff_file_content__clear(&patch->ofile);
git_diff_file_content__clear(&patch->nfile);
git_array_clear(patch->lines);
git_array_clear(patch->hunks);
git_diff_free(patch->diff); /* decrements refcount */
patch->diff = NULL;
git_pool_clear(&patch->flattened);
git__free((char *)patch->diff_opts.old_prefix);
git__free((char *)patch->diff_opts.new_prefix);
git__free((char *)patch->binary.old_file.data);
git__free((char *)patch->binary.new_file.data);
if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
git__free(patch);
}
static int diff_required(git_diff *diff, const char *action) static int diff_required(git_diff *diff, const char *action)
{ {
if (diff) if (diff)
...@@ -412,7 +426,7 @@ int git_diff_foreach( ...@@ -412,7 +426,7 @@ int git_diff_foreach(
int error = 0; int error = 0;
git_xdiff_output xo; git_xdiff_output xo;
size_t idx; size_t idx;
git_patch patch; git_patch_generated patch;
if ((error = diff_required(diff, "git_diff_foreach")) < 0) if ((error = diff_required(diff, "git_diff_foreach")) < 0)
return error; return error;
...@@ -423,24 +437,24 @@ int git_diff_foreach( ...@@ -423,24 +437,24 @@ int git_diff_foreach(
&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload); &xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts); git_xdiff_init(&xo, &diff->opts);
git_vector_foreach(&diff->deltas, idx, patch.delta) { git_vector_foreach(&diff->deltas, idx, patch.base.delta) {
/* check flags against patch status */ /* check flags against patch status */
if (git_diff_delta__should_skip(&diff->opts, patch.delta)) if (git_diff_delta__should_skip(&diff->opts, patch.base.delta))
continue; continue;
if (binary_cb || hunk_cb || data_cb) { if (binary_cb || hunk_cb || data_cb) {
if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 || if ((error = patch_generated_init(&patch, diff, idx)) != 0 ||
(error = diff_patch_load(&patch, &xo.output)) != 0) (error = patch_generated_load(&patch, &xo.output)) != 0)
return error; 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) 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) if (error)
break; break;
...@@ -450,15 +464,15 @@ int git_diff_foreach( ...@@ -450,15 +464,15 @@ int git_diff_foreach(
} }
typedef struct { typedef struct {
git_patch patch; git_patch_generated patch;
git_diff_delta delta; git_diff_delta delta;
char paths[GIT_FLEX_ARRAY]; 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; 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_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
...@@ -469,24 +483,24 @@ static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) ...@@ -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)) if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
pd->delta.status = GIT_DELTA_UNMODIFIED; pd->delta.status = GIT_DELTA_UNMODIFIED;
patch->delta = &pd->delta; patch->base.delta = &pd->delta;
diff_patch_init_common(patch); patch_generated_init_common(patch);
if (pd->delta.status == GIT_DELTA_UNMODIFIED && if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
return error; return error;
error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo); error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
if (!error) if (!error)
error = diff_patch_generate(patch, (git_diff_output *)xo); error = patch_generated_create(patch, (git_patch_generated_output *)xo);
return error; return error;
} }
static int diff_patch_from_sources( static int patch_generated_from_sources(
diff_patch_with_delta *pd, patch_generated_with_delta *pd,
git_xdiff_output *xo, git_xdiff_output *xo,
git_diff_file_content_src *oldsrc, git_diff_file_content_src *oldsrc,
git_diff_file_content_src *newsrc, git_diff_file_content_src *newsrc,
...@@ -499,7 +513,7 @@ static int diff_patch_from_sources( ...@@ -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 *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0) if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
return error; return error;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
...@@ -507,7 +521,7 @@ static int diff_patch_from_sources( ...@@ -507,7 +521,7 @@ static int diff_patch_from_sources(
tmp = ldata; ldata = rdata; rdata = tmp; tmp = ldata; ldata = rdata; rdata = tmp;
} }
pd->patch.delta = &pd->delta; pd->patch.base.delta = &pd->delta;
if (!oldsrc->as_path) { if (!oldsrc->as_path) {
if (newsrc->as_path) if (newsrc->as_path)
...@@ -530,12 +544,12 @@ static int diff_patch_from_sources( ...@@ -530,12 +544,12 @@ static int diff_patch_from_sources(
return diff_single_generate(pd, xo); return diff_single_generate(pd, xo);
} }
static int diff_patch_with_delta_alloc( static int patch_generated_with_delta_alloc(
diff_patch_with_delta **out, patch_generated_with_delta **out,
const char **old_path, const char **old_path,
const char **new_path) const char **new_path)
{ {
diff_patch_with_delta *pd; patch_generated_with_delta *pd;
size_t old_len = *old_path ? strlen(*old_path) : 0; size_t old_len = *old_path ? strlen(*old_path) : 0;
size_t new_len = *new_path ? strlen(*new_path) : 0; size_t new_len = *new_path ? strlen(*new_path) : 0;
size_t alloc_len; size_t alloc_len;
...@@ -547,7 +561,7 @@ static int diff_patch_with_delta_alloc( ...@@ -547,7 +561,7 @@ static int diff_patch_with_delta_alloc(
*out = pd = git__calloc(1, alloc_len); *out = pd = git__calloc(1, alloc_len);
GITERR_CHECK_ALLOC(pd); GITERR_CHECK_ALLOC(pd);
pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
if (*old_path) { if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len); memcpy(&pd->paths[0], *old_path, old_len);
...@@ -575,7 +589,7 @@ static int diff_from_sources( ...@@ -575,7 +589,7 @@ static int diff_from_sources(
void *payload) void *payload)
{ {
int error = 0; int error = 0;
diff_patch_with_delta pd; patch_generated_with_delta pd;
git_xdiff_output xo; git_xdiff_output xo;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
...@@ -585,9 +599,9 @@ static int diff_from_sources( ...@@ -585,9 +599,9 @@ static int diff_from_sources(
memset(&pd, 0, sizeof(pd)); memset(&pd, 0, sizeof(pd));
error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts); error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
git_patch_free(&pd.patch); git_patch_free(&pd.patch.base);
return error; return error;
} }
...@@ -599,13 +613,13 @@ static int patch_from_sources( ...@@ -599,13 +613,13 @@ static int patch_from_sources(
const git_diff_options *opts) const git_diff_options *opts)
{ {
int error = 0; int error = 0;
diff_patch_with_delta *pd; patch_generated_with_delta *pd;
git_xdiff_output xo; git_xdiff_output xo;
assert(out); assert(out);
*out = NULL; *out = NULL;
if ((error = diff_patch_with_delta_alloc( if ((error = patch_generated_with_delta_alloc(
&pd, &oldsrc->as_path, &newsrc->as_path)) < 0) &pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
return error; return error;
...@@ -613,7 +627,7 @@ static int patch_from_sources( ...@@ -613,7 +627,7 @@ static int patch_from_sources(
diff_output_to_patch(&xo.output, &pd->patch); diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts); git_xdiff_init(&xo, opts);
if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts))) if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
*out = (git_patch *)pd; *out = (git_patch *)pd;
else else
git_patch_free((git_patch *)pd); git_patch_free((git_patch *)pd);
...@@ -738,7 +752,7 @@ int git_patch_from_diff( ...@@ -738,7 +752,7 @@ int git_patch_from_diff(
int error = 0; int error = 0;
git_xdiff_output xo; git_xdiff_output xo;
git_diff_delta *delta = NULL; git_diff_delta *delta = NULL;
git_patch *patch = NULL; git_patch_generated *patch = NULL;
if (patch_ptr) *patch_ptr = NULL; if (patch_ptr) *patch_ptr = NULL;
...@@ -760,17 +774,17 @@ int git_patch_from_diff( ...@@ -760,17 +774,17 @@ int git_patch_from_diff(
(diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
return 0; return 0;
if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
return error; return error;
memset(&xo, 0, sizeof(xo)); memset(&xo, 0, sizeof(xo));
diff_output_to_patch(&xo.output, patch); diff_output_to_patch(&xo.output, patch);
git_xdiff_init(&xo, &diff->opts); git_xdiff_init(&xo, &diff->opts);
error = diff_patch_invoke_file_callback(patch, &xo.output); error = patch_generated_invoke_file_callback(patch, &xo.output);
if (!error) if (!error)
error = diff_patch_generate(patch, &xo.output); error = patch_generated_create(patch, &xo.output);
if (!error) { if (!error) {
/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
...@@ -778,237 +792,34 @@ int git_patch_from_diff( ...@@ -778,237 +792,34 @@ int git_patch_from_diff(
} }
if (error || !patch_ptr) if (error || !patch_ptr)
git_patch_free(patch); git_patch_free(&patch->base);
else else
*patch_ptr = patch; *patch_ptr = &patch->base;
return error; return error;
} }
void git_patch_free(git_patch *patch) git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
{
if (patch)
GIT_REFCOUNT_DEC(patch, diff_patch_free);
}
const git_diff_delta *git_patch_get_delta(const git_patch *patch)
{
assert(patch);
return patch->delta;
}
size_t git_patch_num_hunks(const git_patch *patch)
{
assert(patch);
return git_array_size(patch->hunks);
}
int git_patch_line_stats(
size_t *total_ctxt,
size_t *total_adds,
size_t *total_dels,
const git_patch *patch)
{
size_t totals[3], idx;
memset(totals, 0, sizeof(totals));
for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
git_diff_line *line = git_array_get(patch->lines, idx);
if (!line)
continue;
switch (line->origin) {
case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
case GIT_DIFF_LINE_DELETION: totals[2]++; break;
default:
/* diff --stat and --numstat don't count EOFNL marks because
* they will always be paired with a ADDITION or DELETION line.
*/
break;
}
}
if (total_ctxt)
*total_ctxt = totals[0];
if (total_adds)
*total_adds = totals[1];
if (total_dels)
*total_dels = totals[2];
return 0;
}
static int diff_error_outofrange(const char *thing)
{
giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
return GIT_ENOTFOUND;
}
int git_patch_get_hunk(
const git_diff_hunk **out,
size_t *lines_in_hunk,
git_patch *patch,
size_t hunk_idx)
{
diff_patch_hunk *hunk;
assert(patch);
hunk = git_array_get(patch->hunks, hunk_idx);
if (!hunk) {
if (out) *out = NULL;
if (lines_in_hunk) *lines_in_hunk = 0;
return diff_error_outofrange("hunk");
}
if (out) *out = &hunk->hunk;
if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
return 0;
}
int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
{
diff_patch_hunk *hunk;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
return diff_error_outofrange("hunk");
return (int)hunk->line_count;
}
int git_patch_get_line_in_hunk(
const git_diff_line **out,
git_patch *patch,
size_t hunk_idx,
size_t line_of_hunk)
{
diff_patch_hunk *hunk;
git_diff_line *line;
assert(patch);
if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
if (out) *out = NULL;
return diff_error_outofrange("hunk");
}
if (line_of_hunk >= hunk->line_count ||
!(line = git_array_get(
patch->lines, hunk->line_start + line_of_hunk))) {
if (out) *out = NULL;
return diff_error_outofrange("line");
}
if (out) *out = line;
return 0;
}
size_t git_patch_size(
git_patch *patch,
int include_context,
int include_hunk_headers,
int include_file_headers)
{
size_t out;
assert(patch);
out = patch->content_size;
if (!include_context)
out -= patch->context_size;
if (include_hunk_headers)
out += patch->header_size;
if (include_file_headers) {
git_buf file_header = GIT_BUF_INIT;
if (git_diff_delta__format_file_header(
&file_header, patch->delta, NULL, NULL, 0) < 0)
giterr_clear();
else
out += git_buf_len(&file_header);
git_buf_free(&file_header);
}
return out;
}
git_diff *git_patch__diff(git_patch *patch)
{
return patch->diff;
}
git_diff_driver *git_patch__driver(git_patch *patch)
{ {
/* ofile driver is representative for whole patch */ /* ofile driver is representative for whole patch */
return patch->ofile.driver; return patch->ofile.driver;
} }
void git_patch__old_data( void git_patch_generated_old_data(
char **ptr, size_t *len, git_patch *patch) char **ptr, size_t *len, git_patch_generated *patch)
{ {
*ptr = patch->ofile.map.data; *ptr = patch->ofile.map.data;
*len = patch->ofile.map.len; *len = patch->ofile.map.len;
} }
void git_patch__new_data( void git_patch_generated_new_data(
char **ptr, size_t *len, git_patch *patch) char **ptr, size_t *len, git_patch_generated *patch)
{ {
*ptr = patch->nfile.map.data; *ptr = patch->nfile.map.data;
*len = patch->nfile.map.len; *len = patch->nfile.map.len;
} }
int git_patch__invoke_callbacks( static int patch_generated_file_cb(
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload)
{
int error = 0;
uint32_t i, j;
if (file_cb)
error = file_cb(patch->delta, 0, payload);
if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (binary_cb)
error = binary_cb(patch->delta, &patch->binary, payload);
return error;
}
if (!hunk_cb && !line_cb)
return error;
for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
diff_patch_hunk *h = git_array_get(patch->hunks, i);
if (hunk_cb)
error = hunk_cb(patch->delta, &h->hunk, payload);
if (!line_cb)
continue;
for (j = 0; !error && j < h->line_count; ++j) {
git_diff_line *l =
git_array_get(patch->lines, h->line_start + j);
error = line_cb(patch->delta, &h->hunk, l, payload);
}
}
return error;
}
static int diff_patch_file_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
float progress, float progress,
void *payload) void *payload)
...@@ -1017,7 +828,7 @@ static int diff_patch_file_cb( ...@@ -1017,7 +828,7 @@ static int diff_patch_file_cb(
return 0; return 0;
} }
static int diff_patch_binary_cb( static int patch_generated_binary_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_binary *binary, const git_diff_binary *binary,
void *payload) void *payload)
...@@ -1047,62 +858,62 @@ static int diff_patch_binary_cb( ...@@ -1047,62 +858,62 @@ static int diff_patch_binary_cb(
return 0; return 0;
} }
static int diff_patch_hunk_cb( static int git_patch_hunk_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk_, const git_diff_hunk *hunk_,
void *payload) void *payload)
{ {
git_patch *patch = payload; git_patch_generated *patch = payload;
diff_patch_hunk *hunk; git_patch_hunk *hunk;
GIT_UNUSED(delta); GIT_UNUSED(delta);
hunk = git_array_alloc(patch->hunks); hunk = git_array_alloc(patch->base.hunks);
GITERR_CHECK_ALLOC(hunk); GITERR_CHECK_ALLOC(hunk);
memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
patch->header_size += hunk_->header_len; patch->base.header_size += hunk_->header_len;
hunk->line_start = git_array_size(patch->lines); hunk->line_start = git_array_size(patch->base.lines);
hunk->line_count = 0; hunk->line_count = 0;
return 0; return 0;
} }
static int diff_patch_line_cb( static int patch_generated_line_cb(
const git_diff_delta *delta, const git_diff_delta *delta,
const git_diff_hunk *hunk_, const git_diff_hunk *hunk_,
const git_diff_line *line_, const git_diff_line *line_,
void *payload) void *payload)
{ {
git_patch *patch = payload; git_patch_generated *patch = payload;
diff_patch_hunk *hunk; git_patch_hunk *hunk;
git_diff_line *line; git_diff_line *line;
GIT_UNUSED(delta); GIT_UNUSED(delta);
GIT_UNUSED(hunk_); GIT_UNUSED(hunk_);
hunk = git_array_last(patch->hunks); hunk = git_array_last(patch->base.hunks);
assert(hunk); /* programmer error if no hunk is available */ assert(hunk); /* programmer error if no hunk is available */
line = git_array_alloc(patch->lines); line = git_array_alloc(patch->base.lines);
GITERR_CHECK_ALLOC(line); GITERR_CHECK_ALLOC(line);
memcpy(line, line_, sizeof(*line)); memcpy(line, line_, sizeof(*line));
/* do some bookkeeping so we can provide old/new line numbers */ /* do some bookkeeping so we can provide old/new line numbers */
patch->content_size += line->content_len; patch->base.content_size += line->content_len;
if (line->origin == GIT_DIFF_LINE_ADDITION || if (line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION) line->origin == GIT_DIFF_LINE_DELETION)
patch->content_size += 1; patch->base.content_size += 1;
else if (line->origin == GIT_DIFF_LINE_CONTEXT) { else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
patch->content_size += 1; patch->base.content_size += 1;
patch->context_size += line->content_len + 1; patch->base.context_size += line->content_len + 1;
} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
patch->context_size += line->content_len; patch->base.context_size += line->content_len;
hunk->line_count++; hunk->line_count++;
...@@ -1110,7 +921,7 @@ static int diff_patch_line_cb( ...@@ -1110,7 +921,7 @@ static int diff_patch_line_cb(
} }
static void diff_output_init( static void diff_output_init(
git_diff_output *out, git_patch_generated_output *out,
const git_diff_options *opts, const git_diff_options *opts,
git_diff_file_cb file_cb, git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb, git_diff_binary_cb binary_cb,
...@@ -1129,14 +940,15 @@ static void diff_output_init( ...@@ -1129,14 +940,15 @@ static void diff_output_init(
out->payload = payload; out->payload = payload;
} }
static void diff_output_to_patch(git_diff_output *out, git_patch *patch) static void diff_output_to_patch(
git_patch_generated_output *out, git_patch_generated *patch)
{ {
diff_output_init( diff_output_init(
out, out,
NULL, NULL,
diff_patch_file_cb, patch_generated_file_cb,
diff_patch_binary_cb, patch_generated_binary_cb,
diff_patch_hunk_cb, git_patch_hunk_cb,
diff_patch_line_cb, patch_generated_line_cb,
patch); patch);
} }
...@@ -4,66 +4,48 @@ ...@@ -4,66 +4,48 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with * This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#ifndef INCLUDE_diff_patch_h__ #ifndef INCLUDE_patch_generate_h__
#define INCLUDE_diff_patch_h__ #define INCLUDE_patch_generate_h__
#include "common.h" #include "common.h"
#include "diff.h" #include "diff.h"
#include "diff_file.h" #include "diff_file.h"
#include "array.h" #include "patch.h"
#include "git2/patch.h"
/* cached information about a hunk in a diff */
typedef struct diff_patch_hunk {
git_diff_hunk hunk;
size_t line_start;
size_t line_count;
} diff_patch_hunk;
enum { enum {
GIT_DIFF_PATCH_ALLOCATED = (1 << 0), GIT_PATCH_GENERATED_ALLOCATED = (1 << 0),
GIT_DIFF_PATCH_INITIALIZED = (1 << 1), GIT_PATCH_GENERATED_INITIALIZED = (1 << 1),
GIT_DIFF_PATCH_LOADED = (1 << 2), GIT_PATCH_GENERATED_LOADED = (1 << 2),
/* the two sides are different */ /* 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 */ /* the difference between the two sides has been computed */
GIT_DIFF_PATCH_DIFFED = (1 << 4), GIT_PATCH_GENERATED_DIFFED = (1 << 4),
GIT_DIFF_PATCH_FLATTENED = (1 << 5), GIT_PATCH_GENERATED_FLATTENED = (1 << 5),
}; };
struct git_patch { struct git_patch_generated {
git_refcount rc; struct git_patch base;
git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
git_diff_options diff_opts;
git_diff_delta *delta;
size_t delta_index; size_t delta_index;
git_diff_file_content ofile; git_diff_file_content ofile;
git_diff_file_content nfile; git_diff_file_content nfile;
uint32_t flags; uint32_t flags;
git_diff_binary binary;
git_array_t(diff_patch_hunk) hunks;
git_array_t(git_diff_line) lines;
size_t content_size, context_size, header_size;
git_pool flattened; git_pool flattened;
}; };
extern git_diff *git_patch__diff(git_patch *); typedef struct git_patch_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_generated_old_data(
extern void git_patch__new_data(char **, size_t *, git_patch *); 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( typedef struct git_patch_generated_output git_patch_generated_output;
git_patch *patch,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb line_cb,
void *payload);
typedef struct git_diff_output git_diff_output; struct git_patch_generated_output {
struct git_diff_output {
/* these callbacks are issued with the diff data */ /* these callbacks are issued with the diff data */
git_diff_file_cb file_cb; git_diff_file_cb file_cb;
git_diff_binary_cb binary_cb; git_diff_binary_cb binary_cb;
...@@ -77,7 +59,8 @@ struct git_diff_output { ...@@ -77,7 +59,8 @@ struct git_diff_output {
/* this callback is used to do the diff and drive the other callbacks. /* this callback is used to do the diff and drive the other callbacks.
* see diff_xdiff.h for how to use this in practice for now. * see diff_xdiff.h for how to use this in practice for now.
*/ */
int (*diff_cb)(git_diff_output *output, git_patch *patch); int (*diff_cb)(git_patch_generated_output *output,
git_patch_generated *patch);
}; };
#endif #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( ...@@ -306,6 +306,25 @@ int git_path_join_unrooted(
return 0; 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) int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{ {
char buf[GIT_PATH_MAX]; char buf[GIT_PATH_MAX];
......
...@@ -244,6 +244,12 @@ extern int git_path_join_unrooted( ...@@ -244,6 +244,12 @@ extern int git_path_join_unrooted(
git_buf *path_out, const char *path, const char *base, ssize_t *root_at); 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. * 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); extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "iterator.h" #include "iterator.h"
#include "merge.h" #include "merge.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
static int create_error(int error, const char *msg) static int create_error(int error, const char *msg)
{ {
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "git2/diff.h" #include "git2/diff.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
static unsigned int index_delta2status(const git_diff_delta *head2idx) 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) ...@@ -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) 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; const char *p;
int64_t n, nn; int64_t n, nn;
int c, ovfl, v, neg, ndig; int c, ovfl, v, neg, ndig;
...@@ -111,7 +117,7 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba ...@@ -111,7 +117,7 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba
/* /*
* Non-empty sequence of digits * Non-empty sequence of digits
*/ */
for (;; p++,ndig++) { for (; nptr_len > 0; p++,ndig++,nptr_len--) {
c = *p; c = *p;
v = base; v = base;
if ('0'<=c && c<='9') if ('0'<=c && c<='9')
...@@ -148,11 +154,17 @@ Return: ...@@ -148,11 +154,17 @@ Return:
int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) 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; int error;
int32_t tmp_int; int32_t tmp_int;
int64_t tmp_long; 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; return error;
tmp_int = tmp_long & 0xFFFFFFFF; tmp_int = tmp_long & 0xFFFFFFFF;
...@@ -321,6 +333,12 @@ char *git__strsep(char **end, const char *sep) ...@@ -321,6 +333,12 @@ char *git__strsep(char **end, const char *sep)
return NULL; 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) void git__hexdump(const char *buffer, size_t len)
{ {
static const size_t LINE_WIDTH = 16; static const size_t LINE_WIDTH = 16;
......
...@@ -263,7 +263,10 @@ GIT_INLINE(int) git__signum(int val) ...@@ -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__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__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 void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed); extern uint32_t git__hash(const void *key, int len, uint32_t seed);
...@@ -290,6 +293,8 @@ GIT_INLINE(int) git__tolower(int c) ...@@ -290,6 +293,8 @@ GIT_INLINE(int) git__tolower(int c)
# define git__tolower(a) tolower(a) # define git__tolower(a) tolower(a)
#endif #endif
extern size_t git__linenlen(const char *buffer, size_t buffer_len);
GIT_INLINE(const char *) git__next_line(const char *s) GIT_INLINE(const char *) git__next_line(const char *s)
{ {
while (*s && *s != '\n') s++; while (*s && *s != '\n') s++;
...@@ -466,6 +471,11 @@ GIT_INLINE(bool) git__iswildcard(int c) ...@@ -466,6 +471,11 @@ GIT_INLINE(bool) git__iswildcard(int c)
return (c == '*' || c == '?' || 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. * Parse a string value as a boolean, just like Core Git does.
* *
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "common.h" #include "common.h"
#include "vector.h" #include "vector.h"
#include "integer.h"
/* In elements, not bytes */ /* In elements, not bytes */
#define MIN_ALLOCSIZE 8 #define MIN_ALLOCSIZE 8
...@@ -330,6 +331,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length) ...@@ -330,6 +331,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length)
return 0; 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) int git_vector_set(void **old, git_vector *v, size_t position, void *value)
{ {
if (position + 1 > v->length) { if (position + 1 > v->length) {
......
...@@ -93,6 +93,9 @@ void git_vector_remove_matching( ...@@ -93,6 +93,9 @@ void git_vector_remove_matching(
void *payload); void *payload);
int git_vector_resize_to(git_vector *v, size_t new_length); 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); int git_vector_set(void **old, git_vector *v, size_t position, void *value);
/** Check if vector is sorted */ /** Check if vector is sorted */
......
...@@ -28,19 +28,30 @@ static int zstream_seterr(git_zstream *zs) ...@@ -28,19 +28,30 @@ static int zstream_seterr(git_zstream *zs)
return -1; 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); zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
return zstream_seterr(zstream); return zstream_seterr(zstream);
} }
void git_zstream_free(git_zstream *zstream) void git_zstream_free(git_zstream *zstream)
{ {
if (zstream->type == GIT_ZSTREAM_INFLATE)
inflateEnd(&zstream->z);
else
deflateEnd(&zstream->z); deflateEnd(&zstream->z);
} }
void git_zstream_reset(git_zstream *zstream) void git_zstream_reset(git_zstream *zstream)
{ {
if (zstream->type == GIT_ZSTREAM_INFLATE)
inflateReset(&zstream->z);
else
deflateReset(&zstream->z); deflateReset(&zstream->z);
zstream->in = NULL; zstream->in = NULL;
zstream->in_len = 0; zstream->in_len = 0;
...@@ -75,6 +86,11 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) ...@@ -75,6 +86,11 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
int zflush = Z_FINISH; int zflush = Z_FINISH;
size_t out_remain = *out_len; 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) { while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
size_t out_queued, in_queued, out_used, in_used; 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) ...@@ -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; out_queued = (size_t)zstream->z.avail_out;
/* compress next chunk */ /* compress next chunk */
if (zstream->type == GIT_ZSTREAM_INFLATE)
zstream->zerr = inflate(&zstream->z, zflush);
else
zstream->zerr = deflate(&zstream->z, zflush); zstream->zerr = deflate(&zstream->z, zflush);
if (zstream->zerr == Z_STREAM_ERROR) if (zstream->zerr == Z_STREAM_ERROR)
...@@ -120,12 +139,12 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) ...@@ -120,12 +139,12 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
return 0; 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; git_zstream zs = GIT_ZSTREAM_INIT;
int error = 0; int error = 0;
if ((error = git_zstream_init(&zs)) < 0) if ((error = git_zstream_init(&zs, type)) < 0)
return error; return error;
if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
...@@ -154,3 +173,13 @@ done: ...@@ -154,3 +173,13 @@ done:
git_zstream_free(&zs); git_zstream_free(&zs);
return error; 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 @@ ...@@ -12,8 +12,14 @@
#include "common.h" #include "common.h"
#include "buffer.h" #include "buffer.h"
typedef enum {
GIT_ZSTREAM_INFLATE,
GIT_ZSTREAM_DEFLATE,
} git_zstream_t;
typedef struct { typedef struct {
z_stream z; z_stream z;
git_zstream_t type;
const char *in; const char *in;
size_t in_len; size_t in_len;
int zerr; int zerr;
...@@ -21,7 +27,7 @@ typedef struct { ...@@ -21,7 +27,7 @@ typedef struct {
#define GIT_ZSTREAM_INIT {{0}} #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); void git_zstream_free(git_zstream *zstream);
int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); 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); ...@@ -35,5 +41,6 @@ bool git_zstream_done(git_zstream *zstream);
void git_zstream_reset(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_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__ */ #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) ...@@ -813,6 +813,44 @@ void test_core_buffer__encode_base85(void)
git_buf_free(&buf); 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) void test_core_buffer__classify_with_utf8(void)
{ {
char *data0 = "Simple text\n"; char *data0 = "Simple text\n";
......
...@@ -274,3 +274,105 @@ void test_core_vector__remove_matching(void) ...@@ -274,3 +274,105 @@ void test_core_vector__remove_matching(void)
git_vector_free(&x); 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) ...@@ -48,7 +48,7 @@ void test_core_zstream__basic(void)
char out[128]; char out[128];
size_t outlen = sizeof(out); 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_set_input(&z, data, strlen(data) + 1));
cl_git_pass(git_zstream_get_output(out, &outlen, &z)); cl_git_pass(git_zstream_get_output(out, &outlen, &z));
cl_assert(git_zstream_done(&z)); cl_assert(git_zstream_done(&z));
...@@ -58,6 +58,25 @@ void test_core_zstream__basic(void) ...@@ -58,6 +58,25 @@ void test_core_zstream__basic(void)
assert_zlib_equal(data, strlen(data) + 1, out, outlen); 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) void test_core_zstream__buffer(void)
{ {
git_buf out = GIT_BUF_INIT; git_buf out = GIT_BUF_INIT;
...@@ -68,9 +87,10 @@ void test_core_zstream__buffer(void) ...@@ -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" #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 out1 = GIT_BUF_INIT, out2 = GIT_BUF_INIT;
git_buf inflated = GIT_BUF_INIT;
size_t i, fixed_size = max(input->size / 2, 256); size_t i, fixed_size = max(input->size / 2, 256);
char *fixed = git__malloc(fixed_size); char *fixed = git__malloc(fixed_size);
cl_assert(fixed); cl_assert(fixed);
...@@ -93,7 +113,7 @@ static void compress_input_various_ways(git_buf *input) ...@@ -93,7 +113,7 @@ static void compress_input_various_ways(git_buf *input)
} }
cl_assert(use_fixed_size <= fixed_size); 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)); cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size));
while (!git_zstream_done(&zs)) { while (!git_zstream_done(&zs)) {
...@@ -112,7 +132,12 @@ static void compress_input_various_ways(git_buf *input) ...@@ -112,7 +132,12 @@ static void compress_input_various_ways(git_buf *input)
git_buf_free(&out2); 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(&out1);
git_buf_free(&inflated);
git__free(fixed); git__free(fixed);
} }
...@@ -129,14 +154,14 @@ void test_core_zstream__big_data(void) ...@@ -129,14 +154,14 @@ void test_core_zstream__big_data(void)
cl_git_pass( cl_git_pass(
git_buf_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); 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 */ /* make a big string that's hard to compress */
srand(0xabad1dea); srand(0xabad1dea);
for (scan = 0; scan < in.size; ++scan) for (scan = 0; scan < in.size; ++scan)
in.ptr[scan] = (char)rand(); in.ptr[scan] = (char)rand();
compress_input_various_ways(&in); compress_and_decompress_input_various_ways(&in);
} }
git_buf_free(&in); git_buf_free(&in);
......
...@@ -241,3 +241,76 @@ void diff_print_raw(FILE *fp, git_diff *diff) ...@@ -241,3 +241,76 @@ void diff_print_raw(FILE *fp, git_diff *diff)
git_diff_print(diff, GIT_DIFF_FORMAT_RAW, git_diff_print(diff, GIT_DIFF_FORMAT_RAW,
git_diff_print_callback__to_file_handle, fp ? fp : stderr)); 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( ...@@ -68,3 +68,6 @@ extern int diff_foreach_via_iterator(
extern void diff_print(FILE *fp, git_diff *diff); extern void diff_print(FILE *fp, git_diff *diff);
extern void diff_print_raw(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 @@ ...@@ -4,6 +4,7 @@
#include "buffer.h" #include "buffer.h"
#include "commit.h" #include "commit.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
static git_repository *repo; static git_repository *repo;
...@@ -350,9 +351,6 @@ void test_diff_format_email__mode_change(void) ...@@ -350,9 +351,6 @@ void test_diff_format_email__mode_change(void)
"diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
"old mode 100644\n" \ "old mode 100644\n" \
"new mode 100755\n" \ "new mode 100755\n" \
"index a97157a..a97157a\n" \
"--- a/file1.txt.renamed\n" \
"+++ b/file1.txt.renamed\n" \
"--\n" \ "--\n" \
"libgit2 " LIBGIT2_VERSION "\n" \ "libgit2 " LIBGIT2_VERSION "\n" \
"\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 ...@@ -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", 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); 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 @@ ...@@ -4,6 +4,7 @@
#include "buffer.h" #include "buffer.h"
#include "commit.h" #include "commit.h"
#include "diff.h" #include "diff.h"
#include "diff_generate.h"
static git_repository *_repo; static git_repository *_repo;
static git_diff_stats *_stats; static git_diff_stats *_stats;
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
#include "merge.h" #include "merge.h"
#include "../merge_helpers.h" #include "../merge_helpers.h"
#include "diff.h" #include "diff.h"
#include "diff_tform.h"
#include "git2/sys/hashsig.h" #include "git2/sys/hashsig.h"
static git_repository *repo; 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