summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/diff.h42
-rw-r--r--include/git2/errors.h3
-rw-r--r--src/apply.c366
-rw-r--r--src/apply.h21
-rw-r--r--src/array.h1
-rw-r--r--src/buffer.c272
-rw-r--r--src/buffer.h8
-rw-r--r--src/checkout.c1
-rw-r--r--src/delta-apply.c166
-rw-r--r--src/delta-apply.h62
-rw-r--r--src/delta.c300
-rw-r--r--src/delta.h137
-rw-r--r--src/diff.c1579
-rw-r--r--src/diff.h134
-rw-r--r--src/diff_driver.c1
-rw-r--r--src/diff_file.c3
-rw-r--r--src/diff_generate.c1611
-rw-r--r--src/diff_generate.h123
-rw-r--r--src/diff_parse.c105
-rw-r--r--src/diff_patch.h83
-rw-r--r--src/diff_print.c401
-rw-r--r--src/diff_stats.c5
-rw-r--r--src/diff_tform.c1
-rw-r--r--src/diff_tform.h22
-rw-r--r--src/diff_xdiff.c18
-rw-r--r--src/diff_xdiff.h10
-rw-r--r--src/merge.c2
-rw-r--r--src/odb.c2
-rw-r--r--src/odb_loose.c2
-rw-r--r--src/odb_pack.c2
-rw-r--r--src/pack-objects.c35
-rw-r--r--src/pack.c7
-rw-r--r--src/patch.c207
-rw-r--r--src/patch.h66
-rw-r--r--src/patch_generate.c (renamed from src/diff_patch.c)526
-rw-r--r--src/patch_generate.h66
-rw-r--r--src/patch_parse.c1121
-rw-r--r--src/patch_parse.h54
-rw-r--r--src/path.c19
-rw-r--r--src/path.h6
-rw-r--r--src/stash.c1
-rw-r--r--src/status.c1
-rw-r--r--src/util.c22
-rw-r--r--src/util.h10
-rw-r--r--src/vector.c42
-rw-r--r--src/vector.h3
-rw-r--r--src/zstream.c43
-rw-r--r--src/zstream.h9
-rw-r--r--tests/apply/fromdiff.c289
-rw-r--r--tests/apply/fromfile.c449
-rw-r--r--tests/buf/quote.c88
-rw-r--r--tests/core/buffer.c38
-rw-r--r--tests/core/vector.c102
-rw-r--r--tests/core/zstream.c35
-rw-r--r--tests/diff/diff_helpers.c73
-rw-r--r--tests/diff/diff_helpers.h3
-rw-r--r--tests/diff/format_email.c4
-rw-r--r--tests/diff/parse.c153
-rw-r--r--tests/diff/rename.c46
-rw-r--r--tests/diff/stats.c1
-rw-r--r--tests/merge/trees/treediff.c1
-rw-r--r--tests/patch/parse.c104
-rw-r--r--tests/patch/patch_common.h663
-rw-r--r--tests/patch/print.c168
64 files changed, 7255 insertions, 2683 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h
index c35701a46..005b33965 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -264,10 +264,15 @@ typedef enum {
* link, a submodule commit id, or even a tree (although that only if you
* are tracking type changes or ignored/untracked directories).
*
- * The `oid` is the `git_oid` of the item. If the entry represents an
+ * The `id` is the `git_oid` of the item. If the entry represents an
* absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta),
* then the oid will be zeroes.
*
+ * The `id_abbrev` represents the known length of the `id` field, when
+ * converted to a hex string. It is generally `GIT_OID_HEXSZ`, unless this
+ * delta was created from reading a patch file, in which case it may be
+ * abbreviated to something reasonable, like 7 characters.
+ *
* `path` is the NUL-terminated path to the entry relative to the working
* directory of the repository.
*
@@ -280,6 +285,7 @@ typedef enum {
*/
typedef struct {
git_oid id;
+ int id_abbrev;
const char *path;
git_off_t size;
uint32_t flags;
@@ -448,6 +454,8 @@ typedef int (*git_diff_file_cb)(
float progress,
void *payload);
+#define GIT_DIFF_HUNK_HEADER_SIZE 128
+
/**
* When producing a binary diff, the binary data returned will be
* either the deflated full ("literal") contents of the file, or
@@ -499,12 +507,12 @@ typedef int(*git_diff_binary_cb)(
* Structure describing a hunk of a diff.
*/
typedef struct {
- int old_start; /**< Starting line number in old_file */
- int old_lines; /**< Number of lines in old_file */
- int new_start; /**< Starting line number in new_file */
- int new_lines; /**< Number of lines in new_file */
- size_t header_len; /**< Number of bytes in header text */
- char header[128]; /**< Header text, NUL-byte terminated */
+ int old_start; /** Starting line number in old_file */
+ int old_lines; /** Number of lines in old_file */
+ int new_start; /** Starting line number in new_file */
+ int new_lines; /** Number of lines in new_file */
+ size_t header_len; /** Number of bytes in header text */
+ char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */
} git_diff_hunk;
/**
@@ -1046,6 +1054,21 @@ GIT_EXTERN(int) git_diff_print(
git_diff_line_cb print_cb,
void *payload);
+/**
+ * Produce the complete formatted text output from a diff into a
+ * buffer.
+ *
+ * @param out A pointer to a user-allocated git_buf that will
+ * contain the diff text
+ * @param diff A git_diff generated by one of the above functions.
+ * @param format A git_diff_format_t value to pick the text format.
+ * @return 0 on success or error code
+ */
+GIT_EXTERN(int) git_diff_to_buf(
+ git_buf *out,
+ git_diff *diff,
+ git_diff_format_t format);
+
/**@}*/
@@ -1166,6 +1189,11 @@ GIT_EXTERN(int) git_diff_buffers(
git_diff_line_cb line_cb,
void *payload);
+GIT_EXTERN(int) git_diff_from_buffer(
+ git_diff **out,
+ const char *content,
+ size_t content_len);
+
/**
* This is an opaque structure which is allocated by `git_diff_get_stats`.
* You are responsible for releasing the object memory when done, using the
diff --git a/include/git2/errors.h b/include/git2/errors.h
index 3ecea34bf..e959ffd8a 100644
--- a/include/git2/errors.h
+++ b/include/git2/errors.h
@@ -98,7 +98,8 @@ typedef enum {
GITERR_CHERRYPICK,
GITERR_DESCRIBE,
GITERR_REBASE,
- GITERR_FILESYSTEM
+ GITERR_FILESYSTEM,
+ GITERR_PATCH,
} git_error_t;
/**
diff --git a/src/apply.c b/src/apply.c
new file mode 100644
index 000000000..876860754
--- /dev/null
+++ b/src/apply.c
@@ -0,0 +1,366 @@
+/*
+ * 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;
+}
diff --git a/src/apply.h b/src/apply.h
new file mode 100644
index 000000000..96e0f55b5
--- /dev/null
+++ b/src/apply.h
@@ -0,0 +1,21 @@
+/*
+ * 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
diff --git a/src/array.h b/src/array.h
index 78d321e82..1d8a01c96 100644
--- a/src/array.h
+++ b/src/array.h
@@ -85,7 +85,6 @@ on_oom:
#define git_array_foreach(a, i, element) \
for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
-
GIT_INLINE(int) git_array__search(
size_t *out,
void *array_ptr,
diff --git a/src/buffer.c b/src/buffer.c
index 1a5809cca..d135ebe4a 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -273,42 +273,51 @@ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len)
return 0;
}
-/* The inverse of base64_encode, offset by '+' == 43. */
+/* The inverse of base64_encode */
static const int8_t base64_decode[] = {
- 62,
- -1, -1, -1,
- 63,
- 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
- -1, -1, -1, 0, -1, -1, -1,
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
- 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
- -1, -1, -1, -1, -1, -1,
- 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
- 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
-#define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43])
-
int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
{
size_t i;
int8_t a, b, c, d;
size_t orig_size = buf->size, new_size;
+ if (len % 4) {
+ giterr_set(GITERR_INVALID, "invalid base64 input");
+ return -1;
+ }
+
assert(len % 4 == 0);
GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size);
GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
ENSURE_SIZE(buf, new_size);
for (i = 0; i < len; i += 4) {
- if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 ||
- (b = BASE64_DECODE_VALUE(base64[i+1])) < 0 ||
- (c = BASE64_DECODE_VALUE(base64[i+2])) < 0 ||
- (d = BASE64_DECODE_VALUE(base64[i+3])) < 0) {
+ if ((a = base64_decode[(unsigned char)base64[i]]) < 0 ||
+ (b = base64_decode[(unsigned char)base64[i+1]]) < 0 ||
+ (c = base64_decode[(unsigned char)base64[i+2]]) < 0 ||
+ (d = base64_decode[(unsigned char)base64[i+3]]) < 0) {
buf->size = orig_size;
buf->ptr[buf->size] = '\0';
- giterr_set(GITERR_INVALID, "Invalid base64 input");
+ giterr_set(GITERR_INVALID, "invalid base64 input");
return -1;
}
@@ -321,7 +330,7 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len)
return 0;
}
-static const char b85str[] =
+static const char base85_encode[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
@@ -351,7 +360,7 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
int val = acc % 85;
acc /= 85;
- b85[i] = b85str[val];
+ b85[i] = base85_encode[val];
}
for (i = 0; i < 5; i++)
@@ -363,6 +372,88 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len)
return 0;
}
+/* The inverse of base85_encode */
+static const int8_t base85_decode[] = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77,
+ 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80,
+ 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+int git_buf_decode_base85(
+ git_buf *buf,
+ const char *base85,
+ size_t base85_len,
+ size_t output_len)
+{
+ size_t orig_size = buf->size, new_size;
+
+ if (base85_len % 5 ||
+ output_len > base85_len * 4 / 5) {
+ giterr_set(GITERR_INVALID, "invalid base85 input");
+ return -1;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size);
+ GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1);
+ ENSURE_SIZE(buf, new_size);
+
+ while (output_len) {
+ unsigned acc = 0;
+ int de, cnt = 4;
+ unsigned char ch;
+ do {
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ acc = acc * 85 + de;
+ } while (--cnt);
+ ch = *base85++;
+ de = base85_decode[ch];
+ if (--de < 0)
+ goto on_error;
+
+ /* Detect overflow. */
+ if (0xffffffff / 85 < acc ||
+ 0xffffffff - de < (acc *= 85))
+ goto on_error;
+
+ acc += de;
+
+ cnt = (output_len < 4) ? output_len : 4;
+ output_len -= cnt;
+ do {
+ acc = (acc << 8) | (acc >> 24);
+ buf->ptr[buf->size++] = acc;
+ } while (--cnt);
+ }
+
+ buf->ptr[buf->size] = 0;
+
+ return 0;
+
+on_error:
+ buf->size = orig_size;
+ buf->ptr[buf->size] = '\0';
+
+ giterr_set(GITERR_INVALID, "invalid base85 input");
+ return -1;
+}
+
int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
size_t expected_size, new_size;
@@ -766,3 +857,144 @@ int git_buf_splice(
buf->ptr[buf->size] = '\0';
return 0;
}
+
+/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_buf_quote(git_buf *buf)
+{
+ const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' };
+ git_buf quoted = GIT_BUF_INIT;
+ size_t i = 0;
+ bool quote = false;
+ int error = 0;
+
+ /* walk to the first char that needs quoting */
+ if (buf->size && buf->ptr[0] == '!')
+ quote = true;
+
+ for (i = 0; !quote && i < buf->size; i++) {
+ if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' ||
+ buf->ptr[i] < ' ' || buf->ptr[i] > '~') {
+ quote = true;
+ break;
+ }
+ }
+
+ if (!quote)
+ goto done;
+
+ git_buf_putc(&quoted, '"');
+ git_buf_put(&quoted, buf->ptr, i);
+
+ for (; i < buf->size; i++) {
+ /* whitespace - use the map above, which is ordered by ascii value */
+ if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') {
+ git_buf_putc(&quoted, '\\');
+ git_buf_putc(&quoted, whitespace[buf->ptr[i] - '\a']);
+ }
+
+ /* double quote and backslash must be escaped */
+ else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') {
+ git_buf_putc(&quoted, '\\');
+ git_buf_putc(&quoted, buf->ptr[i]);
+ }
+
+ /* escape anything unprintable as octal */
+ else if (buf->ptr[i] != ' ' &&
+ (buf->ptr[i] < '!' || buf->ptr[i] > '~')) {
+ git_buf_printf(&quoted, "\\%03o", (unsigned char)buf->ptr[i]);
+ }
+
+ /* yay, printable! */
+ else {
+ git_buf_putc(&quoted, buf->ptr[i]);
+ }
+ }
+
+ git_buf_putc(&quoted, '"');
+
+ if (git_buf_oom(&quoted)) {
+ error = -1;
+ goto done;
+ }
+
+ git_buf_swap(&quoted, buf);
+
+done:
+ git_buf_free(&quoted);
+ return error;
+}
+
+/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */
+int git_buf_unquote(git_buf *buf)
+{
+ size_t i, j;
+ char ch;
+
+ git_buf_rtrim(buf);
+
+ if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"')
+ goto invalid;
+
+ for (i = 0, j = 1; j < buf->size-1; i++, j++) {
+ ch = buf->ptr[j];
+
+ if (ch == '\\') {
+ if (j == buf->size-2)
+ goto invalid;
+
+ ch = buf->ptr[++j];
+
+ switch (ch) {
+ /* \" or \\ simply copy the char in */
+ case '"': case '\\':
+ break;
+
+ /* add the appropriate escaped char */
+ case 'a': ch = '\a'; break;
+ case 'b': ch = '\b'; break;
+ case 'f': ch = '\f'; break;
+ case 'n': ch = '\n'; break;
+ case 'r': ch = '\r'; break;
+ case 't': ch = '\t'; break;
+ case 'v': ch = '\v'; break;
+
+ /* \xyz digits convert to the char*/
+ case '0': case '1': case '2': case '3':
+ if (j == buf->size-3) {
+ giterr_set(GITERR_INVALID,
+ "Truncated quoted character \\%c", ch);
+ return -1;
+ }
+
+ if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' ||
+ buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') {
+ giterr_set(GITERR_INVALID,
+ "Truncated quoted character \\%c%c%c",
+ buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]);
+ return -1;
+ }
+
+ ch = ((buf->ptr[j] - '0') << 6) |
+ ((buf->ptr[j+1] - '0') << 3) |
+ (buf->ptr[j+2] - '0');
+ j += 2;
+ break;
+
+ default:
+ giterr_set(GITERR_INVALID, "Invalid quoted character \\%c", ch);
+ return -1;
+ }
+ }
+
+ buf->ptr[i] = ch;
+ }
+
+ buf->ptr[i] = '\0';
+ buf->size = i;
+
+ return 0;
+
+invalid:
+ giterr_set(GITERR_INVALID, "Invalid quoted line");
+ return -1;
+}
diff --git a/src/buffer.h b/src/buffer.h
index e46ee5dd7..cdfca6d99 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -173,6 +173,12 @@ void git_buf_rtrim(git_buf *buf);
int git_buf_cmp(const git_buf *a, const git_buf *b);
+/* Quote and unquote a buffer as specified in
+ * http://marc.info/?l=git&m=112927316408690&w=2
+ */
+int git_buf_quote(git_buf *buf);
+int git_buf_unquote(git_buf *buf);
+
/* Write data as base64 encoded in buffer */
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len);
/* Decode the given bas64 and write the result to the buffer */
@@ -180,6 +186,8 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len);
/* Write data as "base85" encoded in buffer */
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
+/* Decode the given "base85" and write the result to the buffer */
+int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len);
/*
* Insert, remove or replace a portion of the buffer.
diff --git a/src/checkout.c b/src/checkout.c
index f39c341d4..72ee8b624 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -26,6 +26,7 @@
#include "filter.h"
#include "blob.h"
#include "diff.h"
+#include "diff_generate.h"
#include "pathspec.h"
#include "buf_text.h"
#include "diff_xdiff.h"
diff --git a/src/delta-apply.c b/src/delta-apply.c
deleted file mode 100644
index 02ec7b75e..000000000
--- a/src/delta-apply.c
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#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;
-}
diff --git a/src/delta-apply.h b/src/delta-apply.h
deleted file mode 100644
index eeeb78682..000000000
--- a/src/delta-apply.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#ifndef INCLUDE_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
diff --git a/src/delta.c b/src/delta.c
index d72d820d8..dc45697b6 100644
--- a/src/delta.c
+++ b/src/delta.c
@@ -114,7 +114,7 @@ struct index_entry {
struct git_delta_index {
unsigned long memsize;
const void *src_buf;
- unsigned long src_size;
+ size_t src_size;
unsigned int hash_mask;
struct index_entry *hash[GIT_FLEX_ARRAY];
};
@@ -142,8 +142,8 @@ static int lookup_index_alloc(
return 0;
}
-struct git_delta_index *
-git_delta_create_index(const void *buf, unsigned long bufsize)
+int git_delta_index_init(
+ git_delta_index **out, const void *buf, size_t bufsize)
{
unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
const unsigned char *data, *buffer = buf;
@@ -152,8 +152,10 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
void *mem;
unsigned long memsize;
+ *out = NULL;
+
if (!buf || !bufsize)
- return NULL;
+ return 0;
/* Determine index hash size. Note that indexing skips the
first byte to allow for optimizing the rabin polynomial
@@ -172,7 +174,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hmask = hsize - 1;
if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0)
- return NULL;
+ return -1;
index = mem;
mem = index->hash;
@@ -190,7 +192,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
hash_count = git__calloc(hsize, sizeof(*hash_count));
if (!hash_count) {
git__free(index);
- return NULL;
+ return -1;
}
/* then populate the index */
@@ -243,20 +245,20 @@ git_delta_create_index(const void *buf, unsigned long bufsize)
}
git__free(hash_count);
- return index;
+ *out = index;
+ return 0;
}
-void git_delta_free_index(struct git_delta_index *index)
+void git_delta_index_free(git_delta_index *index)
{
git__free(index);
}
-unsigned long git_delta_sizeof_index(struct git_delta_index *index)
+size_t git_delta_index_size(git_delta_index *index)
{
- if (index)
- return index->memsize;
- else
- return 0;
+ assert(index);
+
+ return index->memsize;
}
/*
@@ -265,55 +267,57 @@ unsigned long git_delta_sizeof_index(struct git_delta_index *index)
*/
#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
-void *
-git_delta_create(
+int git_delta_create_from_index(
+ void **out,
+ size_t *out_len,
const struct git_delta_index *index,
const void *trg_buf,
- unsigned long trg_size,
- unsigned long *delta_size,
- unsigned long max_size)
+ size_t trg_size,
+ size_t max_size)
{
- unsigned int i, outpos, outsize, moff, msize, val;
+ unsigned int i, bufpos, bufsize, moff, msize, val;
int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top;
- unsigned char *out;
+ unsigned char *buf;
+
+ *out = NULL;
+ *out_len = 0;
if (!trg_buf || !trg_size)
- return NULL;
+ return 0;
- outpos = 0;
- outsize = 8192;
- if (max_size && outsize >= max_size)
- outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
- out = git__malloc(outsize);
- if (!out)
- return NULL;
+ bufpos = 0;
+ bufsize = 8192;
+ if (max_size && bufsize >= max_size)
+ bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
+ buf = git__malloc(bufsize);
+ GITERR_CHECK_ALLOC(buf);
/* store reference buffer size */
i = index->src_size;
while (i >= 0x80) {
- out[outpos++] = i | 0x80;
+ buf[bufpos++] = i | 0x80;
i >>= 7;
}
- out[outpos++] = i;
+ buf[bufpos++] = i;
/* store target buffer size */
i = trg_size;
while (i >= 0x80) {
- out[outpos++] = i | 0x80;
+ buf[bufpos++] = i | 0x80;
i >>= 7;
}
- out[outpos++] = i;
+ buf[bufpos++] = i;
ref_data = index->src_buf;
ref_top = ref_data + index->src_size;
data = trg_buf;
top = (const unsigned char *) trg_buf + trg_size;
- outpos++;
+ bufpos++;
val = 0;
for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
- out[outpos++] = *data;
+ buf[bufpos++] = *data;
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
}
inscnt = i;
@@ -350,11 +354,11 @@ git_delta_create(
if (msize < 4) {
if (!inscnt)
- outpos++;
- out[outpos++] = *data++;
+ bufpos++;
+ buf[bufpos++] = *data++;
inscnt++;
if (inscnt == 0x7f) {
- out[outpos - inscnt - 1] = inscnt;
+ buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
msize = 0;
@@ -368,14 +372,14 @@ git_delta_create(
msize++;
moff--;
data--;
- outpos--;
+ bufpos--;
if (--inscnt)
continue;
- outpos--; /* remove count slot */
+ bufpos--; /* remove count slot */
inscnt--; /* make it -1 */
break;
}
- out[outpos - inscnt - 1] = inscnt;
+ buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
@@ -383,22 +387,22 @@ git_delta_create(
left = (msize < 0x10000) ? 0 : (msize - 0x10000);
msize -= left;
- op = out + outpos++;
+ op = buf + bufpos++;
i = 0x80;
if (moff & 0x000000ff)
- out[outpos++] = moff >> 0, i |= 0x01;
+ buf[bufpos++] = moff >> 0, i |= 0x01;
if (moff & 0x0000ff00)
- out[outpos++] = moff >> 8, i |= 0x02;
+ buf[bufpos++] = moff >> 8, i |= 0x02;
if (moff & 0x00ff0000)
- out[outpos++] = moff >> 16, i |= 0x04;
+ buf[bufpos++] = moff >> 16, i |= 0x04;
if (moff & 0xff000000)
- out[outpos++] = moff >> 24, i |= 0x08;
+ buf[bufpos++] = moff >> 24, i |= 0x08;
if (msize & 0x00ff)
- out[outpos++] = msize >> 0, i |= 0x10;
+ buf[bufpos++] = msize >> 0, i |= 0x10;
if (msize & 0xff00)
- out[outpos++] = msize >> 8, i |= 0x20;
+ buf[bufpos++] = msize >> 8, i |= 0x20;
*op = i;
@@ -415,29 +419,201 @@ git_delta_create(
}
}
- if (outpos >= outsize - MAX_OP_SIZE) {
- void *tmp = out;
- outsize = outsize * 3 / 2;
- if (max_size && outsize >= max_size)
- outsize = max_size + MAX_OP_SIZE + 1;
- if (max_size && outpos > max_size)
+ if (bufpos >= bufsize - MAX_OP_SIZE) {
+ void *tmp = buf;
+ bufsize = bufsize * 3 / 2;
+ if (max_size && bufsize >= max_size)
+ bufsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && bufpos > max_size)
break;
- out = git__realloc(out, outsize);
- if (!out) {
+ buf = git__realloc(buf, bufsize);
+ if (!buf) {
git__free(tmp);
- return NULL;
+ return -1;
}
}
}
if (inscnt)
- out[outpos - inscnt - 1] = inscnt;
+ buf[bufpos - inscnt - 1] = inscnt;
- if (max_size && outpos > max_size) {
- git__free(out);
- return NULL;
+ if (max_size && bufpos > max_size) {
+ giterr_set(GITERR_NOMEMORY, "delta would be larger than maximum size");
+ git__free(buf);
+ return GIT_EBUFS;
}
- *delta_size = outpos;
- return out;
+ *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;
+ }
+
+ GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
+ res_dp = git__malloc(alloc_sz);
+ GITERR_CHECK_ALLOC(res_dp);
+
+ res_dp[res_sz] = '\0';
+ *out = res_dp;
+ *out_len = res_sz;
+
+ while (delta < delta_end) {
+ unsigned char cmd = *delta++;
+ if (cmd & 0x80) {
+ /* cmd is a copy instruction; copy from the base.
+ */
+ size_t off = 0, len = 0;
+
+ if (cmd & 0x01) off = *delta++;
+ if (cmd & 0x02) off |= *delta++ << 8UL;
+ if (cmd & 0x04) off |= *delta++ << 16UL;
+ if (cmd & 0x08) off |= *delta++ << 24UL;
+
+ if (cmd & 0x10) len = *delta++;
+ if (cmd & 0x20) len |= *delta++ << 8UL;
+ if (cmd & 0x40) len |= *delta++ << 16UL;
+ if (!len) len = 0x10000;
+
+ if (base_len < off + len || res_sz < len)
+ goto fail;
+ memcpy(res_dp, base + off, len);
+ res_dp += len;
+ res_sz -= len;
+
+ }
+ else if (cmd) {
+ /* cmd is a literal insert instruction; copy from
+ * the delta stream itself.
+ */
+ if (delta_end - delta < cmd || res_sz < cmd)
+ goto fail;
+ memcpy(res_dp, delta, cmd);
+ delta += cmd;
+ res_dp += cmd;
+ res_sz -= cmd;
+
+ }
+ else {
+ /* cmd == 0 is reserved for future encodings.
+ */
+ goto fail;
+ }
+ }
+
+ if (delta != delta_end || res_sz)
+ goto fail;
+ return 0;
+
+fail:
+ git__free(*out);
+
+ *out = NULL;
+ *out_len = 0;
+
+ giterr_set(GITERR_INVALID, "Failed to apply delta");
+ return -1;
}
diff --git a/src/delta.h b/src/delta.h
index 4ca327992..cc9372922 100644
--- a/src/delta.h
+++ b/src/delta.h
@@ -6,12 +6,12 @@
#define INCLUDE_git_delta_h__
#include "common.h"
+#include "pack.h"
-/* opaque object for delta index */
-struct git_delta_index;
+typedef struct git_delta_index git_delta_index;
/*
- * create_delta_index: compute index data from given buffer
+ * git_delta_index_init: compute index data from given buffer
*
* This returns a pointer to a struct delta_index that should be passed to
* subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
@@ -19,22 +19,18 @@ struct git_delta_index;
* before free_delta_index() is called. The returned pointer must be freed
* using free_delta_index().
*/
-extern struct git_delta_index *
-git_delta_create_index(const void *buf, unsigned long bufsize);
+extern int git_delta_index_init(
+ git_delta_index **out, const void *buf, size_t bufsize);
/*
- * free_delta_index: free the index created by create_delta_index()
- *
- * Given pointer must be what create_delta_index() returned, or NULL.
+ * Free the index created by git_delta_index_init()
*/
-extern void git_delta_free_index(struct git_delta_index *index);
+extern void git_delta_index_free(git_delta_index *index);
/*
- * sizeof_delta_index: returns memory usage of delta index
- *
- * Given pointer must be what create_delta_index() returned, or NULL.
+ * Returns memory usage of delta index.
*/
-extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
+extern size_t git_delta_index_size(git_delta_index *index);
/*
* create_delta: create a delta from given index for the given buffer
@@ -46,69 +42,94 @@ extern unsigned long git_delta_sizeof_index(struct git_delta_index *index);
* returned and *delta_size is updated with its size. The returned buffer
* must be freed by the caller.
*/
-extern void *git_delta_create(
+extern int git_delta_create_from_index(
+ void **out,
+ size_t *out_size,
const struct git_delta_index *index,
const void *buf,
- unsigned long bufsize,
- unsigned long *delta_size,
- unsigned long max_delta_size);
+ size_t bufsize,
+ size_t max_delta_size);
/*
* diff_delta: create a delta from source buffer to target buffer
*
* If max_delta_size is non-zero and the resulting delta is to be larger
- * than max_delta_size then NULL is returned. On success, a non-NULL
+ * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL
* pointer to the buffer with the delta data is returned and *delta_size is
* updated with its size. The returned buffer must be freed by the caller.
*/
-GIT_INLINE(void *) git_delta(
- const void *src_buf, unsigned long src_bufsize,
- const void *trg_buf, unsigned long trg_bufsize,
- unsigned long *delta_size,
- unsigned long max_delta_size)
+GIT_INLINE(int) git_delta(
+ void **out, size_t *out_len,
+ const void *src_buf, size_t src_bufsize,
+ const void *trg_buf, size_t trg_bufsize,
+ size_t max_delta_size)
{
- struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize);
+ git_delta_index *index;
+ int error = 0;
+
+ *out = NULL;
+ *out_len = 0;
+
+ if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0)
+ return error;
+
if (index) {
- void *delta = git_delta_create(
- index, trg_buf, trg_bufsize, delta_size, max_delta_size);
- git_delta_free_index(index);
- return delta;
+ error = git_delta_create_from_index(out, out_len,
+ index, trg_buf, trg_bufsize, max_delta_size);
+
+ git_delta_index_free(index);
}
- return NULL;
-}
-/*
- * patch_delta: recreate target buffer given source buffer and delta data
- *
- * On success, a non-NULL pointer to the target buffer is returned and
- * *trg_bufsize is updated with its size. On failure a NULL pointer is
- * returned. The returned buffer must be freed by the caller.
- */
-extern void *git_delta_patch(
- const void *src_buf, unsigned long src_size,
- const void *delta_buf, unsigned long delta_size,
- unsigned long *dst_size);
+ return error;
+}
/* the smallest possible delta size is 4 bytes */
#define GIT_DELTA_SIZE_MIN 4
-/*
- * This must be called twice on the delta data buffer, first to get the
- * expected source buffer size, and again to get the target buffer size.
+/**
+* Apply a git binary delta to recover the original content.
+* The caller is responsible for freeing the returned buffer.
+*
+* @param out the output buffer
+* @param out_len the length of the output buffer
+* @param base the base to copy from during copy instructions.
+* @param base_len number of bytes available at base.
+* @param delta the delta to execute copy/insert instructions from.
+* @param delta_len total number of bytes in the delta.
+* @return 0 on success or an error code
+*/
+extern int git_delta_apply(
+ void **out,
+ size_t *out_len,
+ const unsigned char *base,
+ size_t base_len,
+ const unsigned char *delta,
+ size_t delta_len);
+
+/**
+* Read the header of a git binary delta.
+*
+* @param base_out pointer to store the base size field.
+* @param result_out pointer to store the result size field.
+* @param delta the delta to execute copy/insert instructions from.
+* @param delta_len total number of bytes in the delta.
+* @return 0 on success or an error code
+*/
+extern int git_delta_read_header(
+ size_t *base_out,
+ size_t *result_out,
+ const unsigned char *delta,
+ size_t delta_len);
+
+/**
+ * Read the header of a git binary delta
+ *
+ * This variant reads just enough from the packfile stream to read the
+ * delta header.
*/
-GIT_INLINE(unsigned long) git_delta_get_hdr_size(
- const unsigned char **datap, const unsigned char *top)
-{
- const unsigned char *data = *datap;
- unsigned long cmd, size = 0;
- int i = 0;
- do {
- cmd = *data++;
- size |= (cmd & 0x7f) << i;
- i += 7;
- } while (cmd & 0x80 && data < top);
- *datap = data;
- return size;
-}
+extern int git_delta_read_header_fromstream(
+ size_t *base_out,
+ size_t *result_out,
+ git_packfile_stream *stream);
#endif
diff --git a/src/diff.c b/src/diff.c
index 26c0b895b..317d49597 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -4,275 +4,21 @@
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
+#include "git2/version.h"
#include "common.h"
#include "diff.h"
-#include "fileops.h"
-#include "config.h"
-#include "attr_file.h"
-#include "filter.h"
-#include "pathspec.h"
+#include "diff_generate.h"
+#include "patch.h"
+#include "commit.h"
#include "index.h"
-#include "odb.h"
-#include "submodule.h"
-#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0)
-#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0)
+#define DIFF_FLAG_IS_SET(DIFF,FLAG) \
+ (((DIFF)->opts.flags & (FLAG)) != 0)
+#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \
+ (((DIFF)->opts.flags & (FLAG)) == 0)
#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \
(VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL))
-static git_diff_delta *diff_delta__alloc(
- git_diff *diff,
- git_delta_t status,
- const char *path)
-{
- git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta));
- if (!delta)
- return NULL;
-
- delta->old_file.path = git_pool_strdup(&diff->pool, path);
- if (delta->old_file.path == NULL) {
- git__free(delta);
- return NULL;
- }
-
- delta->new_file.path = delta->old_file.path;
-
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
- switch (status) {
- case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break;
- case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break;
- default: break; /* leave other status values alone */
- }
- }
- delta->status = status;
-
- return delta;
-}
-
-static int diff_insert_delta(
- git_diff *diff, git_diff_delta *delta, const char *matched_pathspec)
-{
- int error = 0;
-
- if (diff->opts.notify_cb) {
- error = diff->opts.notify_cb(
- diff, delta, matched_pathspec, diff->opts.payload);
-
- if (error) {
- git__free(delta);
-
- if (error > 0) /* positive value means to skip this delta */
- return 0;
- else /* negative value means to cancel diff */
- return giterr_set_after_callback_function(error, "git_diff");
- }
- }
-
- if ((error = git_vector_insert(&diff->deltas, delta)) < 0)
- git__free(delta);
-
- return error;
-}
-
-static bool diff_pathspec_match(
- const char **matched_pathspec,
- git_diff *diff,
- const git_index_entry *entry)
-{
- bool disable_pathspec_match =
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH);
-
- /* If we're disabling fnmatch, then the iterator has already applied
- * the filters to the files for us and we don't have to do anything.
- * However, this only applies to *files* - the iterator will include
- * directories that we need to recurse into when not autoexpanding,
- * so we still need to apply the pathspec match to directories.
- */
- if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) &&
- disable_pathspec_match) {
- *matched_pathspec = entry->path;
- return true;
- }
-
- return git_pathspec__match(
- &diff->pathspec, entry->path, disable_pathspec_match,
- DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE),
- matched_pathspec, NULL);
-}
-
-static int diff_delta__from_one(
- git_diff *diff,
- git_delta_t status,
- const git_index_entry *oitem,
- const git_index_entry *nitem)
-{
- const git_index_entry *entry = nitem;
- bool has_old = false;
- git_diff_delta *delta;
- const char *matched_pathspec;
-
- assert((oitem != NULL) ^ (nitem != NULL));
-
- if (oitem) {
- entry = oitem;
- has_old = true;
- }
-
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE))
- has_old = !has_old;
-
- if ((entry->flags & GIT_IDXENTRY_VALID) != 0)
- return 0;
-
- if (status == GIT_DELTA_IGNORED &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED))
- return 0;
-
- if (status == GIT_DELTA_UNTRACKED &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED))
- return 0;
-
- if (status == GIT_DELTA_UNREADABLE &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE))
- return 0;
-
- if (!diff_pathspec_match(&matched_pathspec, diff, entry))
- return 0;
-
- delta = diff_delta__alloc(diff, status, entry->path);
- GITERR_CHECK_ALLOC(delta);
-
- /* This fn is just for single-sided diffs */
- assert(status != GIT_DELTA_MODIFIED);
- delta->nfiles = 1;
-
- if (has_old) {
- delta->old_file.mode = entry->mode;
- delta->old_file.size = entry->file_size;
- delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
- git_oid_cpy(&delta->old_file.id, &entry->id);
- } else /* ADDED, IGNORED, UNTRACKED */ {
- delta->new_file.mode = entry->mode;
- delta->new_file.size = entry->file_size;
- delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
- git_oid_cpy(&delta->new_file.id, &entry->id);
- }
-
- delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID;
-
- if (has_old || !git_oid_iszero(&delta->new_file.id))
- delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
-
- return diff_insert_delta(diff, delta, matched_pathspec);
-}
-
-static int diff_delta__from_two(
- git_diff *diff,
- git_delta_t status,
- const git_index_entry *old_entry,
- uint32_t old_mode,
- const git_index_entry *new_entry,
- uint32_t new_mode,
- const git_oid *new_id,
- const char *matched_pathspec)
-{
- const git_oid *old_id = &old_entry->id;
- git_diff_delta *delta;
- const char *canonical_path = old_entry->path;
-
- if (status == GIT_DELTA_UNMODIFIED &&
- DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED))
- return 0;
-
- if (!new_id)
- new_id = &new_entry->id;
-
- if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) {
- uint32_t temp_mode = old_mode;
- const git_index_entry *temp_entry = old_entry;
- const git_oid *temp_id = old_id;
-
- old_entry = new_entry;
- new_entry = temp_entry;
- old_mode = new_mode;
- new_mode = temp_mode;
- old_id = new_id;
- new_id = temp_id;
- }
-
- delta = diff_delta__alloc(diff, status, canonical_path);
- GITERR_CHECK_ALLOC(delta);
- delta->nfiles = 2;
-
- if (!git_index_entry_is_conflict(old_entry)) {
- delta->old_file.size = old_entry->file_size;
- delta->old_file.mode = old_mode;
- git_oid_cpy(&delta->old_file.id, old_id);
- delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID |
- GIT_DIFF_FLAG_EXISTS;
- }
-
- if (!git_index_entry_is_conflict(new_entry)) {
- git_oid_cpy(&delta->new_file.id, new_id);
- delta->new_file.size = new_entry->file_size;
- delta->new_file.mode = new_mode;
- delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS;
- delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS;
-
- if (!git_oid_iszero(&new_entry->id))
- delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID;
- }
-
- return diff_insert_delta(diff, delta, matched_pathspec);
-}
-
-static git_diff_delta *diff_delta__last_for_item(
- git_diff *diff,
- const git_index_entry *item)
-{
- git_diff_delta *delta = git_vector_last(&diff->deltas);
- if (!delta)
- return NULL;
-
- switch (delta->status) {
- case GIT_DELTA_UNMODIFIED:
- case GIT_DELTA_DELETED:
- if (git_oid__cmp(&delta->old_file.id, &item->id) == 0)
- return delta;
- break;
- case GIT_DELTA_ADDED:
- if (git_oid__cmp(&delta->new_file.id, &item->id) == 0)
- return delta;
- break;
- case GIT_DELTA_UNREADABLE:
- case GIT_DELTA_UNTRACKED:
- if (diff->strcomp(delta->new_file.path, item->path) == 0 &&
- git_oid__cmp(&delta->new_file.id, &item->id) == 0)
- return delta;
- break;
- case GIT_DELTA_MODIFIED:
- if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 ||
- git_oid__cmp(&delta->new_file.id, &item->id) == 0)
- return delta;
- break;
- default:
- break;
- }
-
- return NULL;
-}
-
-static char *diff_strdup_prefix(git_pool *pool, const char *prefix)
-{
- size_t len = strlen(prefix);
-
- /* append '/' at end if needed */
- if (len > 0 && prefix[len - 1] != '/')
- return git_pool_strcat(pool, prefix, "/");
- else
- return git_pool_strndup(pool, prefix, len + 1);
-}
-
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
const char *str = delta->old_file.path;
@@ -305,73 +51,7 @@ int git_diff_delta__casecmp(const void *a, const void *b)
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)
+int git_diff__entry_cmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
@@ -379,7 +59,7 @@ static int diff_entry_cmp(const void *a, const void *b)
return strcmp(entry_a->path, entry_b->path);
}
-static int diff_entry_icmp(const void *a, const void *b)
+int git_diff__entry_icmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
@@ -387,198 +67,12 @@ static int diff_entry_icmp(const void *a, const void *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);
+ GIT_REFCOUNT_DEC(diff, diff->free_fn);
}
void git_diff_addref(git_diff *diff)
@@ -586,896 +80,6 @@ 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;
- 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 (!error)
- *diff_ptr = diff;
- else
- git_diff_free(diff);
-
- 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 **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;
- 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 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 **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 |
- GIT_ITERATOR_INCLUDE_CONFLICTS;
- bool index_ignore_case = false;
- int error = 0;
-
- assert(diff && repo);
-
- if (!index && (error = diff_load_index(&index, repo)) < 0)
- return error;
-
- index_ignore_case = index->ignore_case;
-
- DIFF_FROM_ITERATORS(
- git_iterator_for_tree(&a, old_tree, &a_opts), iflag,
- git_iterator_for_index(&b, repo, index, &b_opts), iflag
- );
-
- /* if index is in case-insensitive order, re-sort deltas to match */
- if (!error && index_ignore_case)
- diff_set_ignore_case(*diff, true);
-
- return error;
-}
-
-int git_diff_index_to_workdir(
- git_diff **diff,
- git_repository *repo,
- git_index *index,
- const git_diff_options *opts)
-{
- int error = 0;
-
- assert(diff && repo);
-
- if (!index && (error = diff_load_index(&index, repo)) < 0)
- return error;
-
- DIFF_FROM_ITERATORS(
- git_iterator_for_index(&a, repo, index, &a_opts),
- GIT_ITERATOR_INCLUDE_CONFLICTS,
-
- git_iterator_for_workdir(&b, repo, index, NULL, &b_opts),
- GIT_ITERATOR_DONT_AUTOEXPAND
- );
-
- if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX) && (*diff)->index_updated)
- error = git_index_write(index);
-
- return error;
-}
-
-int git_diff_tree_to_workdir(
- git_diff **diff,
- git_repository *repo,
- git_tree *old_tree,
- const git_diff_options *opts)
-{
- int error = 0;
- git_index *index;
-
- assert(diff && repo);
-
- if ((error = git_repository_index__weakptr(&index, repo)))
- return error;
-
- DIFF_FROM_ITERATORS(
- git_iterator_for_tree(&a, old_tree, &a_opts), 0,
- git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND
- );
-
- return error;
-}
-
-int git_diff_tree_to_workdir_with_index(
- git_diff **diff,
- git_repository *repo,
- git_tree *old_tree,
- const git_diff_options *opts)
-{
- int error = 0;
- git_diff *d1 = NULL, *d2 = NULL;
- git_index *index = NULL;
-
- assert(diff && repo);
-
- if ((error = diff_load_index(&index, repo)) < 0)
- return error;
-
- if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) &&
- !(error = git_diff_index_to_workdir(&d2, repo, index, opts)))
- error = git_diff_merge(d1, d2);
-
- git_diff_free(d2);
-
- if (error) {
- git_diff_free(d1);
- d1 = NULL;
- }
-
- *diff = d1;
- return error;
-}
-
-int git_diff_index_to_index(
- git_diff **diff,
- git_repository *repo,
- git_index *old_index,
- git_index *new_index,
- const git_diff_options *opts)
-{
- int error = 0;
-
- assert(diff && old_index && new_index);
-
- DIFF_FROM_ITERATORS(
- git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE,
- git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE
- );
-
- /* if index is in case-insensitive order, re-sort deltas to match */
- if (!error && (old_index->ignore_case || new_index->ignore_case))
- diff_set_ignore_case(*diff, true);
-
- return error;
-}
-
size_t git_diff_num_deltas(const git_diff *diff)
{
assert(diff);
@@ -1516,137 +120,6 @@ int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
return 0;
}
-int git_diff__paired_foreach(
- git_diff *head2idx,
- git_diff *idx2wd,
- int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload),
- void *payload)
-{
- int cmp, error = 0;
- git_diff_delta *h2i, *i2w;
- size_t i, j, i_max, j_max;
- int (*strcomp)(const char *, const char *) = git__strcmp;
- bool h2i_icase, i2w_icase, icase_mismatch;
-
- i_max = head2idx ? head2idx->deltas.length : 0;
- j_max = idx2wd ? idx2wd->deltas.length : 0;
- if (!i_max && !j_max)
- return 0;
-
- /* At some point, tree-to-index diffs will probably never ignore case,
- * even if that isn't true now. Index-to-workdir diffs may or may not
- * ignore case, but the index filename for the idx2wd diff should
- * still be using the canonical case-preserving name.
- *
- * Therefore the main thing we need to do here is make sure the diffs
- * are traversed in a compatible order. To do this, we temporarily
- * resort a mismatched diff to get the order correct.
- *
- * In order to traverse renames in the index->workdir, we need to
- * ensure that we compare the index name on both sides, so we
- * always sort by the old name in the i2w list.
- */
- h2i_icase = head2idx != NULL &&
- (head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
-
- i2w_icase = idx2wd != NULL &&
- (idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
-
- icase_mismatch =
- (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase);
-
- if (icase_mismatch && h2i_icase) {
- git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp);
- git_vector_sort(&head2idx->deltas);
- }
-
- if (i2w_icase && !icase_mismatch) {
- strcomp = git__strcasecmp;
-
- git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp);
- git_vector_sort(&idx2wd->deltas);
- } else if (idx2wd != NULL) {
- git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp);
- git_vector_sort(&idx2wd->deltas);
- }
-
- for (i = 0, j = 0; i < i_max || j < j_max; ) {
- h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL;
- i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL;
-
- cmp = !i2w ? -1 : !h2i ? 1 :
- strcomp(h2i->new_file.path, i2w->old_file.path);
-
- if (cmp < 0) {
- i++; i2w = NULL;
- } else if (cmp > 0) {
- j++; h2i = NULL;
- } else {
- i++; j++;
- }
-
- if ((error = cb(h2i, i2w, payload)) != 0) {
- giterr_set_after_callback(error);
- break;
- }
- }
-
- /* restore case-insensitive delta sort */
- if (icase_mismatch && h2i_icase) {
- git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp);
- git_vector_sort(&head2idx->deltas);
- }
-
- /* restore idx2wd sort by new path */
- if (idx2wd != NULL) {
- git_vector_set_cmp(&idx2wd->deltas,
- i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp);
- git_vector_sort(&idx2wd->deltas);
- }
-
- return error;
-}
-
-int git_diff__commit(
- git_diff **diff,
- git_repository *repo,
- const git_commit *commit,
- const git_diff_options *opts)
-{
- git_commit *parent = NULL;
- git_diff *commit_diff = NULL;
- git_tree *old_tree = NULL, *new_tree = NULL;
- size_t parents;
- int error = 0;
-
- if ((parents = git_commit_parentcount(commit)) > 1) {
- char commit_oidstr[GIT_OID_HEXSZ + 1];
-
- error = -1;
- giterr_set(GITERR_INVALID, "Commit %s is a merge commit",
- git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
- goto on_error;
- }
-
- if (parents > 0)
- if ((error = git_commit_parent(&parent, commit, 0)) < 0 ||
- (error = git_commit_tree(&old_tree, parent)) < 0)
- goto on_error;
-
- if ((error = git_commit_tree(&new_tree, commit)) < 0 ||
- (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0)
- goto on_error;
-
- *diff = commit_diff;
-
-on_error:
- git_tree_free(new_tree);
- git_tree_free(old_tree);
- git_commit_free(parent);
-
- return error;
-}
-
int git_diff_format_email__append_header_tobuf(
git_buf *out,
const git_oid *id,
@@ -1664,7 +137,8 @@ int git_diff_format_email__append_header_tobuf(
git_oid_fmt(idstr, id);
idstr[GIT_OID_HEXSZ] = '\0';
- if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), &author->when)) < 0)
+ if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
+ &author->when)) < 0)
return error;
error = git_buf_printf(out,
@@ -1683,7 +157,8 @@ int git_diff_format_email__append_header_tobuf(
if (total_patches == 1) {
error = git_buf_puts(out, "[PATCH] ");
} else {
- error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", patch_no, total_patches);
+ error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
+ patch_no, total_patches);
}
if (error < 0)
@@ -1741,16 +216,24 @@ int git_diff_format_email(
assert(out && diff && opts);
assert(opts->summary && opts->id && opts->author);
- GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options");
+ GITERR_CHECK_VERSION(opts,
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
+ "git_format_email_options");
+
+ ignore_marker = (opts->flags &
+ GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
- if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) {
+ if (!ignore_marker) {
if (opts->patch_no > opts->total_patches) {
- giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches);
+ giterr_set(GITERR_INVALID,
+ "patch %"PRIuZ" out of range. max %"PRIuZ,
+ opts->patch_no, opts->total_patches);
return -1;
}
if (opts->patch_no == 0) {
- giterr_set(GITERR_INVALID, "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
+ giterr_set(GITERR_INVALID,
+ "invalid patch no %"PRIuZ". should be >0", opts->patch_no);
return -1;
}
}
@@ -1775,8 +258,8 @@ int git_diff_format_email(
}
error = git_diff_format_email__append_header_tobuf(out,
- opts->id, opts->author, summary == NULL ? opts->summary : summary,
- opts->body, opts->patch_no, opts->total_patches, ignore_marker);
+ opts->id, opts->author, summary == NULL ? opts->summary : summary,
+ opts->body, opts->patch_no, opts->total_patches, ignore_marker);
if (error < 0)
goto on_error;
@@ -1809,7 +292,8 @@ int git_diff_commit_as_email(
const git_diff_options *diff_opts)
{
git_diff *diff = NULL;
- git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
+ git_diff_format_email_options opts =
+ GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
int error;
assert (out && repo && commit);
@@ -1854,3 +338,4 @@ int git_diff_format_email_init_options(
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
return 0;
}
+
diff --git a/src/diff.h b/src/diff.h
index 47743f88b..2c0e52ca2 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -22,67 +22,30 @@
#define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/"
-enum {
- GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
- GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
- GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
- GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
- GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
-};
-
-#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
-#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
-
-enum {
- GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
- GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
- GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
- GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
- GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
- GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
-
- GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
- GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
- GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
- GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
- GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
-};
-
-#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
-
-#define GIT_DIFF__VERBOSE (1 << 30)
+typedef enum {
+ GIT_DIFF_TYPE_UNKNOWN = 0,
+ GIT_DIFF_TYPE_GENERATED = 1,
+ GIT_DIFF_TYPE_PARSED = 2,
+} git_diff_origin_t;
struct git_diff {
git_refcount rc;
git_repository *repo;
+ git_diff_origin_t type;
git_diff_options opts;
- git_vector pathspec;
git_vector deltas; /* vector of git_diff_delta */
git_pool pool;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
- uint32_t diffcaps;
git_diff_perfdata perf;
- bool index_updated;
int (*strcomp)(const char *, const char *);
int (*strncomp)(const char *, const char *, size_t);
int (*pfxcomp)(const char *str, const char *pfx);
int (*entrycomp)(const void *a, const void *b);
-};
-
-extern void git_diff__cleanup_modes(
- uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
-extern void git_diff_addref(git_diff *diff);
-
-extern int git_diff_delta__cmp(const void *a, const void *b);
-extern int git_diff_delta__casecmp(const void *a, const void *b);
-
-extern const char *git_diff_delta__path(const git_diff_delta *delta);
-
-extern bool git_diff_delta__should_skip(
- const git_diff_options *opts, const git_diff_delta *delta);
+ void (*free_fn)(git_diff *diff);
+};
extern int git_diff_delta__format_file_header(
git_buf *out,
@@ -91,84 +54,11 @@ extern int git_diff_delta__format_file_header(
const char *newpfx,
int oid_strlen);
-extern int git_diff__oid_for_file(
- git_oid *out, git_diff *, const char *, uint16_t, git_off_t);
-extern int git_diff__oid_for_entry(
- git_oid *out, git_diff *, const git_index_entry *, uint16_t, const git_oid *update);
-
-extern int git_diff__from_iterators(
- git_diff **diff_ptr,
- git_repository *repo,
- git_iterator *old_iter,
- git_iterator *new_iter,
- const git_diff_options *opts);
-
-extern int git_diff__paired_foreach(
- git_diff *idx2head,
- git_diff *wd2idx,
- int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
- void *payload);
-
-extern int git_diff_find_similar__hashsig_for_file(
- void **out, const git_diff_file *f, const char *path, void *p);
-
-extern int git_diff_find_similar__hashsig_for_buf(
- void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
-
-extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
-
-extern int git_diff_find_similar__calc_similarity(
- int *score, void *siga, void *sigb, void *payload);
-
-extern int git_diff__commit(
- git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
-
-/* Merge two `git_diff`s according to the callback given by `cb`. */
-
-typedef git_diff_delta *(*git_diff__merge_cb)(
- const git_diff_delta *left,
- const git_diff_delta *right,
- git_pool *pool);
-
-extern int git_diff__merge(
- git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
-
-extern git_diff_delta *git_diff__merge_like_cgit(
- const git_diff_delta *a,
- const git_diff_delta *b,
- git_pool *pool);
-
-/* Duplicate a `git_diff_delta` out of the `git_pool` */
-extern git_diff_delta *git_diff__delta_dup(
- const git_diff_delta *d, git_pool *pool);
-
-/*
- * Sometimes a git_diff_file will have a zero size; this attempts to
- * fill in the size without loading the blob if possible. If that is
- * not possible, then it will return the git_odb_object that had to be
- * loaded and the caller can use it or dispose of it as needed.
- */
-GIT_INLINE(int) git_diff_file__resolve_zero_size(
- git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
-{
- int error;
- git_odb *odb;
- size_t len;
- git_otype type;
-
- if ((error = git_repository_odb(&odb, repo)) < 0)
- return error;
-
- error = git_odb__read_header_or_object(
- odb_obj, &len, &type, odb, &file->id);
-
- git_odb_free(odb);
-
- if (!error)
- file->size = (git_off_t)len;
+extern int git_diff_delta__cmp(const void *a, const void *b);
+extern int git_diff_delta__casecmp(const void *a, const void *b);
- return error;
-}
+extern int git_diff__entry_cmp(const void *a, const void *b);
+extern int git_diff__entry_icmp(const void *a, const void *b);
#endif
diff --git a/src/diff_driver.c b/src/diff_driver.c
index 2ead551c2..14a898c4f 100644
--- a/src/diff_driver.c
+++ b/src/diff_driver.c
@@ -9,7 +9,6 @@
#include "git2/attr.h"
#include "diff.h"
-#include "diff_patch.h"
#include "diff_driver.h"
#include "strmap.h"
#include "map.h"
diff --git a/src/diff_file.c b/src/diff_file.c
index ecc34cf55..cc1029038 100644
--- a/src/diff_file.c
+++ b/src/diff_file.c
@@ -8,6 +8,7 @@
#include "git2/blob.h"
#include "git2/submodule.h"
#include "diff.h"
+#include "diff_generate.h"
#include "diff_file.h"
#include "odb.h"
#include "fileops.h"
@@ -149,12 +150,14 @@ int git_diff_file_content__init_from_src(
if (src->blob) {
fc->file->size = git_blob_rawsize(src->blob);
git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
+ fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = (size_t)fc->file->size;
fc->map.data = (char *)git_blob_rawcontent(src->blob);
} else {
fc->file->size = src->buflen;
git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB);
+ fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = src->buflen;
fc->map.data = (char *)src->buf;
diff --git a/src/diff_generate.c b/src/diff_generate.c
new file mode 100644
index 000000000..a996bf156
--- /dev/null
+++ b/src/diff_generate.c
@@ -0,0 +1,1611 @@
+/*
+ * 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;
+}
+
diff --git a/src/diff_generate.h b/src/diff_generate.h
new file mode 100644
index 000000000..63e7f0532
--- /dev/null
+++ b/src/diff_generate.h
@@ -0,0 +1,123 @@
+/*
+ * 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
+
diff --git a/src/diff_parse.c b/src/diff_parse.c
new file mode 100644
index 000000000..ffdc8df88
--- /dev/null
+++ b/src/diff_parse.c
@@ -0,0 +1,105 @@
+/*
+ * 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;
+}
+
diff --git a/src/diff_patch.h b/src/diff_patch.h
deleted file mode 100644
index 7b4dacdde..000000000
--- a/src/diff_patch.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) the libgit2 contributors. All rights reserved.
- *
- * This file is part of libgit2, distributed under the GNU GPL v2 with
- * a Linking Exception. For full terms see the included COPYING file.
- */
-#ifndef INCLUDE_diff_patch_h__
-#define INCLUDE_diff_patch_h__
-
-#include "common.h"
-#include "diff.h"
-#include "diff_file.h"
-#include "array.h"
-#include "git2/patch.h"
-
- /* cached information about a hunk in a diff */
-typedef struct diff_patch_hunk {
- git_diff_hunk hunk;
- size_t line_start;
- size_t line_count;
-} diff_patch_hunk;
-
-enum {
- GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
- GIT_DIFF_PATCH_INITIALIZED = (1 << 1),
- GIT_DIFF_PATCH_LOADED = (1 << 2),
- /* the two sides are different */
- GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
- /* the difference between the two sides has been computed */
- GIT_DIFF_PATCH_DIFFED = (1 << 4),
- GIT_DIFF_PATCH_FLATTENED = (1 << 5),
-};
-
-struct git_patch {
- git_refcount rc;
- git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
- git_diff_options diff_opts;
- git_diff_delta *delta;
- size_t delta_index;
- git_diff_file_content ofile;
- git_diff_file_content nfile;
- uint32_t flags;
- git_diff_binary binary;
- git_array_t(diff_patch_hunk) hunks;
- git_array_t(git_diff_line) lines;
- size_t content_size, context_size, header_size;
- git_pool flattened;
-};
-
-extern git_diff *git_patch__diff(git_patch *);
-
-extern git_diff_driver *git_patch__driver(git_patch *);
-
-extern void git_patch__old_data(char **, size_t *, git_patch *);
-extern void git_patch__new_data(char **, size_t *, git_patch *);
-
-extern int git_patch__invoke_callbacks(
- git_patch *patch,
- git_diff_file_cb file_cb,
- git_diff_binary_cb binary_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_line_cb line_cb,
- void *payload);
-
-typedef struct git_diff_output git_diff_output;
-struct git_diff_output {
- /* these callbacks are issued with the diff data */
- git_diff_file_cb file_cb;
- git_diff_binary_cb binary_cb;
- git_diff_hunk_cb hunk_cb;
- git_diff_line_cb data_cb;
- void *payload;
-
- /* this records the actual error in cases where it may be obscured */
- int error;
-
- /* this callback is used to do the diff and drive the other callbacks.
- * see diff_xdiff.h for how to use this in practice for now.
- */
- int (*diff_cb)(git_diff_output *output, git_patch *patch);
-};
-
-#endif
diff --git a/src/diff_print.c b/src/diff_print.c
index dae9e341d..f72ca8935 100644
--- a/src/diff_print.c
+++ b/src/diff_print.c
@@ -6,7 +6,8 @@
*/
#include "common.h"
#include "diff.h"
-#include "diff_patch.h"
+#include "diff_file.h"
+#include "patch_generate.h"
#include "fileops.h"
#include "zstream.h"
#include "blob.h"
@@ -14,19 +15,19 @@
#include "git2/sys/diff.h"
typedef struct {
- git_diff *diff;
git_diff_format_t format;
git_diff_line_cb print_cb;
void *payload;
+
git_buf *buf;
- uint32_t flags;
- int oid_strlen;
git_diff_line line;
- unsigned int
- content_loaded : 1,
- content_allocated : 1;
- git_diff_file_content *ofile;
- git_diff_file_content *nfile;
+
+ const char *old_prefix;
+ const char *new_prefix;
+ uint32_t flags;
+ int id_strlen;
+
+ int (*strcomp)(const char *, const char *);
} diff_print_info;
static int diff_print_info_init__common(
@@ -42,17 +43,15 @@ static int diff_print_info_init__common(
pi->payload = payload;
pi->buf = out;
- if (!pi->oid_strlen) {
+ if (!pi->id_strlen) {
if (!repo)
- pi->oid_strlen = GIT_ABBREV_DEFAULT;
- else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0)
+ pi->id_strlen = GIT_ABBREV_DEFAULT;
+ else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0)
return -1;
}
- pi->oid_strlen += 1; /* for NUL byte */
-
- if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
- pi->oid_strlen = GIT_OID_HEXSZ + 1;
+ if (pi->id_strlen > GIT_OID_HEXSZ)
+ pi->id_strlen = GIT_OID_HEXSZ;
memset(&pi->line, 0, sizeof(pi->line));
pi->line.old_lineno = -1;
@@ -74,11 +73,13 @@ static int diff_print_info_init_fromdiff(
memset(pi, 0, sizeof(diff_print_info));
- pi->diff = diff;
-
if (diff) {
pi->flags = diff->opts.flags;
- pi->oid_strlen = diff->opts.id_abbrev;
+ pi->id_strlen = diff->opts.id_abbrev;
+ pi->old_prefix = diff->opts.old_prefix;
+ pi->new_prefix = diff->opts.new_prefix;
+
+ pi->strcomp = diff->strcomp;
}
return diff_print_info_init__common(pi, out, repo, format, cb, payload);
@@ -92,24 +93,16 @@ static int diff_print_info_init_frompatch(
git_diff_line_cb cb,
void *payload)
{
- git_repository *repo;
-
assert(patch);
- repo = patch->diff ? patch->diff->repo : NULL;
-
memset(pi, 0, sizeof(diff_print_info));
- pi->diff = patch->diff;
-
pi->flags = patch->diff_opts.flags;
- pi->oid_strlen = patch->diff_opts.id_abbrev;
-
- pi->content_loaded = 1;
- pi->ofile = &patch->ofile;
- pi->nfile = &patch->nfile;
+ pi->id_strlen = patch->diff_opts.id_abbrev;
+ pi->old_prefix = patch->diff_opts.old_prefix;
+ pi->new_prefix = patch->diff_opts.new_prefix;
- return diff_print_info_init__common(pi, out, repo, format, cb, payload);
+ return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
}
static char diff_pick_suffix(int mode)
@@ -173,8 +166,8 @@ static int diff_print_one_name_status(
diff_print_info *pi = data;
git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
- int (*strcomp)(const char *, const char *) =
- pi->diff ? pi->diff->strcomp : git__strcmp;
+ int(*strcomp)(const char *, const char *) = pi->strcomp ?
+ pi->strcomp : git__strcmp;
GIT_UNUSED(progress);
@@ -213,6 +206,7 @@ static int diff_print_one_raw(
{
diff_print_info *pi = data;
git_buf *out = pi->buf;
+ int id_abbrev;
char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
@@ -223,11 +217,21 @@ static int diff_print_one_raw(
git_buf_clear(out);
- git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id);
- git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
+ id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
+ delta->new_file.id_abbrev;
+
+ if (pi->id_strlen > id_abbrev) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ id_abbrev, pi->id_strlen);
+ return -1;
+ }
+
+ git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
+ git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
git_buf_printf(
- out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
+ out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
@@ -252,53 +256,140 @@ static int diff_print_one_raw(
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
+static int diff_print_modes(
+ git_buf *out, const git_diff_delta *delta)
+{
+ git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+ git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
+
+ return git_buf_oom(out) ? -1 : 0;
+}
+
static int diff_print_oid_range(
- git_buf *out, const git_diff_delta *delta, int oid_strlen)
+ git_buf *out, const git_diff_delta *delta, int id_strlen)
{
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
- git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
- git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
+ if (delta->old_file.mode &&
+ id_strlen > delta->old_file.id_abbrev) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ delta->old_file.id_abbrev, id_strlen);
+ return -1;
+ }
+
+ if ((delta->new_file.mode &&
+ id_strlen > delta->new_file.id_abbrev)) {
+ giterr_set(GITERR_PATCH,
+ "The patch input contains %d id characters (cannot print %d)",
+ delta->new_file.id_abbrev, id_strlen);
+ return -1;
+ }
+
+ git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
+ git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
- /* TODO: Match git diff more closely */
if (delta->old_file.mode == delta->new_file.mode) {
git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode);
} else {
- if (delta->old_file.mode == 0) {
+ if (delta->old_file.mode == 0)
git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
- } else if (delta->new_file.mode == 0) {
+ else if (delta->new_file.mode == 0)
git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
- } else {
- git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
- git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
- }
+ else
+ diff_print_modes(out, delta);
+
git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
}
return git_buf_oom(out) ? -1 : 0;
}
+static int diff_delta_format_path(
+ git_buf *out, const char *prefix, const char *filename)
+{
+ if (git_buf_joinpath(out, prefix, filename) < 0)
+ return -1;
+
+ return git_buf_quote(out);
+}
+
static int diff_delta_format_with_paths(
git_buf *out,
const git_diff_delta *delta,
- const char *oldpfx,
- const char *newpfx,
- const char *template)
+ const char *template,
+ const char *oldpath,
+ const char *newpath)
{
- const char *oldpath = delta->old_file.path;
- const char *newpath = delta->new_file.path;
-
- if (git_oid_iszero(&delta->old_file.id)) {
- oldpfx = "";
+ if (git_oid_iszero(&delta->old_file.id))
oldpath = "/dev/null";
- }
- if (git_oid_iszero(&delta->new_file.id)) {
- newpfx = "";
+
+ if (git_oid_iszero(&delta->new_file.id))
newpath = "/dev/null";
+
+ return git_buf_printf(out, template, oldpath, newpath);
+}
+
+int diff_delta_format_similarity_header(
+ git_buf *out,
+ const git_diff_delta *delta)
+{
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+ const char *type;
+ int error = 0;
+
+ if (delta->similarity > 100) {
+ giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity);
+ error = -1;
+ goto done;
}
- return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
+ if (delta->status == GIT_DELTA_RENAMED)
+ type = "rename";
+ else if (delta->status == GIT_DELTA_COPIED)
+ type = "copy";
+ else
+ abort();
+
+ if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
+ (error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
+ (error = git_buf_quote(&old_path)) < 0 ||
+ (error = git_buf_quote(&new_path)) < 0)
+ goto done;
+
+ git_buf_printf(out,
+ "similarity index %d%%\n"
+ "%s from %s\n"
+ "%s to %s\n",
+ delta->similarity,
+ type, old_path.ptr,
+ type, new_path.ptr);
+
+ if (git_buf_oom(out))
+ error = -1;
+
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+
+ return error;
+}
+
+static bool delta_is_unchanged(const git_diff_delta *delta)
+{
+ if (git_oid_iszero(&delta->old_file.id) &&
+ git_oid_iszero(&delta->new_file.id))
+ return true;
+
+ if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
+ delta->new_file.mode == GIT_FILEMODE_COMMIT)
+ return false;
+
+ if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
+ return true;
+
+ return false;
}
int git_diff_delta__format_file_header(
@@ -306,27 +397,56 @@ int git_diff_delta__format_file_header(
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
- int oid_strlen)
+ int id_strlen)
{
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+ bool unchanged = delta_is_unchanged(delta);
+ int error = 0;
+
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
- if (!oid_strlen)
- oid_strlen = GIT_ABBREV_DEFAULT + 1;
+ if (!id_strlen)
+ id_strlen = GIT_ABBREV_DEFAULT;
+
+ if ((error = diff_delta_format_path(
+ &old_path, oldpfx, delta->old_file.path)) < 0 ||
+ (error = diff_delta_format_path(
+ &new_path, newpfx, delta->new_file.path)) < 0)
+ goto done;
git_buf_clear(out);
- git_buf_printf(out, "diff --git %s%s %s%s\n",
- oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
+ git_buf_printf(out, "diff --git %s %s\n",
+ old_path.ptr, new_path.ptr);
- GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
+ if (delta->status == GIT_DELTA_RENAMED ||
+ (delta->status == GIT_DELTA_COPIED && unchanged)) {
+ if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
+ goto done;
+ }
- if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
- diff_delta_format_with_paths(
- out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
+ if (!unchanged) {
+ if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0)
+ goto done;
- return git_buf_oom(out) ? -1 : 0;
+ if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+ diff_delta_format_with_paths(out, delta,
+ "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
+ }
+
+ if (unchanged && delta->old_file.mode != delta->new_file.mode)
+ diff_print_modes(out, delta);
+
+ if (git_buf_oom(out))
+ error = -1;
+
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
+
+ return error;
}
static int format_binary(
@@ -367,37 +487,30 @@ static int format_binary(
return 0;
}
-static int diff_print_load_content(
- diff_print_info *pi,
- git_diff_delta *delta)
+static int diff_print_patch_file_binary_noshow(
+ diff_print_info *pi, git_diff_delta *delta,
+ const char *old_pfx, const char *new_pfx)
{
- git_diff_file_content *ofile, *nfile;
+ git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
int error;
- assert(pi->diff);
-
- ofile = git__calloc(1, sizeof(git_diff_file_content));
- nfile = git__calloc(1, sizeof(git_diff_file_content));
+ 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;
- GITERR_CHECK_ALLOC(ofile);
- GITERR_CHECK_ALLOC(nfile);
- if ((error = git_diff_file_content__init_from_diff(
- ofile, pi->diff, delta, true)) < 0 ||
- (error = git_diff_file_content__init_from_diff(
- nfile, pi->diff, delta, true)) < 0) {
-
- git__free(ofile);
- git__free(nfile);
- return error;
- }
+ pi->line.num_lines = 1;
+ error = diff_delta_format_with_paths(
+ pi->buf, delta, "Binary files %s and %s differ\n",
+ old_path.ptr, new_path.ptr);
- pi->content_loaded = 1;
- pi->content_allocated = 1;
- pi->ofile = ofile;
- pi->nfile = nfile;
+done:
+ git_buf_free(&old_path);
+ git_buf_free(&new_path);
- return 0;
+ return error;
}
static int diff_print_patch_file_binary(
@@ -409,11 +522,11 @@ static int diff_print_patch_file_binary(
int error;
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
- goto noshow;
+ return diff_print_patch_file_binary_noshow(
+ pi, delta, old_pfx, new_pfx);
- if (!pi->content_loaded &&
- (error = diff_print_load_content(pi, delta)) < 0)
- return error;
+ if (binary->new_file.datalen == 0 && binary->old_file.datalen == 0)
+ return 0;
pre_binary_size = pi->buf->size;
git_buf_printf(pi->buf, "GIT binary patch\n");
@@ -427,18 +540,14 @@ static int diff_print_patch_file_binary(
if (error == GIT_EBUFS) {
giterr_clear();
git_buf_truncate(pi->buf, pre_binary_size);
- goto noshow;
+
+ return diff_print_patch_file_binary_noshow(
+ pi, delta, old_pfx, new_pfx);
}
}
pi->line.num_lines++;
return error;
-
-noshow:
- pi->line.num_lines = 1;
- return diff_delta_format_with_paths(
- pi->buf, delta, old_pfx, new_pfx,
- "Binary files %s%s and %s%s differ\n");
}
static int diff_print_patch_file(
@@ -447,15 +556,15 @@ static int diff_print_patch_file(
int error;
diff_print_info *pi = data;
const char *oldpfx =
- pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx =
- pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+ pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
(pi->flags & GIT_DIFF_FORCE_BINARY);
bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
- int oid_strlen = binary && show_binary ?
- GIT_OID_HEXSZ + 1 : pi->oid_strlen;
+ int id_strlen = binary && show_binary ?
+ GIT_OID_HEXSZ : pi->id_strlen;
GIT_UNUSED(progress);
@@ -468,7 +577,7 @@ static int diff_print_patch_file(
return 0;
if ((error = git_diff_delta__format_file_header(
- pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
+ pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0)
return error;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
@@ -485,9 +594,9 @@ static int diff_print_patch_binary(
{
diff_print_info *pi = data;
const char *old_pfx =
- pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+ pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *new_pfx =
- pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+ pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
int error;
git_buf_clear(pi->buf);
@@ -582,43 +691,11 @@ int git_diff_print(
giterr_set_after_callback_function(error, "git_diff_print");
}
- git__free(pi.nfile);
- git__free(pi.ofile);
-
git_buf_free(&buf);
return error;
}
-/* print a git_patch to an output callback */
-int git_patch_print(
- git_patch *patch,
- git_diff_line_cb print_cb,
- void *payload)
-{
- int error;
- git_buf temp = GIT_BUF_INIT;
- diff_print_info pi;
-
- assert(patch && print_cb);
-
- if (!(error = diff_print_info_init_frompatch(
- &pi, &temp, patch,
- GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
- {
- error = git_patch__invoke_callbacks(
- patch, diff_print_patch_file, diff_print_patch_binary,
- diff_print_patch_hunk, diff_print_patch_line, &pi);
-
- if (error) /* make sure error message is set */
- giterr_set_after_callback_function(error, "git_patch_print");
- }
-
- git_buf_free(&temp);
-
- return error;
-}
-
int git_diff_print_callback__to_buf(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
@@ -659,6 +736,46 @@ int git_diff_print_callback__to_file_handle(
return 0;
}
+/* print a git_diff to a git_buf */
+int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
+{
+ assert(out && diff);
+ git_buf_sanitize(out);
+ return git_diff_print(
+ diff, format, git_diff_print_callback__to_buf, out);
+}
+
+/* print a git_patch to an output callback */
+int git_patch_print(
+ git_patch *patch,
+ git_diff_line_cb print_cb,
+ void *payload)
+{
+ int error;
+ git_buf temp = GIT_BUF_INIT;
+ diff_print_info pi;
+
+ assert(patch && print_cb);
+
+ if (!(error = diff_print_info_init_frompatch(
+ &pi, &temp, patch,
+ GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
+ {
+ error = git_patch__invoke_callbacks(
+ patch,
+ diff_print_patch_file, diff_print_patch_binary,
+ diff_print_patch_hunk, diff_print_patch_line,
+ &pi);
+
+ if (error) /* make sure error message is set */
+ giterr_set_after_callback_function(error, "git_patch_print");
+ }
+
+ git_buf_free(&temp);
+
+ return error;
+}
+
/* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch)
{
diff --git a/src/diff_stats.c b/src/diff_stats.c
index 42ccbfb87..9c186d793 100644
--- a/src/diff_stats.c
+++ b/src/diff_stats.c
@@ -7,7 +7,7 @@
#include "common.h"
#include "vector.h"
#include "diff.h"
-#include "diff_patch.h"
+#include "patch_generate.h"
#define DIFF_RENAME_FILE_SEPARATOR " => "
#define STATS_FULL_MIN_SCALE 7
@@ -190,8 +190,9 @@ int git_diff_get_stats(
break;
/* keep a count of renames because it will affect formatting */
- delta = git_patch_get_delta(patch);
+ delta = patch->delta;
+ /* TODO ugh */
namelen = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
namelen += strlen(delta->old_file.path);
diff --git a/src/diff_tform.c b/src/diff_tform.c
index 6a6a62811..e8848bd45 100644
--- a/src/diff_tform.c
+++ b/src/diff_tform.c
@@ -11,6 +11,7 @@
#include "git2/sys/hashsig.h"
#include "diff.h"
+#include "diff_generate.h"
#include "path.h"
#include "fileops.h"
#include "config.h"
diff --git a/src/diff_tform.h b/src/diff_tform.h
new file mode 100644
index 000000000..5bd9712d9
--- /dev/null
+++ b/src/diff_tform.h
@@ -0,0 +1,22 @@
+/*
+ * 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
+
diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c
index 1057df3aa..5bd6381b5 100644
--- a/src/diff_xdiff.c
+++ b/src/diff_xdiff.c
@@ -8,8 +8,8 @@
#include "common.h"
#include "diff.h"
#include "diff_driver.h"
-#include "diff_patch.h"
#include "diff_xdiff.h"
+#include "patch_generate.h"
static int git_xdiff_scan_int(const char **str, int *value)
{
@@ -56,7 +56,7 @@ fail:
typedef struct {
git_xdiff_output *xo;
- git_patch *patch;
+ git_patch_generated *patch;
git_diff_hunk hunk;
int old_lineno, new_lineno;
mmfile_t xd_old_data, xd_new_data;
@@ -110,9 +110,9 @@ static int diff_update_lines(
static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
{
git_xdiff_info *info = priv;
- git_patch *patch = info->patch;
- const git_diff_delta *delta = git_patch_get_delta(patch);
- git_diff_output *output = &info->xo->output;
+ git_patch_generated *patch = info->patch;
+ const git_diff_delta *delta = patch->base.delta;
+ git_patch_generated_output *output = &info->xo->output;
git_diff_line line;
if (len == 1) {
@@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
return output->error;
}
-static int git_xdiff(git_diff_output *output, git_patch *patch)
+static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch)
{
git_xdiff_output *xo = (git_xdiff_output *)output;
git_xdiff_info info;
@@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
xo->callback.priv = &info;
git_diff_find_context_init(
- &xo->config.find_func, &findctxt, git_patch__driver(patch));
+ &xo->config.find_func, &findctxt, git_patch_generated_driver(patch));
xo->config.find_func_priv = &findctxt;
if (xo->config.find_func != NULL)
@@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch)
* updates are needed to xo->params.flags
*/
- git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
- git_patch__new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
+ git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
+ git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h
index 98e11b2cb..88375986b 100644
--- a/src/diff_xdiff.h
+++ b/src/diff_xdiff.h
@@ -8,20 +8,20 @@
#define INCLUDE_diff_xdiff_h__
#include "diff.h"
-#include "diff_patch.h"
#include "xdiff/xdiff.h"
+#include "patch_generate.h"
/* xdiff cannot cope with large files. these files should not be passed to
* xdiff. callers should treat these large files as binary.
*/
#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023)
-/* A git_xdiff_output is a git_diff_output with extra fields necessary
- * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field
- * of the output to use xdiff to generate the diffs.
+/* A git_xdiff_output is a git_patch_generate_output with extra fields
+ * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb
+ * field of the output to use xdiff to generate the diffs.
*/
typedef struct {
- git_diff_output output;
+ git_patch_generated_output output;
xdemitconf_t config;
xpparam_t params;
diff --git a/src/merge.c b/src/merge.c
index b93851b7e..6934aa731 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -18,6 +18,8 @@
#include "iterator.h"
#include "refs.h"
#include "diff.h"
+#include "diff_generate.h"
+#include "diff_tform.h"
#include "checkout.h"
#include "tree.h"
#include "blob.h"
diff --git a/src/odb.c b/src/odb.c
index 87c1bb6b1..02391c4ac 100644
--- a/src/odb.c
+++ b/src/odb.c
@@ -12,7 +12,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
-#include "delta-apply.h"
+#include "delta.h"
#include "filter.h"
#include "repository.h"
diff --git a/src/odb_loose.c b/src/odb_loose.c
index 3c33160d0..228d4c334 100644
--- a/src/odb_loose.c
+++ b/src/odb_loose.c
@@ -12,7 +12,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
-#include "delta-apply.h"
+#include "delta.h"
#include "filebuf.h"
#include "git2/odb_backend.h"
diff --git a/src/odb_pack.c b/src/odb_pack.c
index 5a57864ad..244e12bc3 100644
--- a/src/odb_pack.c
+++ b/src/odb_pack.c
@@ -13,7 +13,7 @@
#include "fileops.h"
#include "hash.h"
#include "odb.h"
-#include "delta-apply.h"
+#include "delta.h"
#include "sha1_lookup.h"
#include "mwindow.h"
#include "pack.h"
diff --git a/src/pack-objects.c b/src/pack-objects.c
index 29231e028..288aebb20 100644
--- a/src/pack-objects.c
+++ b/src/pack-objects.c
@@ -144,7 +144,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo)
pb->nr_threads = 1; /* do not spawn any thread by default */
if (git_hash_ctx_init(&pb->ctx) < 0 ||
- git_zstream_init(&pb->zstream) < 0 ||
+ git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 ||
git_repository_odb(&pb->odb, repo) < 0 ||
packbuilder_config(pb) < 0)
goto on_error;
@@ -274,6 +274,7 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_object *src = NULL, *trg = NULL;
unsigned long delta_size;
void *delta_buf;
+ int error;
*out = NULL;
@@ -281,12 +282,15 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po)
git_odb_read(&trg, odb, &po->id) < 0)
goto on_error;
- delta_buf = git_delta(
- git_odb_object_data(src), (unsigned long)git_odb_object_size(src),
- git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg),
- &delta_size, 0);
+ error = git_delta(&delta_buf, &delta_size,
+ git_odb_object_data(src), git_odb_object_size(src),
+ git_odb_object_data(trg), git_odb_object_size(trg),
+ 0);
+
+ if (error < 0 && error != GIT_EBUFS)
+ goto on_error;
- if (!delta_buf || delta_size != po->delta_size) {
+ if (error == GIT_EBUFS || delta_size != po->delta_size) {
giterr_set(GITERR_INVALID, "Delta size changed");
goto on_error;
}
@@ -815,16 +819,14 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg,
*mem_usage += sz;
}
if (!src->index) {
- src->index = git_delta_create_index(src->data, src_size);
- if (!src->index)
+ if (git_delta_index_init(&src->index, src->data, src_size) < 0)
return 0; /* suboptimal pack - out of memory */
- *mem_usage += git_delta_sizeof_index(src->index);
+ *mem_usage += git_delta_index_size(src->index);
}
- delta_buf = git_delta_create(src->index, trg->data, trg_size,
- &delta_size, max_size);
- if (!delta_buf)
+ if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size,
+ max_size) < 0)
return 0;
if (trg_object->delta) {
@@ -885,9 +887,14 @@ static unsigned int check_delta_limit(git_pobject *me, unsigned int n)
static unsigned long free_unpacked(struct unpacked *n)
{
- unsigned long freed_mem = git_delta_sizeof_index(n->index);
- git_delta_free_index(n->index);
+ unsigned long freed_mem = 0;
+
+ if (n->index) {
+ freed_mem += git_delta_index_size(n->index);
+ git_delta_index_free(n->index);
+ }
n->index = NULL;
+
if (n->data) {
freed_mem += (unsigned long)n->object->size;
git__free(n->data);
diff --git a/src/pack.c b/src/pack.c
index 6a700e29f..310f00fa3 100644
--- a/src/pack.c
+++ b/src/pack.c
@@ -8,7 +8,7 @@
#include "common.h"
#include "odb.h"
#include "pack.h"
-#include "delta-apply.h"
+#include "delta.h"
#include "sha1_lookup.h"
#include "mwindow.h"
#include "fileops.h"
@@ -505,7 +505,7 @@ int git_packfile_resolve_header(
git_mwindow_close(&w_curs);
if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0)
return error;
- error = git__delta_read_header_fromstream(&base_size, size_p, &stream);
+ error = git_delta_read_header_fromstream(&base_size, size_p, &stream);
git_packfile_stream_free(&stream);
if (error < 0)
return error;
@@ -730,8 +730,9 @@ int git_packfile_unpack(
obj->len = 0;
obj->type = GIT_OBJ_BAD;
- error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len);
+ error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len);
obj->type = base_type;
+
/*
* We usually don't want to free the base at this
* point, as we put it into the cache in the previous
diff --git a/src/patch.c b/src/patch.c
new file mode 100644
index 000000000..e14ffa9c0
--- /dev/null
+++ b/src/patch.c
@@ -0,0 +1,207 @@
+#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);
+}
diff --git a/src/patch.h b/src/patch.h
new file mode 100644
index 000000000..525ae7007
--- /dev/null
+++ b/src/patch.h
@@ -0,0 +1,66 @@
+/*
+* 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
diff --git a/src/diff_patch.c b/src/patch_generate.c
index 50faa3b3f..98c14923d 100644
--- a/src/diff_patch.c
+++ b/src/patch_generate.c
@@ -7,49 +7,78 @@
#include "common.h"
#include "git2/blob.h"
#include "diff.h"
+#include "diff_generate.h"
#include "diff_file.h"
#include "diff_driver.h"
-#include "diff_patch.h"
+#include "patch_generate.h"
#include "diff_xdiff.h"
#include "delta.h"
#include "zstream.h"
#include "fileops.h"
static void diff_output_init(
- git_diff_output*, const git_diff_options*, git_diff_file_cb,
+ git_patch_generated_output *, const git_diff_options *, git_diff_file_cb,
git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*);
-static void diff_output_to_patch(git_diff_output *, git_patch *);
+static void diff_output_to_patch(
+ git_patch_generated_output *, git_patch_generated *);
-static void diff_patch_update_binary(git_patch *patch)
+static void patch_generated_free(git_patch *p)
{
- if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
+ git_patch_generated *patch = (git_patch_generated *)p;
+
+ git_array_clear(patch->base.lines);
+ git_array_clear(patch->base.hunks);
+
+ git__free((char *)patch->base.binary.old_file.data);
+ git__free((char *)patch->base.binary.new_file.data);
+
+ git_diff_file_content__clear(&patch->ofile);
+ git_diff_file_content__clear(&patch->nfile);
+
+ git_diff_free(patch->diff); /* decrements refcount */
+ patch->diff = NULL;
+
+ git_pool_clear(&patch->flattened);
+
+ git__free((char *)patch->base.diff_opts.old_prefix);
+ git__free((char *)patch->base.diff_opts.new_prefix);
+
+ if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED)
+ git__free(patch);
+}
+
+static void patch_generated_update_binary(git_patch_generated *patch)
+{
+ if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
return;
if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 ||
(patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0)
- patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE ||
- patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
- patch->delta->flags |= GIT_DIFF_FLAG_BINARY;
+ patch->nfile.file->size > GIT_XDIFF_MAX_SIZE)
+ patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY;
else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 &&
- (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
- patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
+ (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0)
+ patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}
-static void diff_patch_init_common(git_patch *patch)
+static void patch_generated_init_common(git_patch_generated *patch)
{
- diff_patch_update_binary(patch);
+ patch->base.free_fn = patch_generated_free;
- patch->flags |= GIT_DIFF_PATCH_INITIALIZED;
+ patch_generated_update_binary(patch);
+
+ patch->flags |= GIT_PATCH_GENERATED_INITIALIZED;
if (patch->diff)
git_diff_addref(patch->diff);
}
-static int diff_patch_normalize_options(
+static int patch_generated_normalize_options(
git_diff_options *out,
const git_diff_options *opts)
{
@@ -75,38 +104,40 @@ static int diff_patch_normalize_options(
return 0;
}
-static int diff_patch_init_from_diff(
- git_patch *patch, git_diff *diff, size_t delta_index)
+static int patch_generated_init(
+ git_patch_generated *patch, git_diff *diff, size_t delta_index)
{
int error = 0;
memset(patch, 0, sizeof(*patch));
- patch->diff = diff;
- patch->delta = git_vector_get(&diff->deltas, delta_index);
+
+ patch->diff = diff;
+ patch->base.repo = diff->repo;
+ patch->base.delta = git_vector_get(&diff->deltas, delta_index);
patch->delta_index = delta_index;
- if ((error = diff_patch_normalize_options(
- &patch->diff_opts, &diff->opts)) < 0 ||
+ if ((error = patch_generated_normalize_options(
+ &patch->base.diff_opts, &diff->opts)) < 0 ||
(error = git_diff_file_content__init_from_diff(
- &patch->ofile, diff, patch->delta, true)) < 0 ||
+ &patch->ofile, diff, patch->base.delta, true)) < 0 ||
(error = git_diff_file_content__init_from_diff(
- &patch->nfile, diff, patch->delta, false)) < 0)
+ &patch->nfile, diff, patch->base.delta, false)) < 0)
return error;
- diff_patch_init_common(patch);
+ patch_generated_init_common(patch);
return 0;
}
-static int diff_patch_alloc_from_diff(
- git_patch **out, git_diff *diff, size_t delta_index)
+static int patch_generated_alloc_from_diff(
+ git_patch_generated **out, git_diff *diff, size_t delta_index)
{
int error;
- git_patch *patch = git__calloc(1, sizeof(git_patch));
+ git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated));
GITERR_CHECK_ALLOC(patch);
- if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) {
- patch->flags |= GIT_DIFF_PATCH_ALLOCATED;
+ if (!(error = patch_generated_init(patch, diff, delta_index))) {
+ patch->flags |= GIT_PATCH_GENERATED_ALLOCATED;
GIT_REFCOUNT_INC(patch);
} else {
git__free(patch);
@@ -117,27 +148,27 @@ static int diff_patch_alloc_from_diff(
return error;
}
-GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file)
+GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file)
{
- if ((patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
+ if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0)
return false;
return (file->flags & GIT_DIFF_FLAG_BINARY) != 0;
}
-static bool diff_patch_diffable(git_patch *patch)
+static bool patch_generated_diffable(git_patch_generated *patch)
{
size_t olen, nlen;
- if (patch->delta->status == GIT_DELTA_UNMODIFIED)
+ if (patch->base.delta->status == GIT_DELTA_UNMODIFIED)
return false;
/* if we've determined this to be binary (and we are not showing binary
* data) then we have skipped loading the map data. instead, query the
* file data itself.
*/
- if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
- (patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
+ if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
+ (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) {
olen = (size_t)patch->ofile.file->size;
nlen = (size_t)patch->nfile.file->size;
} else {
@@ -154,12 +185,12 @@ static bool diff_patch_diffable(git_patch *patch)
!git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id));
}
-static int diff_patch_load(git_patch *patch, git_diff_output *output)
+static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output)
{
int error = 0;
bool incomplete_data;
- if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
+ if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0)
return 0;
/* if no hunk and data callbacks and user doesn't care if data looks
@@ -180,13 +211,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
*/
if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
- &patch->ofile, &patch->diff_opts)) < 0 ||
+ &patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file))
goto cleanup;
}
if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
- &patch->nfile, &patch->diff_opts)) < 0 ||
+ &patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file))
goto cleanup;
}
@@ -194,13 +225,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
/* once workdir has been tried, load other data as needed */
if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
- &patch->ofile, &patch->diff_opts)) < 0 ||
+ &patch->ofile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->ofile.file))
goto cleanup;
}
if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) {
if ((error = git_diff_file_content__load(
- &patch->nfile, &patch->diff_opts)) < 0 ||
+ &patch->nfile, &patch->base.diff_opts)) < 0 ||
should_skip_binary(patch, patch->nfile.file))
goto cleanup;
}
@@ -212,24 +243,24 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output)
patch->ofile.file->mode == patch->nfile.file->mode &&
patch->ofile.file->mode != GIT_FILEMODE_COMMIT &&
git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) &&
- patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
- patch->delta->status = GIT_DELTA_UNMODIFIED;
+ patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */
+ patch->base.delta->status = GIT_DELTA_UNMODIFIED;
cleanup:
- diff_patch_update_binary(patch);
+ patch_generated_update_binary(patch);
if (!error) {
- if (diff_patch_diffable(patch))
- patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
+ if (patch_generated_diffable(patch))
+ patch->flags |= GIT_PATCH_GENERATED_DIFFABLE;
- patch->flags |= GIT_DIFF_PATCH_LOADED;
+ patch->flags |= GIT_PATCH_GENERATED_LOADED;
}
return error;
}
-static int diff_patch_invoke_file_callback(
- git_patch *patch, git_diff_output *output)
+static int patch_generated_invoke_file_callback(
+ git_patch_generated *patch, git_patch_generated_output *output)
{
float progress = patch->diff ?
((float)patch->delta_index / patch->diff->deltas.length) : 1.0f;
@@ -238,7 +269,7 @@ static int diff_patch_invoke_file_callback(
return 0;
return giterr_set_after_callback_function(
- output->file_cb(patch->delta, progress, output->payload),
+ output->file_cb(patch->base.delta, progress, output->payload),
"git_patch");
}
@@ -270,20 +301,24 @@ static int create_binary(
}
if (a_datalen && b_datalen) {
- void *delta_data = git_delta(
- a_data, (unsigned long)a_datalen,
- b_data, (unsigned long)b_datalen,
- &delta_data_len, (unsigned long)deflate.size);
+ void *delta_data;
- if (delta_data) {
+ error = git_delta(&delta_data, &delta_data_len,
+ a_data, a_datalen,
+ b_data, b_datalen,
+ deflate.size);
+
+ if (error == 0) {
error = git_zstream_deflatebuf(
&delta, delta_data, (size_t)delta_data_len);
git__free(delta_data);
-
- if (error < 0)
- goto done;
+ } else if (error == GIT_EBUFS) {
+ error = 0;
}
+
+ if (error < 0)
+ goto done;
}
if (delta.size && delta.size < deflate.size) {
@@ -305,7 +340,7 @@ done:
return error;
}
-static int diff_binary(git_diff_output *output, git_patch *patch)
+static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch)
{
git_diff_binary binary = {{0}};
const char *old_data = patch->ofile.map.data;
@@ -330,7 +365,7 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error;
error = giterr_set_after_callback_function(
- output->binary_cb(patch->delta, &binary, output->payload),
+ output->binary_cb(patch->base.delta, &binary, output->payload),
"git_patch");
git__free((char *) binary.old_file.data);
@@ -339,25 +374,27 @@ static int diff_binary(git_diff_output *output, git_patch *patch)
return error;
}
-static int diff_patch_generate(git_patch *patch, git_diff_output *output)
+static int patch_generated_create(
+ git_patch_generated *patch,
+ git_patch_generated_output *output)
{
int error = 0;
- if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
+ if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0)
return 0;
/* if we are not looking at the binary or text data, don't do the diff */
if (!output->binary_cb && !output->hunk_cb && !output->data_cb)
return 0;
- if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 &&
- (error = diff_patch_load(patch, output)) < 0)
+ if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 &&
+ (error = patch_generated_load(patch, output)) < 0)
return error;
- if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
+ if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0)
return 0;
- if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
+ if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
if (output->binary_cb)
error = diff_binary(output, patch);
}
@@ -366,33 +403,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output)
error = output->diff_cb(output, patch);
}
- patch->flags |= GIT_DIFF_PATCH_DIFFED;
+ patch->flags |= GIT_PATCH_GENERATED_DIFFED;
return error;
}
-static void diff_patch_free(git_patch *patch)
-{
- git_diff_file_content__clear(&patch->ofile);
- git_diff_file_content__clear(&patch->nfile);
-
- git_array_clear(patch->lines);
- git_array_clear(patch->hunks);
-
- git_diff_free(patch->diff); /* decrements refcount */
- patch->diff = NULL;
-
- git_pool_clear(&patch->flattened);
-
- git__free((char *)patch->diff_opts.old_prefix);
- git__free((char *)patch->diff_opts.new_prefix);
-
- git__free((char *)patch->binary.old_file.data);
- git__free((char *)patch->binary.new_file.data);
-
- if (patch->flags & GIT_DIFF_PATCH_ALLOCATED)
- git__free(patch);
-}
-
static int diff_required(git_diff *diff, const char *action)
{
if (diff)
@@ -412,7 +426,7 @@ int git_diff_foreach(
int error = 0;
git_xdiff_output xo;
size_t idx;
- git_patch patch;
+ git_patch_generated patch;
if ((error = diff_required(diff, "git_diff_foreach")) < 0)
return error;
@@ -423,24 +437,24 @@ int git_diff_foreach(
&xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload);
git_xdiff_init(&xo, &diff->opts);
- git_vector_foreach(&diff->deltas, idx, patch.delta) {
+ git_vector_foreach(&diff->deltas, idx, patch.base.delta) {
/* check flags against patch status */
- if (git_diff_delta__should_skip(&diff->opts, patch.delta))
+ if (git_diff_delta__should_skip(&diff->opts, patch.base.delta))
continue;
if (binary_cb || hunk_cb || data_cb) {
- if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 ||
- (error = diff_patch_load(&patch, &xo.output)) != 0)
+ if ((error = patch_generated_init(&patch, diff, idx)) != 0 ||
+ (error = patch_generated_load(&patch, &xo.output)) != 0)
return error;
}
- if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) {
+ if ((error = patch_generated_invoke_file_callback(&patch, &xo.output)) == 0) {
if (binary_cb || hunk_cb || data_cb)
- error = diff_patch_generate(&patch, &xo.output);
+ error = patch_generated_create(&patch, &xo.output);
}
- git_patch_free(&patch);
+ git_patch_free(&patch.base);
if (error)
break;
@@ -450,15 +464,15 @@ int git_diff_foreach(
}
typedef struct {
- git_patch patch;
+ git_patch_generated patch;
git_diff_delta delta;
char paths[GIT_FLEX_ARRAY];
-} diff_patch_with_delta;
+} patch_generated_with_delta;
-static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
+static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo)
{
int error = 0;
- git_patch *patch = &pd->patch;
+ git_patch_generated *patch = &pd->patch;
bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0);
@@ -469,24 +483,24 @@ static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo)
if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id))
pd->delta.status = GIT_DELTA_UNMODIFIED;
- patch->delta = &pd->delta;
+ patch->base.delta = &pd->delta;
- diff_patch_init_common(patch);
+ patch_generated_init_common(patch);
if (pd->delta.status == GIT_DELTA_UNMODIFIED &&
!(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED))
return error;
- error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo);
+ error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo);
if (!error)
- error = diff_patch_generate(patch, (git_diff_output *)xo);
+ error = patch_generated_create(patch, (git_patch_generated_output *)xo);
return error;
}
-static int diff_patch_from_sources(
- diff_patch_with_delta *pd,
+static int patch_generated_from_sources(
+ patch_generated_with_delta *pd,
git_xdiff_output *xo,
git_diff_file_content_src *oldsrc,
git_diff_file_content_src *newsrc,
@@ -499,7 +513,7 @@ static int diff_patch_from_sources(
git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file;
git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile;
- if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0)
+ if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0)
return error;
if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) {
@@ -507,7 +521,7 @@ static int diff_patch_from_sources(
tmp = ldata; ldata = rdata; rdata = tmp;
}
- pd->patch.delta = &pd->delta;
+ pd->patch.base.delta = &pd->delta;
if (!oldsrc->as_path) {
if (newsrc->as_path)
@@ -530,12 +544,12 @@ static int diff_patch_from_sources(
return diff_single_generate(pd, xo);
}
-static int diff_patch_with_delta_alloc(
- diff_patch_with_delta **out,
+static int patch_generated_with_delta_alloc(
+ patch_generated_with_delta **out,
const char **old_path,
const char **new_path)
{
- diff_patch_with_delta *pd;
+ patch_generated_with_delta *pd;
size_t old_len = *old_path ? strlen(*old_path) : 0;
size_t new_len = *new_path ? strlen(*new_path) : 0;
size_t alloc_len;
@@ -547,7 +561,7 @@ static int diff_patch_with_delta_alloc(
*out = pd = git__calloc(1, alloc_len);
GITERR_CHECK_ALLOC(pd);
- pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED;
+ pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED;
if (*old_path) {
memcpy(&pd->paths[0], *old_path, old_len);
@@ -575,7 +589,7 @@ static int diff_from_sources(
void *payload)
{
int error = 0;
- diff_patch_with_delta pd;
+ patch_generated_with_delta pd;
git_xdiff_output xo;
memset(&xo, 0, sizeof(xo));
@@ -585,9 +599,9 @@ static int diff_from_sources(
memset(&pd, 0, sizeof(pd));
- error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts);
+ error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts);
- git_patch_free(&pd.patch);
+ git_patch_free(&pd.patch.base);
return error;
}
@@ -599,13 +613,13 @@ static int patch_from_sources(
const git_diff_options *opts)
{
int error = 0;
- diff_patch_with_delta *pd;
+ patch_generated_with_delta *pd;
git_xdiff_output xo;
assert(out);
*out = NULL;
- if ((error = diff_patch_with_delta_alloc(
+ if ((error = patch_generated_with_delta_alloc(
&pd, &oldsrc->as_path, &newsrc->as_path)) < 0)
return error;
@@ -613,7 +627,7 @@ static int patch_from_sources(
diff_output_to_patch(&xo.output, &pd->patch);
git_xdiff_init(&xo, opts);
- if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts)))
+ if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts)))
*out = (git_patch *)pd;
else
git_patch_free((git_patch *)pd);
@@ -738,7 +752,7 @@ int git_patch_from_diff(
int error = 0;
git_xdiff_output xo;
git_diff_delta *delta = NULL;
- git_patch *patch = NULL;
+ git_patch_generated *patch = NULL;
if (patch_ptr) *patch_ptr = NULL;
@@ -760,17 +774,17 @@ int git_patch_from_diff(
(diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0))
return 0;
- if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0)
+ if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0)
return error;
memset(&xo, 0, sizeof(xo));
diff_output_to_patch(&xo.output, patch);
git_xdiff_init(&xo, &diff->opts);
- error = diff_patch_invoke_file_callback(patch, &xo.output);
+ error = patch_generated_invoke_file_callback(patch, &xo.output);
if (!error)
- error = diff_patch_generate(patch, &xo.output);
+ error = patch_generated_create(patch, &xo.output);
if (!error) {
/* TODO: if cumulative diff size is < 0.5 total size, flatten patch */
@@ -778,237 +792,34 @@ int git_patch_from_diff(
}
if (error || !patch_ptr)
- git_patch_free(patch);
+ git_patch_free(&patch->base);
else
- *patch_ptr = patch;
+ *patch_ptr = &patch->base;
return error;
}
-void git_patch_free(git_patch *patch)
-{
- if (patch)
- GIT_REFCOUNT_DEC(patch, diff_patch_free);
-}
-
-const git_diff_delta *git_patch_get_delta(const git_patch *patch)
-{
- assert(patch);
- return patch->delta;
-}
-
-size_t git_patch_num_hunks(const git_patch *patch)
-{
- assert(patch);
- return git_array_size(patch->hunks);
-}
-
-int git_patch_line_stats(
- size_t *total_ctxt,
- size_t *total_adds,
- size_t *total_dels,
- const git_patch *patch)
-{
- size_t totals[3], idx;
-
- memset(totals, 0, sizeof(totals));
-
- for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
- git_diff_line *line = git_array_get(patch->lines, idx);
- if (!line)
- continue;
-
- switch (line->origin) {
- case GIT_DIFF_LINE_CONTEXT: totals[0]++; break;
- case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
- case GIT_DIFF_LINE_DELETION: totals[2]++; break;
- default:
- /* diff --stat and --numstat don't count EOFNL marks because
- * they will always be paired with a ADDITION or DELETION line.
- */
- break;
- }
- }
-
- if (total_ctxt)
- *total_ctxt = totals[0];
- if (total_adds)
- *total_adds = totals[1];
- if (total_dels)
- *total_dels = totals[2];
-
- return 0;
-}
-
-static int diff_error_outofrange(const char *thing)
-{
- giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing);
- return GIT_ENOTFOUND;
-}
-
-int git_patch_get_hunk(
- const git_diff_hunk **out,
- size_t *lines_in_hunk,
- git_patch *patch,
- size_t hunk_idx)
-{
- diff_patch_hunk *hunk;
- assert(patch);
-
- hunk = git_array_get(patch->hunks, hunk_idx);
-
- if (!hunk) {
- if (out) *out = NULL;
- if (lines_in_hunk) *lines_in_hunk = 0;
- return diff_error_outofrange("hunk");
- }
-
- if (out) *out = &hunk->hunk;
- if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
- return 0;
-}
-
-int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
-{
- diff_patch_hunk *hunk;
- assert(patch);
-
- if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
- return diff_error_outofrange("hunk");
- return (int)hunk->line_count;
-}
-
-int git_patch_get_line_in_hunk(
- const git_diff_line **out,
- git_patch *patch,
- size_t hunk_idx,
- size_t line_of_hunk)
-{
- diff_patch_hunk *hunk;
- git_diff_line *line;
-
- assert(patch);
-
- if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
- if (out) *out = NULL;
- return diff_error_outofrange("hunk");
- }
-
- if (line_of_hunk >= hunk->line_count ||
- !(line = git_array_get(
- patch->lines, hunk->line_start + line_of_hunk))) {
- if (out) *out = NULL;
- return diff_error_outofrange("line");
- }
-
- if (out) *out = line;
- return 0;
-}
-
-size_t git_patch_size(
- git_patch *patch,
- int include_context,
- int include_hunk_headers,
- int include_file_headers)
-{
- size_t out;
-
- assert(patch);
-
- out = patch->content_size;
-
- if (!include_context)
- out -= patch->context_size;
-
- if (include_hunk_headers)
- out += patch->header_size;
-
- if (include_file_headers) {
- git_buf file_header = GIT_BUF_INIT;
-
- if (git_diff_delta__format_file_header(
- &file_header, patch->delta, NULL, NULL, 0) < 0)
- giterr_clear();
- else
- out += git_buf_len(&file_header);
-
- git_buf_free(&file_header);
- }
-
- return out;
-}
-
-git_diff *git_patch__diff(git_patch *patch)
-{
- return patch->diff;
-}
-
-git_diff_driver *git_patch__driver(git_patch *patch)
+git_diff_driver *git_patch_generated_driver(git_patch_generated *patch)
{
/* ofile driver is representative for whole patch */
return patch->ofile.driver;
}
-void git_patch__old_data(
- char **ptr, size_t *len, git_patch *patch)
+void git_patch_generated_old_data(
+ char **ptr, size_t *len, git_patch_generated *patch)
{
*ptr = patch->ofile.map.data;
*len = patch->ofile.map.len;
}
-void git_patch__new_data(
- char **ptr, size_t *len, git_patch *patch)
+void git_patch_generated_new_data(
+ char **ptr, size_t *len, git_patch_generated *patch)
{
*ptr = patch->nfile.map.data;
*len = patch->nfile.map.len;
}
-int git_patch__invoke_callbacks(
- git_patch *patch,
- git_diff_file_cb file_cb,
- git_diff_binary_cb binary_cb,
- git_diff_hunk_cb hunk_cb,
- git_diff_line_cb line_cb,
- void *payload)
-{
- int error = 0;
- uint32_t i, j;
-
- if (file_cb)
- error = file_cb(patch->delta, 0, payload);
-
- if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
- if (binary_cb)
- error = binary_cb(patch->delta, &patch->binary, payload);
-
- return error;
- }
-
- if (!hunk_cb && !line_cb)
- return error;
-
- for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
- diff_patch_hunk *h = git_array_get(patch->hunks, i);
-
- if (hunk_cb)
- error = hunk_cb(patch->delta, &h->hunk, payload);
-
- if (!line_cb)
- continue;
-
- for (j = 0; !error && j < h->line_count; ++j) {
- git_diff_line *l =
- git_array_get(patch->lines, h->line_start + j);
-
- error = line_cb(patch->delta, &h->hunk, l, payload);
- }
- }
-
- return error;
-}
-
-
-static int diff_patch_file_cb(
+static int patch_generated_file_cb(
const git_diff_delta *delta,
float progress,
void *payload)
@@ -1017,7 +828,7 @@ static int diff_patch_file_cb(
return 0;
}
-static int diff_patch_binary_cb(
+static int patch_generated_binary_cb(
const git_diff_delta *delta,
const git_diff_binary *binary,
void *payload)
@@ -1047,62 +858,62 @@ static int diff_patch_binary_cb(
return 0;
}
-static int diff_patch_hunk_cb(
+static int git_patch_hunk_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk_,
void *payload)
{
- git_patch *patch = payload;
- diff_patch_hunk *hunk;
+ git_patch_generated *patch = payload;
+ git_patch_hunk *hunk;
GIT_UNUSED(delta);
- hunk = git_array_alloc(patch->hunks);
+ hunk = git_array_alloc(patch->base.hunks);
GITERR_CHECK_ALLOC(hunk);
memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk));
- patch->header_size += hunk_->header_len;
+ patch->base.header_size += hunk_->header_len;
- hunk->line_start = git_array_size(patch->lines);
+ hunk->line_start = git_array_size(patch->base.lines);
hunk->line_count = 0;
return 0;
}
-static int diff_patch_line_cb(
+static int patch_generated_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk_,
const git_diff_line *line_,
void *payload)
{
- git_patch *patch = payload;
- diff_patch_hunk *hunk;
+ git_patch_generated *patch = payload;
+ git_patch_hunk *hunk;
git_diff_line *line;
GIT_UNUSED(delta);
GIT_UNUSED(hunk_);
- hunk = git_array_last(patch->hunks);
+ hunk = git_array_last(patch->base.hunks);
assert(hunk); /* programmer error if no hunk is available */
- line = git_array_alloc(patch->lines);
+ line = git_array_alloc(patch->base.lines);
GITERR_CHECK_ALLOC(line);
memcpy(line, line_, sizeof(*line));
/* do some bookkeeping so we can provide old/new line numbers */
- patch->content_size += line->content_len;
+ patch->base.content_size += line->content_len;
if (line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION)
- patch->content_size += 1;
+ patch->base.content_size += 1;
else if (line->origin == GIT_DIFF_LINE_CONTEXT) {
- patch->content_size += 1;
- patch->context_size += line->content_len + 1;
+ patch->base.content_size += 1;
+ patch->base.context_size += line->content_len + 1;
} else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL)
- patch->context_size += line->content_len;
+ patch->base.context_size += line->content_len;
hunk->line_count++;
@@ -1110,7 +921,7 @@ static int diff_patch_line_cb(
}
static void diff_output_init(
- git_diff_output *out,
+ git_patch_generated_output *out,
const git_diff_options *opts,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
@@ -1129,14 +940,15 @@ static void diff_output_init(
out->payload = payload;
}
-static void diff_output_to_patch(git_diff_output *out, git_patch *patch)
+static void diff_output_to_patch(
+ git_patch_generated_output *out, git_patch_generated *patch)
{
diff_output_init(
out,
NULL,
- diff_patch_file_cb,
- diff_patch_binary_cb,
- diff_patch_hunk_cb,
- diff_patch_line_cb,
+ patch_generated_file_cb,
+ patch_generated_binary_cb,
+ git_patch_hunk_cb,
+ patch_generated_line_cb,
patch);
}
diff --git a/src/patch_generate.h b/src/patch_generate.h
new file mode 100644
index 000000000..bb26a76e5
--- /dev/null
+++ b/src/patch_generate.h
@@ -0,0 +1,66 @@
+/*
+ * 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_generate_h__
+#define INCLUDE_patch_generate_h__
+
+#include "common.h"
+#include "diff.h"
+#include "diff_file.h"
+#include "patch.h"
+
+enum {
+ GIT_PATCH_GENERATED_ALLOCATED = (1 << 0),
+ GIT_PATCH_GENERATED_INITIALIZED = (1 << 1),
+ GIT_PATCH_GENERATED_LOADED = (1 << 2),
+ /* the two sides are different */
+ GIT_PATCH_GENERATED_DIFFABLE = (1 << 3),
+ /* the difference between the two sides has been computed */
+ GIT_PATCH_GENERATED_DIFFED = (1 << 4),
+ GIT_PATCH_GENERATED_FLATTENED = (1 << 5),
+};
+
+struct git_patch_generated {
+ struct git_patch base;
+
+ git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ size_t delta_index;
+ git_diff_file_content ofile;
+ git_diff_file_content nfile;
+ uint32_t flags;
+ git_pool flattened;
+};
+
+typedef struct git_patch_generated git_patch_generated;
+
+extern git_diff_driver *git_patch_generated_driver(git_patch_generated *);
+
+extern void git_patch_generated_old_data(
+ char **, size_t *, git_patch_generated *);
+extern void git_patch_generated_new_data(
+ char **, size_t *, git_patch_generated *);
+
+typedef struct git_patch_generated_output git_patch_generated_output;
+
+struct git_patch_generated_output {
+ /* these callbacks are issued with the diff data */
+ git_diff_file_cb file_cb;
+ git_diff_binary_cb binary_cb;
+ git_diff_hunk_cb hunk_cb;
+ git_diff_line_cb data_cb;
+ void *payload;
+
+ /* this records the actual error in cases where it may be obscured */
+ int error;
+
+ /* this callback is used to do the diff and drive the other callbacks.
+ * see diff_xdiff.h for how to use this in practice for now.
+ */
+ int (*diff_cb)(git_patch_generated_output *output,
+ git_patch_generated *patch);
+};
+
+#endif
diff --git a/src/patch_parse.c b/src/patch_parse.c
new file mode 100644
index 000000000..7f21e3f8e
--- /dev/null
+++ b/src/patch_parse.c
@@ -0,0 +1,1121 @@
+/*
+ * 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;
+}
+
diff --git a/src/patch_parse.h b/src/patch_parse.h
new file mode 100644
index 000000000..da56dad7c
--- /dev/null
+++ b/src/patch_parse.h
@@ -0,0 +1,54 @@
+/*
+ * 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
diff --git a/src/path.c b/src/path.c
index 4133985a4..e5f04a56a 100644
--- a/src/path.c
+++ b/src/path.c
@@ -306,6 +306,25 @@ int git_path_join_unrooted(
return 0;
}
+void git_path_squash_slashes(git_buf *path)
+{
+ char *p, *q;
+
+ if (path->size == 0)
+ return;
+
+ for (p = path->ptr, q = path->ptr; *q; p++, q++) {
+ *p = *q;
+
+ while (*q == '/' && *(q+1) == '/') {
+ path->size--;
+ q++;
+ }
+ }
+
+ *p = '\0';
+}
+
int git_path_prettify(git_buf *path_out, const char *path, const char *base)
{
char buf[GIT_PATH_MAX];
diff --git a/src/path.h b/src/path.h
index f31cacc70..fb45a6534 100644
--- a/src/path.h
+++ b/src/path.h
@@ -244,6 +244,12 @@ extern int git_path_join_unrooted(
git_buf *path_out, const char *path, const char *base, ssize_t *root_at);
/**
+ * Removes multiple occurrences of '/' in a row, squashing them into a
+ * single '/'.
+ */
+extern void git_path_squash_slashes(git_buf *path);
+
+/**
* Clean up path, prepending base if it is not already rooted.
*/
extern int git_path_prettify(git_buf *path_out, const char *path, const char *base);
diff --git a/src/stash.c b/src/stash.c
index 43a464e64..f5f4f36bf 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -23,6 +23,7 @@
#include "iterator.h"
#include "merge.h"
#include "diff.h"
+#include "diff_generate.h"
static int create_error(int error, const char *msg)
{
diff --git a/src/status.c b/src/status.c
index b206b0e2f..e610f5fe1 100644
--- a/src/status.c
+++ b/src/status.c
@@ -19,6 +19,7 @@
#include "git2/diff.h"
#include "diff.h"
+#include "diff_generate.h"
static unsigned int index_delta2status(const git_diff_delta *head2idx)
{
diff --git a/src/util.c b/src/util.c
index 9e67f4347..3090c7437 100644
--- a/src/util.c
+++ b/src/util.c
@@ -66,6 +66,12 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src)
int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base)
{
+
+ return git__strntol64(result, nptr, (size_t)-1, endptr, base);
+}
+
+int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
const char *p;
int64_t n, nn;
int c, ovfl, v, neg, ndig;
@@ -111,7 +117,7 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba
/*
* Non-empty sequence of digits
*/
- for (;; p++,ndig++) {
+ for (; nptr_len > 0; p++,ndig++,nptr_len--) {
c = *p;
v = base;
if ('0'<=c && c<='9')
@@ -148,11 +154,17 @@ Return:
int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base)
{
+
+ return git__strntol32(result, nptr, (size_t)-1, endptr, base);
+}
+
+int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base)
+{
int error;
int32_t tmp_int;
int64_t tmp_long;
- if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0)
+ if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0)
return error;
tmp_int = tmp_long & 0xFFFFFFFF;
@@ -321,6 +333,12 @@ char *git__strsep(char **end, const char *sep)
return NULL;
}
+size_t git__linenlen(const char *buffer, size_t buffer_len)
+{
+ char *nl = memchr(buffer, '\n', buffer_len);
+ return nl ? (size_t)(nl - buffer) + 1 : buffer_len;
+}
+
void git__hexdump(const char *buffer, size_t len)
{
static const size_t LINE_WIDTH = 16;
diff --git a/src/util.h b/src/util.h
index d0c3cd04a..eb15250d8 100644
--- a/src/util.h
+++ b/src/util.h
@@ -263,7 +263,10 @@ GIT_INLINE(int) git__signum(int val)
}
extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base);
+extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base);
+extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base);
+
extern void git__hexdump(const char *buffer, size_t n);
extern uint32_t git__hash(const void *key, int len, uint32_t seed);
@@ -290,6 +293,8 @@ GIT_INLINE(int) git__tolower(int c)
# define git__tolower(a) tolower(a)
#endif
+extern size_t git__linenlen(const char *buffer, size_t buffer_len);
+
GIT_INLINE(const char *) git__next_line(const char *s)
{
while (*s && *s != '\n') s++;
@@ -466,6 +471,11 @@ GIT_INLINE(bool) git__iswildcard(int c)
return (c == '*' || c == '?' || c == '[');
}
+GIT_INLINE(bool) git__isxdigit(int c)
+{
+ return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
/*
* Parse a string value as a boolean, just like Core Git does.
*
diff --git a/src/vector.c b/src/vector.c
index a81d463ef..db1dcf89a 100644
--- a/src/vector.c
+++ b/src/vector.c
@@ -7,6 +7,7 @@
#include "common.h"
#include "vector.h"
+#include "integer.h"
/* In elements, not bytes */
#define MIN_ALLOCSIZE 8
@@ -330,6 +331,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length)
return 0;
}
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len)
+{
+ size_t new_length;
+
+ assert(insert_len > 0 && idx <= v->length);
+
+ GITERR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len);
+
+ if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0)
+ return -1;
+
+ memmove(&v->contents[idx + insert_len], &v->contents[idx],
+ sizeof(void *) * (v->length - idx));
+ memset(&v->contents[idx], 0, sizeof(void *) * insert_len);
+
+ v->length = new_length;
+ return 0;
+}
+
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len)
+{
+ size_t new_length = v->length - remove_len;
+ size_t end_idx = 0;
+
+ assert(remove_len > 0);
+
+ if (git__add_sizet_overflow(&end_idx, idx, remove_len))
+ assert(0);
+
+ assert(end_idx <= v->length);
+
+ if (end_idx < v->length)
+ memmove(&v->contents[idx], &v->contents[end_idx],
+ sizeof(void *) * (v->length - end_idx));
+
+ memset(&v->contents[new_length], 0, sizeof(void *) * remove_len);
+
+ v->length = new_length;
+ return 0;
+}
+
int git_vector_set(void **old, git_vector *v, size_t position, void *value)
{
if (position + 1 > v->length) {
diff --git a/src/vector.h b/src/vector.h
index b7500ded3..96d149e16 100644
--- a/src/vector.h
+++ b/src/vector.h
@@ -93,6 +93,9 @@ void git_vector_remove_matching(
void *payload);
int git_vector_resize_to(git_vector *v, size_t new_length);
+int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len);
+int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len);
+
int git_vector_set(void **old, git_vector *v, size_t position, void *value);
/** Check if vector is sorted */
diff --git a/src/zstream.c b/src/zstream.c
index 2130bc3ca..d9ad4ca89 100644
--- a/src/zstream.c
+++ b/src/zstream.c
@@ -28,20 +28,31 @@ static int zstream_seterr(git_zstream *zs)
return -1;
}
-int git_zstream_init(git_zstream *zstream)
+int git_zstream_init(git_zstream *zstream, git_zstream_t type)
{
- zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
+ zstream->type = type;
+
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflateInit(&zstream->z);
+ else
+ zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION);
return zstream_seterr(zstream);
}
void git_zstream_free(git_zstream *zstream)
{
- deflateEnd(&zstream->z);
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateEnd(&zstream->z);
+ else
+ deflateEnd(&zstream->z);
}
void git_zstream_reset(git_zstream *zstream)
{
- deflateReset(&zstream->z);
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ inflateReset(&zstream->z);
+ else
+ deflateReset(&zstream->z);
zstream->in = NULL;
zstream->in_len = 0;
zstream->zerr = Z_STREAM_END;
@@ -75,6 +86,11 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
int zflush = Z_FINISH;
size_t out_remain = *out_len;
+ if (zstream->in_len && zstream->zerr == Z_STREAM_END) {
+ giterr_set(GITERR_ZLIB, "zlib input had trailing garbage");
+ return -1;
+ }
+
while (out_remain > 0 && zstream->zerr != Z_STREAM_END) {
size_t out_queued, in_queued, out_used, in_used;
@@ -97,7 +113,10 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
out_queued = (size_t)zstream->z.avail_out;
/* compress next chunk */
- zstream->zerr = deflate(&zstream->z, zflush);
+ if (zstream->type == GIT_ZSTREAM_INFLATE)
+ zstream->zerr = inflate(&zstream->z, zflush);
+ else
+ zstream->zerr = deflate(&zstream->z, zflush);
if (zstream->zerr == Z_STREAM_ERROR)
return zstream_seterr(zstream);
@@ -120,12 +139,12 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream)
return 0;
}
-int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
+static int zstream_buf(git_buf *out, const void *in, size_t in_len, git_zstream_t type)
{
git_zstream zs = GIT_ZSTREAM_INIT;
int error = 0;
- if ((error = git_zstream_init(&zs)) < 0)
+ if ((error = git_zstream_init(&zs, type)) < 0)
return error;
if ((error = git_zstream_set_input(&zs, in, in_len)) < 0)
@@ -154,3 +173,13 @@ done:
git_zstream_free(&zs);
return error;
}
+
+int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE);
+}
+
+int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len)
+{
+ return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE);
+}
diff --git a/src/zstream.h b/src/zstream.h
index 9b5bf6ace..f0006d32e 100644
--- a/src/zstream.h
+++ b/src/zstream.h
@@ -12,8 +12,14 @@
#include "common.h"
#include "buffer.h"
+typedef enum {
+ GIT_ZSTREAM_INFLATE,
+ GIT_ZSTREAM_DEFLATE,
+} git_zstream_t;
+
typedef struct {
z_stream z;
+ git_zstream_t type;
const char *in;
size_t in_len;
int zerr;
@@ -21,7 +27,7 @@ typedef struct {
#define GIT_ZSTREAM_INIT {{0}}
-int git_zstream_init(git_zstream *zstream);
+int git_zstream_init(git_zstream *zstream, git_zstream_t type);
void git_zstream_free(git_zstream *zstream);
int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len);
@@ -35,5 +41,6 @@ bool git_zstream_done(git_zstream *zstream);
void git_zstream_reset(git_zstream *zstream);
int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len);
+int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len);
#endif /* INCLUDE_zstream_h__ */
diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c
new file mode 100644
index 000000000..349773964
--- /dev/null
+++ b/tests/apply/fromdiff.c
@@ -0,0 +1,289 @@
+#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));
+}
diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c
new file mode 100644
index 000000000..31fffa1a2
--- /dev/null
+++ b/tests/apply/fromfile.c
@@ -0,0 +1,449 @@
+#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));
+}
diff --git a/tests/buf/quote.c b/tests/buf/quote.c
new file mode 100644
index 000000000..6f77ab9c1
--- /dev/null
+++ b/tests/buf/quote.c
@@ -0,0 +1,88 @@
+#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\"");
+}
diff --git a/tests/core/buffer.c b/tests/core/buffer.c
index 9872af7f4..c4308cbbf 100644
--- a/tests/core/buffer.c
+++ b/tests/core/buffer.c
@@ -813,6 +813,44 @@ void test_core_buffer__encode_base85(void)
git_buf_free(&buf);
}
+void test_core_buffer__decode_base85(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ cl_git_pass(git_buf_decode_base85(&buf, "bZBXF", 5, 4));
+ cl_assert_equal_sz(4, buf.size);
+ cl_assert_equal_s("this", buf.ptr);
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_buf_decode_base85(&buf, "ba!tca&BaE", 10, 8));
+ cl_assert_equal_sz(8, buf.size);
+ cl_assert_equal_s("two rnds", buf.ptr);
+ git_buf_clear(&buf);
+
+ cl_git_pass(git_buf_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23));
+ cl_assert_equal_sz(23, buf.size);
+ cl_assert_equal_s("this is base 85 encoded", buf.ptr);
+ git_buf_clear(&buf);
+
+ git_buf_free(&buf);
+}
+
+void test_core_buffer__decode_base85_fails_gracefully(void)
+{
+ git_buf buf = GIT_BUF_INIT;
+
+ git_buf_puts(&buf, "foobar");
+
+ cl_git_fail(git_buf_decode_base85(&buf, "invalid charsZZ", 15, 42));
+ cl_git_fail(git_buf_decode_base85(&buf, "invalidchars__ ", 15, 42));
+ cl_git_fail(git_buf_decode_base85(&buf, "overflowZZ~~~~~", 15, 42));
+ cl_git_fail(git_buf_decode_base85(&buf, "truncated", 9, 42));
+ cl_assert_equal_sz(6, buf.size);
+ cl_assert_equal_s("foobar", buf.ptr);
+
+ git_buf_free(&buf);
+}
+
void test_core_buffer__classify_with_utf8(void)
{
char *data0 = "Simple text\n";
diff --git a/tests/core/vector.c b/tests/core/vector.c
index 66f90b82b..c351655a7 100644
--- a/tests/core/vector.c
+++ b/tests/core/vector.c
@@ -274,3 +274,105 @@ void test_core_vector__remove_matching(void)
git_vector_free(&x);
}
+
+static void assert_vector(git_vector *x, void *expected[], size_t len)
+{
+ size_t i;
+
+ cl_assert_equal_i(len, x->length);
+
+ for (i = 0; i < len; i++)
+ cl_assert(expected[i] == x->contents[i]);
+}
+
+void test_core_vector__grow_and_shrink(void)
+{
+ git_vector x = GIT_VECTOR_INIT;
+ void *expected1[] = {
+ (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05,
+ (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09,
+ (void *)0x0a
+ };
+ void *expected2[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
+ (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a
+ };
+ void *expected3[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06,
+ (void *)0x0a
+ };
+ void *expected4[] = {
+ (void *)0x02, (void *)0x04, (void *)0x05
+ };
+ void *expected5[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x05
+ };
+ void *expected6[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x05, (void *)0x00
+ };
+ void *expected7[] = {
+ (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04,
+ (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05,
+ (void *)0x00
+ };
+ void *expected8[] = {
+ (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00,
+ (void *)0x05, (void *)0x00
+ };
+ void *expected9[] = {
+ (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00
+ };
+ void *expectedA[] = { (void *)0x04, (void *)0x00 };
+ void *expectedB[] = { (void *)0x04 };
+
+ git_vector_insert(&x, (void *)0x01);
+ git_vector_insert(&x, (void *)0x02);
+ git_vector_insert(&x, (void *)0x03);
+ git_vector_insert(&x, (void *)0x04);
+ git_vector_insert(&x, (void *)0x05);
+ git_vector_insert(&x, (void *)0x06);
+ git_vector_insert(&x, (void *)0x07);
+ git_vector_insert(&x, (void *)0x08);
+ git_vector_insert(&x, (void *)0x09);
+ git_vector_insert(&x, (void *)0x0a);
+
+ git_vector_remove_range(&x, 0, 1);
+ assert_vector(&x, expected1, ARRAY_SIZE(expected1));
+
+ git_vector_remove_range(&x, 1, 1);
+ assert_vector(&x, expected2, ARRAY_SIZE(expected2));
+
+ git_vector_remove_range(&x, 4, 3);
+ assert_vector(&x, expected3, ARRAY_SIZE(expected3));
+
+ git_vector_remove_range(&x, 3, 2);
+ assert_vector(&x, expected4, ARRAY_SIZE(expected4));
+
+ git_vector_insert_null(&x, 0, 2);
+ assert_vector(&x, expected5, ARRAY_SIZE(expected5));
+
+ git_vector_insert_null(&x, 5, 1);
+ assert_vector(&x, expected6, ARRAY_SIZE(expected6));
+
+ git_vector_insert_null(&x, 4, 3);
+ assert_vector(&x, expected7, ARRAY_SIZE(expected7));
+
+ git_vector_remove_range(&x, 0, 3);
+ assert_vector(&x, expected8, ARRAY_SIZE(expected8));
+
+ git_vector_remove_range(&x, 1, 2);
+ assert_vector(&x, expected9, ARRAY_SIZE(expected9));
+
+ git_vector_remove_range(&x, 2, 2);
+ assert_vector(&x, expectedA, ARRAY_SIZE(expectedA));
+
+ git_vector_remove_range(&x, 1, 1);
+ assert_vector(&x, expectedB, ARRAY_SIZE(expectedB));
+
+ git_vector_remove_range(&x, 0, 1);
+ assert_vector(&x, NULL, 0);
+
+ git_vector_free(&x);
+}
diff --git a/tests/core/zstream.c b/tests/core/zstream.c
index 7ba9424ba..961904ec3 100644
--- a/tests/core/zstream.c
+++ b/tests/core/zstream.c
@@ -48,7 +48,7 @@ void test_core_zstream__basic(void)
char out[128];
size_t outlen = sizeof(out);
- cl_git_pass(git_zstream_init(&z));
+ cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE));
cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1));
cl_git_pass(git_zstream_get_output(out, &outlen, &z));
cl_assert(git_zstream_done(&z));
@@ -58,6 +58,25 @@ void test_core_zstream__basic(void)
assert_zlib_equal(data, strlen(data) + 1, out, outlen);
}
+void test_core_zstream__fails_on_trailing_garbage(void)
+{
+ git_buf deflated = GIT_BUF_INIT, inflated = GIT_BUF_INIT;
+ size_t i = 0;
+
+ /* compress a simple string */
+ git_zstream_deflatebuf(&deflated, "foobar!!", 8);
+
+ /* append some garbage */
+ for (i = 0; i < 10; i++) {
+ git_buf_putc(&deflated, i);
+ }
+
+ cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size));
+
+ git_buf_free(&deflated);
+ git_buf_free(&inflated);
+}
+
void test_core_zstream__buffer(void)
{
git_buf out = GIT_BUF_INIT;
@@ -68,9 +87,10 @@ void test_core_zstream__buffer(void)
#define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long"
-static void compress_input_various_ways(git_buf *input)
+static void compress_and_decompress_input_various_ways(git_buf *input)
{
git_buf out1 = GIT_BUF_INIT, out2 = GIT_BUF_INIT;
+ git_buf inflated = GIT_BUF_INIT;
size_t i, fixed_size = max(input->size / 2, 256);
char *fixed = git__malloc(fixed_size);
cl_assert(fixed);
@@ -93,7 +113,7 @@ static void compress_input_various_ways(git_buf *input)
}
cl_assert(use_fixed_size <= fixed_size);
- cl_git_pass(git_zstream_init(&zs));
+ cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE));
cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size));
while (!git_zstream_done(&zs)) {
@@ -112,7 +132,12 @@ static void compress_input_various_ways(git_buf *input)
git_buf_free(&out2);
}
+ cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size));
+ cl_assert_equal_i(input->size, inflated.size);
+ cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0);
+
git_buf_free(&out1);
+ git_buf_free(&inflated);
git__free(fixed);
}
@@ -129,14 +154,14 @@ void test_core_zstream__big_data(void)
cl_git_pass(
git_buf_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART)));
- compress_input_various_ways(&in);
+ compress_and_decompress_input_various_ways(&in);
/* make a big string that's hard to compress */
srand(0xabad1dea);
for (scan = 0; scan < in.size; ++scan)
in.ptr[scan] = (char)rand();
- compress_input_various_ways(&in);
+ compress_and_decompress_input_various_ways(&in);
}
git_buf_free(&in);
diff --git a/tests/diff/diff_helpers.c b/tests/diff/diff_helpers.c
index c6cdf803f..50752b203 100644
--- a/tests/diff/diff_helpers.c
+++ b/tests/diff/diff_helpers.c
@@ -241,3 +241,76 @@ void diff_print_raw(FILE *fp, git_diff *diff)
git_diff_print(diff, GIT_DIFF_FORMAT_RAW,
git_diff_print_callback__to_file_handle, fp ? fp : stderr));
}
+
+static size_t num_modified_deltas(git_diff *diff)
+{
+ const git_diff_delta *delta;
+ size_t i, cnt = 0;
+
+ for (i = 0; i < git_diff_num_deltas(diff); i++) {
+ delta = git_diff_get_delta(diff, i);
+
+ if (delta->status != GIT_DELTA_UNMODIFIED)
+ cnt++;
+ }
+
+ return cnt;
+}
+
+void diff_assert_equal(git_diff *a, git_diff *b)
+{
+ const git_diff_delta *ad, *bd;
+ size_t i, j;
+
+ assert(a && b);
+
+ cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b));
+
+ for (i = 0, j = 0;
+ i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) {
+
+ ad = git_diff_get_delta(a, i);
+ bd = git_diff_get_delta(b, j);
+
+ if (ad->status == GIT_DELTA_UNMODIFIED) {
+ i++;
+ continue;
+ }
+ if (bd->status == GIT_DELTA_UNMODIFIED) {
+ j++;
+ continue;
+ }
+
+ cl_assert_equal_i(ad->status, bd->status);
+ cl_assert_equal_i(ad->flags, bd->flags);
+ cl_assert_equal_i(ad->similarity, bd->similarity);
+ cl_assert_equal_i(ad->nfiles, bd->nfiles);
+
+ /* Don't examine the size or the flags of the deltas;
+ * computed deltas have sizes (parsed deltas do not) and
+ * computed deltas will have flags of `VALID_ID` and
+ * `EXISTS` (parsed deltas will not query the ODB.)
+ */
+
+ /* an empty id indicates that it wasn't presented, because
+ * the diff was identical. (eg, pure rename, mode change only, etc)
+ */
+ if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) {
+ cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev);
+ cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id);
+ cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode);
+ }
+ cl_assert_equal_s(ad->old_file.path, bd->old_file.path);
+
+ if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) {
+ cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id);
+ cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev);
+ cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode);
+ }
+ cl_assert_equal_s(ad->new_file.path, bd->new_file.path);
+
+ i++;
+ j++;
+ }
+}
+
diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h
index 4d3cd3474..520b654d3 100644
--- a/tests/diff/diff_helpers.h
+++ b/tests/diff/diff_helpers.h
@@ -68,3 +68,6 @@ extern int diff_foreach_via_iterator(
extern void diff_print(FILE *fp, git_diff *diff);
extern void diff_print_raw(FILE *fp, git_diff *diff);
+
+extern void diff_assert_equal(git_diff *a, git_diff *b);
+
diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c
index e55afe958..9f8fe3142 100644
--- a/tests/diff/format_email.c
+++ b/tests/diff/format_email.c
@@ -4,6 +4,7 @@
#include "buffer.h"
#include "commit.h"
#include "diff.h"
+#include "diff_generate.h"
static git_repository *repo;
@@ -350,9 +351,6 @@ void test_diff_format_email__mode_change(void)
"diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
"old mode 100644\n" \
"new mode 100755\n" \
- "index a97157a..a97157a\n" \
- "--- a/file1.txt.renamed\n" \
- "+++ b/file1.txt.renamed\n" \
"--\n" \
"libgit2 " LIBGIT2_VERSION "\n" \
"\n";
diff --git a/tests/diff/parse.c b/tests/diff/parse.c
new file mode 100644
index 000000000..83000a92d
--- /dev/null
+++ b/tests/diff/parse.c
@@ -0,0 +1,153 @@
+#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);
+}
+
diff --git a/tests/diff/rename.c b/tests/diff/rename.c
index 5cfd8e235..c1cd25239 100644
--- a/tests/diff/rename.c
+++ b/tests/diff/rename.c
@@ -1702,3 +1702,49 @@ void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void
expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
}
+
+/* test that 100% renames and copies emit the correct patch file
+ * git diff --find-copies-harder -M100 -B100 \
+ * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
+ * 2bc7f351d20b53f1c72c16c4b036e491c478c49a
+ */
+void test_diff_rename__identical(void)
+{
+ const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
+ const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
+ git_tree *old_tree, *new_tree;
+ git_diff *diff;
+ git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
+ git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
+ git_buf diff_buf = GIT_BUF_INIT;
+ const char *expected =
+ "diff --git a/serving.txt b/sixserving.txt\n"
+ "similarity index 100%\n"
+ "rename from serving.txt\n"
+ "rename to sixserving.txt\n"
+ "diff --git a/sevencities.txt b/songofseven.txt\n"
+ "similarity index 100%\n"
+ "copy from sevencities.txt\n"
+ "copy to songofseven.txt\n";
+
+ old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
+ new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
+
+ diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
+ find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
+ GIT_DIFF_FIND_EXACT_MATCH_ONLY;
+
+ cl_git_pass(git_diff_tree_to_tree(&diff,
+ g_repo, old_tree, new_tree, &diff_opts));
+ cl_git_pass(git_diff_find_similar(diff, &find_opts));
+
+ cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
+
+ cl_assert_equal_s(expected, diff_buf.ptr);
+
+ git_buf_free(&diff_buf);
+ git_diff_free(diff);
+ git_tree_free(old_tree);
+ git_tree_free(new_tree);
+}
+
diff --git a/tests/diff/stats.c b/tests/diff/stats.c
index f731997da..8f146e2a4 100644
--- a/tests/diff/stats.c
+++ b/tests/diff/stats.c
@@ -4,6 +4,7 @@
#include "buffer.h"
#include "commit.h"
#include "diff.h"
+#include "diff_generate.h"
static git_repository *_repo;
static git_diff_stats *_stats;
diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c
index 3634568de..cd2cf7827 100644
--- a/tests/merge/trees/treediff.c
+++ b/tests/merge/trees/treediff.c
@@ -3,6 +3,7 @@
#include "merge.h"
#include "../merge_helpers.h"
#include "diff.h"
+#include "diff_tform.h"
#include "git2/sys/hashsig.h"
static git_repository *repo;
diff --git a/tests/patch/parse.c b/tests/patch/parse.c
new file mode 100644
index 000000000..8350ac2dd
--- /dev/null
+++ b/tests/patch/parse.c
@@ -0,0 +1,104 @@
+#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));
+}
+
diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h
new file mode 100644
index 000000000..e097062d2
--- /dev/null
+++ b/tests/patch/patch_common.h
@@ -0,0 +1,663 @@
+/* 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"
diff --git a/tests/patch/print.c b/tests/patch/print.c
new file mode 100644
index 000000000..5a86573b3
--- /dev/null
+++ b/tests/patch/print.c
@@ -0,0 +1,168 @@
+#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));
+}