summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-09-24 20:52:34 -0700
committerRussell Belfer <rb@github.com>2012-09-25 16:35:05 -0700
commit5f69a31f7d706aa5788ad9937391577a66e3c77d (patch)
tree8201821372d02499f092b774a8fd521478564a7e
parent9a12a6256efa7da4b4245d0f2b7df6f3b84edabd (diff)
downloadlibgit2-5f69a31f7d706aa5788ad9937391577a66e3c77d.tar.gz
Initial implementation of new diff patch API
Replacing the `git_iterator` object, this creates a simple API for accessing the "patch" for any file pair in a diff list and then gives indexed access to the hunks in the patch and the lines in the hunk. This is the initial implementation of this revised API - it is still broken, but at least builds cleanly.
-rw-r--r--include/git2/diff.h62
-rw-r--r--src/diff.c46
-rw-r--r--src/diff.h32
-rw-r--r--src/diff_output.c1066
-rw-r--r--src/diff_output.h86
-rw-r--r--src/submodule.c7
-rw-r--r--tests-clar/diff/diff_helpers.c75
-rw-r--r--tests-clar/diff/diffiter.c275
-rw-r--r--tests-clar/diff/tree.c75
-rw-r--r--tests-clar/diff/workdir.c69
10 files changed, 967 insertions, 826 deletions
diff --git a/include/git2/diff.h b/include/git2/diff.h
index 2a5cdacc0..d216c1303 100644
--- a/include/git2/diff.h
+++ b/include/git2/diff.h
@@ -98,6 +98,9 @@ enum {
GIT_DIFF_FILE_FREE_PATH = (1 << 1),
GIT_DIFF_FILE_BINARY = (1 << 2),
GIT_DIFF_FILE_NOT_BINARY = (1 << 3),
+ GIT_DIFF_FILE_FREE_DATA = (1 << 4),
+ GIT_DIFF_FILE_UNMAP_DATA = (1 << 5),
+ GIT_DIFF_FILE_NO_DATA = (1 << 6),
};
/**
@@ -425,7 +428,7 @@ GIT_EXTERN(int) git_diff_print_patch(
* @param diff A git_diff_list generated by one of the above functions
* @return Count of number of deltas in the list
*/
-GIT_EXTERN(size_t) git_diff_entrycount(git_diff_list *diff);
+GIT_EXTERN(size_t) git_diff_num_deltas(git_diff_list *diff);
/**
* Query how many diff deltas are there in a diff list filtered by type.
@@ -438,7 +441,7 @@ GIT_EXTERN(size_t) git_diff_entrycount(git_diff_list *diff);
* @param type A git_delta_t value to filter the count
* @return Count of number of deltas matching delta_t type
*/
-GIT_EXTERN(size_t) git_diff_entrycount_of_type(
+GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
git_diff_list *diff,
git_delta_t type);
@@ -469,7 +472,7 @@ GIT_EXTERN(size_t) git_diff_entrycount_of_type(
*/
GIT_EXTERN(int) git_diff_get_patch(
git_diff_patch **patch,
- const git_diff_delta **delta,
+ git_diff_delta **delta,
git_diff_list *diff,
size_t idx);
@@ -482,43 +485,76 @@ GIT_EXTERN(void) git_diff_patch_free(
/**
* Get the delta associated with a patch
*/
-GIT_EXTERN(void) git_diff_patch_get_delta(
- const git_diff_delta **delta,
+GIT_EXTERN(const git_diff_delta *) git_diff_patch_delta(
git_diff_patch *patch);
/**
* Get the number of hunks in a patch
*/
-GIT_EXTERN(size_t) git_diff_patch_hunks(
+GIT_EXTERN(size_t) git_diff_patch_num_hunks(
git_diff_patch *patch);
/**
* Get the information about a hunk in a patch
+ *
+ * Given a patch and a hunk index into the patch, this returns detailed
+ * information about that hunk. Any of the output pointers can be passed
+ * as NULL if you don't care about that particular piece of information.
+ *
+ * @param range Output pointer to git_diff_range of hunk
+ * @param header Output pointer to header string for hunk. Unlike the
+ * content pointer for each line, this will be NUL-terminated
+ * @param header_len Output value of characters in header string
+ * @param lines_in_hunk Output count of total lines in this hunk
+ * @param patch Input pointer to patch object
+ * @param hunk_idx Input index of hunk to get information about
+ * @return 0 on success, GIT_ENOTFOUND if hunk_idx out of range, <0 on error
*/
GIT_EXTERN(int) git_diff_patch_get_hunk(
- const git_diff_range **range,
+ git_diff_range **range,
const char **header,
size_t *header_len,
size_t *lines_in_hunk,
git_diff_patch *patch,
- size_t hunk);
+ size_t hunk_idx);
/**
- * Get the number of lines in a hunk
+ * Get the number of lines in a hunk.
+ *
+ * @param patch The git_diff_patch object
+ * @param hunk_idx Index of the hunk
+ * @return Number of lines in hunk or -1 if invalid hunk index
*/
-GIT_EXTERN(size_t) git_diff_patch_lines_in_hunk(
+GIT_EXTERN(int) git_diff_patch_num_lines_in_hunk(
git_diff_patch *patch,
- size_t hunk);
+ size_t hunk_idx);
/**
- * Get a line in a hunk of a patch
+ * Get data about a line in a hunk of a patch.
+ *
+ * Given a patch, a hunk index, and a line index in the hunk, this
+ * will return a lot of details about that line. If you pass a hunk
+ * index larger than the number of hunks or a line index larger than
+ * the number of lines in the hunk, this will return -1.
+ *
+ * @param line_origin A GIT_DIFF_LINE constant from above
+ * @param content Pointer to content of diff line, not NUL-terminated
+ * @param content_len Number of characters in content
+ * @param old_lineno Line number in old file or -1 if line is added
+ * @param new_lineno Line number in new file or -1 if line is deleted
+ * @param patch The patch to look in
+ * @param hunk_idx The index of the hunk
+ * @param line_of_index The index of the line in the hunk
+ * @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
char *line_origin,
const char **content,
size_t *content_len,
+ int *old_lineno,
+ int *new_lineno,
git_diff_patch *patch,
- size_t hunk,
+ size_t hunk_idx,
size_t line_of_hunk);
/**@}*/
diff --git a/src/diff.c b/src/diff.c
index 499b95b44..7c8e2a9bb 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -5,8 +5,6 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/diff.h"
-#include "git2/oid.h"
#include "diff.h"
#include "fileops.h"
#include "config.h"
@@ -268,9 +266,17 @@ static int diff_delta__from_two(
delta->old_file.mode = old_mode;
delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID;
- git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid);
+ git_oid_cpy(&delta->new_file.oid, &new_entry->oid);
delta->new_file.size = new_entry->file_size;
delta->new_file.mode = new_mode;
+
+ if (new_oid) {
+ if ((diff->opts.flags & GIT_DIFF_REVERSE) != 0)
+ git_oid_cpy(&delta->old_file.oid, new_oid);
+ else
+ git_oid_cpy(&delta->new_file.oid, new_oid);
+ }
+
if (new_oid || !git_oid_iszero(&new_entry->oid))
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
@@ -425,6 +431,11 @@ void git_diff_list_free(git_diff_list *diff)
GIT_REFCOUNT_DEC(diff, diff_list_free);
}
+void git_diff_list_addref(git_diff_list *diff)
+{
+ GIT_REFCOUNT_INC(diff);
+}
+
static int oid_for_workdir_item(
git_repository *repo,
const git_index_entry *item,
@@ -519,17 +530,17 @@ static int maybe_modified(
omode == nmode)
status = GIT_DELTA_UNMODIFIED;
- /* if modes match and we have an unknown OID and a workdir iterator,
- * then check deeper for matching
+ /* if we have an unknown OID and a workdir iterator, then check some
+ * circumstances that can accelerate things or need special handling
*/
- else if (omode == nmode &&
- git_oid_iszero(&nitem->oid) &&
- new_iter->type == GIT_ITERATOR_WORKDIR)
+ else if (git_oid_iszero(&nitem->oid) &&
+ new_iter->type == GIT_ITERATOR_WORKDIR)
{
/* TODO: add check against index file st_mtime to avoid racy-git */
- /* if they files look exactly alike, then we'll assume the same */
- if (oitem->file_size == nitem->file_size &&
+ /* if the stat data looks exactly alike, then assume the same */
+ if (omode == nmode &&
+ oitem->file_size == nitem->file_size &&
(!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
(oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds &&
@@ -554,16 +565,15 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED;
}
}
+ }
- /* TODO: check git attributes so we will not have to read the file
- * in if it is marked binary.
- */
-
- else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
+ /* 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 (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
+ if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1;
-
- else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
- omode == nmode)
+ else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
status = GIT_DELTA_UNMODIFIED;
/* store calculated oid so we don't have to recalc later */
diff --git a/src/diff.h b/src/diff.h
index ea38a678f..862c33c1b 100644
--- a/src/diff.h
+++ b/src/diff.h
@@ -7,6 +7,9 @@
#ifndef INCLUDE_diff_h__
#define INCLUDE_diff_h__
+#include "git2/diff.h"
+#include "git2/oid.h"
+
#include <stdio.h>
#include "vector.h"
#include "buffer.h"
@@ -25,14 +28,17 @@ enum {
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
-#define MAX_DIFF_FILESIZE 0x20000000
+typedef struct {
+ git_refcount rc;
+ git_diff_delta delta;
+} git_diff_delta_refcounted;
struct git_diff_list {
git_refcount rc;
git_repository *repo;
git_diff_options opts;
git_vector pathspec;
- git_vector deltas; /* vector of git_diff_file_delta */
+ git_vector deltas; /* vector of git_diff_delta_refcounted */
git_pool pool;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
@@ -42,27 +48,7 @@ struct git_diff_list {
extern void git_diff__cleanup_modes(
uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
-/**
- * Return the maximum possible number of files in the diff.
- *
- * NOTE: This number has to be treated as an upper bound on the number of
- * files that have changed if the diff is with the working directory.
- *
- * Why?! For efficiency, we defer loading the file contents as long as
- * possible, so if a file has been "touched" in the working directory and
- * then reverted to the original content, it may get stored in the diff list
- * as MODIFIED along with a flag that the status should be reconfirmed when
- * it is actually loaded into memory. When that load happens, it could get
- * flipped to UNMODIFIED. If unmodified files are being skipped, then the
- * iterator will skip that file and this number may be too high.
- *
- * This behavior is true of `git_diff_foreach` as well, but the only
- * implication there is that the `progress` value would not advance evenly.
- *
- * @param iterator The iterator object
- * @return The maximum number of files to be iterated over
- */
-int git_diff_iterator__max_files(git_diff_iterator *iterator);
+extern void git_diff_list_addref(git_diff_list *diff);
#endif
diff --git a/src/diff_output.c b/src/diff_output.c
index 58a1a3567..b84b0c2a4 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -5,58 +5,13 @@
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
-#include "git2/diff.h"
#include "git2/attr.h"
-#include "git2/blob.h"
#include "git2/oid.h"
-#include "xdiff/xdiff.h"
+#include "diff_output.h"
#include <ctype.h>
-#include "diff.h"
-#include "map.h"
#include "fileops.h"
#include "filter.h"
-/*
- * A diff_delta_context represents all of the information that goes into
- * processing the diff of an observed file change. In the case of the
- * git_diff_foreach() call it is an emphemeral structure that is filled
- * in to execute each diff. In the case of a git_diff_iterator, it holds
- * most of the information for the diff in progress.
- *
- * As each delta is processed, it goes through 3 phases: prep, load, exec.
- *
- * - In the prep phase, we just set the delta and quickly check the file
- * attributes to see if it should be treated as binary.
- * - In the load phase, we actually load the file content into memory.
- * At this point, if we had deferred calculating OIDs, we might have to
- * correct the delta to be UNMODIFIED.
- * - In the exec phase, we actually run the diff and execute the callbacks.
- * For foreach, this is just a pass-through to the user's callbacks. For
- * iterators, we record the hunks and data spans into memory.
- */
-typedef struct {
- git_repository *repo;
- git_diff_options *opts;
- xdemitconf_t xdiff_config;
- xpparam_t xdiff_params;
- git_diff_delta *delta;
- uint32_t prepped : 1;
- uint32_t loaded : 1;
- uint32_t diffable : 1;
- uint32_t diffed : 1;
- git_iterator_type_t old_src;
- git_iterator_type_t new_src;
- git_blob *old_blob;
- git_blob *new_blob;
- git_map old_data;
- git_map new_data;
- void *cb_data;
- git_diff_hunk_fn per_hunk;
- git_diff_data_fn per_line;
- int cb_error;
- git_diff_range range;
-} diff_delta_context;
-
static int read_next_int(const char **str, int *value)
{
const char *scan = *str;
@@ -96,55 +51,28 @@ static int parse_hunk_header(git_diff_range *range, const char *header)
return 0;
}
-static int format_hunk_header(char *header, size_t len, git_diff_range *range)
+static bool diff_delta_should_skip(
+ diff_context *ctxt, git_diff_delta *delta)
{
- if (range->old_lines != 1) {
- if (range->new_lines != 1)
- return p_snprintf(
- header, len, "@@ -%d,%d +%d,%d @@",
- range->old_start, range->old_lines,
- range->new_start, range->new_lines);
- else
- return p_snprintf(
- header, len, "@@ -%d,%d +%d @@",
- range->old_start, range->old_lines, range->new_start);
- } else {
- if (range->new_lines != 1)
- return p_snprintf(
- header, len, "@@ -%d +%d,%d @@",
- range->old_start, range->new_start, range->new_lines);
- else
- return p_snprintf(
- header, len, "@@ -%d +%d @@",
- range->old_start, range->new_start);
- }
-}
-
-static bool diff_delta_is_ambiguous(git_diff_delta *delta)
-{
- return (git_oid_iszero(&delta->new_file.oid) &&
- (delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0 &&
- delta->status == GIT_DELTA_MODIFIED);
-}
+ uint32_t flags = ctxt->opts ? ctxt->opts->flags : 0;
-static bool diff_delta_should_skip(git_diff_options *opts, git_diff_delta *delta)
-{
if (delta->status == GIT_DELTA_UNMODIFIED &&
- (opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
+ (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true;
if (delta->status == GIT_DELTA_IGNORED &&
- (opts->flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
+ (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true;
if (delta->status == GIT_DELTA_UNTRACKED &&
- (opts->flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
+ (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true;
return false;
}
-#define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
+#define KNOWN_BINARY_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY)
+#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
static int update_file_is_binary_by_attr(
git_repository *repo, git_diff_file *file)
@@ -173,8 +101,6 @@ static void update_delta_is_binary(git_diff_delta *delta)
(delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)
delta->binary = 1;
-#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA)
-
else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
(delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
delta->binary = 0;
@@ -182,10 +108,11 @@ static void update_delta_is_binary(git_diff_delta *delta)
/* otherwise leave delta->binary value untouched */
}
-static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
+static int diff_delta_is_binary_by_attr(
+ diff_context *ctxt, git_diff_patch *patch)
{
int error = 0, mirror_new;
- git_diff_delta *delta = ctxt->delta;
+ git_diff_delta *delta = patch->delta;
delta->binary = -1;
@@ -200,7 +127,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
}
/* check if user is forcing us to text diff these files */
- if (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) {
+ if (ctxt->opts && (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) != 0) {
delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->binary = 0;
@@ -214,7 +141,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
mirror_new = (delta->new_file.path == delta->old_file.path ||
strcmp(delta->new_file.path, delta->old_file.path) == 0);
if (mirror_new)
- delta->new_file.flags |= (delta->old_file.flags & BINARY_DIFF_FLAGS);
+ delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS);
else
error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file);
@@ -224,11 +151,13 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
}
static int diff_delta_is_binary_by_content(
- diff_delta_context *ctxt, git_diff_file *file, git_map *map)
+ diff_context *ctxt, git_diff_delta *delta, git_diff_file *file, git_map *map)
{
git_buf search;
- if ((file->flags & BINARY_DIFF_FLAGS) == 0) {
+ GIT_UNUSED(ctxt);
+
+ if ((file->flags & KNOWN_BINARY_FLAGS) == 0) {
search.ptr = map->data;
search.size = min(map->len, 4000);
@@ -238,17 +167,17 @@ static int diff_delta_is_binary_by_content(
file->flags |= GIT_DIFF_FILE_NOT_BINARY;
}
- update_delta_is_binary(ctxt->delta);
+ update_delta_is_binary(delta);
return 0;
}
static int diff_delta_is_binary_by_size(
- diff_delta_context *ctxt, git_diff_file *file)
+ diff_context *ctxt, git_diff_delta *delta, git_diff_file *file)
{
git_off_t threshold = MAX_DIFF_FILESIZE;
- if ((file->flags & BINARY_DIFF_FLAGS) != 0)
+ if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
return 0;
if (ctxt && ctxt->opts) {
@@ -262,7 +191,7 @@ static int diff_delta_is_binary_by_size(
if (file->size > threshold)
file->flags |= GIT_DIFF_FILE_BINARY;
- update_delta_is_binary(ctxt->delta);
+ update_delta_is_binary(delta);
return 0;
}
@@ -289,8 +218,10 @@ static void setup_xdiff_options(
param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
}
+
static int get_blob_content(
- diff_delta_context *ctxt,
+ diff_context *ctxt,
+ git_diff_delta *delta,
git_diff_file *file,
git_map *map,
git_blob **blob)
@@ -318,9 +249,9 @@ static int get_blob_content(
}
/* if blob is too large to diff, mark as binary */
- if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0)
+ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0)
return error;
- if (ctxt->delta->binary == 1)
+ if (delta->binary == 1)
return 0;
if (odb_obj != NULL) {
@@ -336,11 +267,12 @@ static int get_blob_content(
map->data = (void *)git_blob_rawcontent(*blob);
map->len = git_blob_rawsize(*blob);
- return diff_delta_is_binary_by_content(ctxt, file, map);
+ return diff_delta_is_binary_by_content(ctxt, delta, file, map);
}
static int get_workdir_content(
- diff_delta_context *ctxt,
+ diff_context *ctxt,
+ git_diff_delta *delta,
git_diff_file *file,
git_map *map)
{
@@ -386,8 +318,8 @@ static int get_workdir_content(
if (!file->size)
file->size = git_futils_filesize(fd);
- if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0 ||
- ctxt->delta->binary == 1)
+ if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
+ delta->binary == 1)
goto close_and_cleanup;
error = git_filters_load(
@@ -428,7 +360,7 @@ close_and_cleanup:
}
if (!error)
- error = diff_delta_is_binary_by_content(ctxt, file, map);
+ error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
cleanup:
git_buf_free(&path);
@@ -454,78 +386,101 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob)
}
}
-static void diff_delta_init_context(
- diff_delta_context *ctxt,
- git_repository *repo,
+
+static void diff_context_init(
+ diff_context *ctxt,
+ git_diff_list *diff,
+ git_repository *repo,
git_diff_options *opts,
- git_iterator_type_t old_src,
- git_iterator_type_t new_src)
+ void *data,
+ git_diff_file_fn file_cb,
+ git_diff_hunk_fn hunk_cb,
+ git_diff_data_fn data_cb)
{
- memset(ctxt, 0, sizeof(diff_delta_context));
+ memset(ctxt, 0, sizeof(diff_context));
ctxt->repo = repo;
+ ctxt->diff = diff;
ctxt->opts = opts;
- ctxt->old_src = old_src;
- ctxt->new_src = new_src;
+ ctxt->file_cb = file_cb;
+ ctxt->hunk_cb = hunk_cb;
+ ctxt->data_cb = data_cb;
+ ctxt->cb_data = data;
+ ctxt->cb_error = 0;
- setup_xdiff_options(opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
+ setup_xdiff_options(ctxt->opts, &ctxt->xdiff_config, &ctxt->xdiff_params);
}
-static void diff_delta_init_context_from_diff_list(
- diff_delta_context *ctxt,
- git_diff_list *diff)
+static bool diff_delta_file_callback(
+ diff_context *ctxt, git_diff_delta *delta, size_t idx)
{
- diff_delta_init_context(
- ctxt, diff->repo, &diff->opts, diff->old_src, diff->new_src);
+ float progress;
+
+ if (!ctxt->file_cb)
+ return 0;
+
+ progress = (float)idx / ctxt->diff->deltas.length;
+
+ if (ctxt->file_cb(ctxt->cb_data, delta, progress) != 0)
+ return GIT_EUSER;
+
+ return 0;
}
-static void diff_delta_unload(diff_delta_context *ctxt)
+static void diff_patch_init(
+ diff_context *ctxt, git_diff_patch *patch)
{
- ctxt->diffed = 0;
+ memset(patch, 0, sizeof(*patch));
- if (ctxt->loaded) {
- release_content(&ctxt->delta->old_file, &ctxt->old_data, ctxt->old_blob);
- release_content(&ctxt->delta->new_file, &ctxt->new_data, ctxt->new_blob);
- ctxt->loaded = 0;
- }
+ patch->diff = ctxt->diff;
+ patch->ctxt = ctxt;
- ctxt->delta = NULL;
- ctxt->prepped = 0;
+ if (patch->diff) {
+ patch->old_src = patch->diff->old_src;
+ patch->new_src = patch->diff->new_src;
+ } else {
+ patch->old_src = patch->new_src = GIT_ITERATOR_TREE;
+ }
}
-static int diff_delta_prep(diff_delta_context *ctxt)
+static git_diff_patch *diff_patch_alloc(
+ diff_context *ctxt, git_diff_delta *delta)
{
- int error;
+ git_diff_patch *patch = git__malloc(sizeof(git_diff_patch));
+ if (!patch)
+ return NULL;
- if (ctxt->prepped || !ctxt->delta)
- return 0;
+ diff_patch_init(ctxt, patch);
- error = diff_delta_is_binary_by_attr(ctxt);
+ git_diff_list_addref(patch->diff);
- ctxt->prepped = !error;
+ GIT_REFCOUNT_INC(&patch);
- return error;
+ patch->delta = delta;
+ patch->flags = GIT_DIFF_PATCH_ALLOCATED;
+
+ return patch;
}
-static int diff_delta_load(diff_delta_context *ctxt)
+static int diff_patch_load(
+ diff_context *ctxt, git_diff_patch *patch)
{
int error = 0;
- git_diff_delta *delta = ctxt->delta;
+ git_diff_delta *delta = patch->delta;
bool check_if_unmodified = false;
- if (ctxt->loaded || !ctxt->delta)
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
return 0;
- if (!ctxt->prepped && (error = diff_delta_prep(ctxt)) < 0)
- goto cleanup;
+ error = diff_delta_is_binary_by_attr(ctxt, patch);
- ctxt->old_data.data = "";
- ctxt->old_data.len = 0;
- ctxt->old_blob = NULL;
+ patch->old_data.data = "";
+ patch->old_data.len = 0;
+ patch->old_blob = NULL;
- ctxt->new_data.data = "";
- ctxt->new_data.len = 0;
- ctxt->new_blob = NULL;
+ patch->new_data.data = "";
+ patch->new_data.len = 0;
+ patch->new_blob = NULL;
if (delta->binary == 1)
goto cleanup;
@@ -557,36 +512,38 @@ static int diff_delta_load(diff_delta_context *ctxt)
*/
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
- ctxt->old_src == GIT_ITERATOR_WORKDIR) {
+ patch->old_src == GIT_ITERATOR_WORKDIR) {
if ((error = get_workdir_content(
- ctxt, &delta->old_file, &ctxt->old_data)) < 0)
+ ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
goto cleanup;
if (delta->binary == 1)
goto cleanup;
}
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
- ctxt->new_src == GIT_ITERATOR_WORKDIR) {
+ patch->new_src == GIT_ITERATOR_WORKDIR) {
if ((error = get_workdir_content(
- ctxt, &delta->new_file, &ctxt->new_data)) < 0)
+ ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
goto cleanup;
if (delta->binary == 1)
goto cleanup;
}
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
- ctxt->old_src != GIT_ITERATOR_WORKDIR) {
+ patch->old_src != GIT_ITERATOR_WORKDIR) {
if ((error = get_blob_content(
- ctxt, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0)
+ ctxt, delta, &delta->old_file,
+ &patch->old_data, &patch->old_blob)) < 0)
goto cleanup;
if (delta->binary == 1)
goto cleanup;
}
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 &&
- ctxt->new_src != GIT_ITERATOR_WORKDIR) {
+ patch->new_src != GIT_ITERATOR_WORKDIR) {
if ((error = get_blob_content(
- ctxt, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0)
+ ctxt, delta, &delta->new_file,
+ &patch->new_data, &patch->new_blob)) < 0)
goto cleanup;
if (delta->binary == 1)
goto cleanup;
@@ -606,33 +563,34 @@ static int diff_delta_load(diff_delta_context *ctxt)
}
cleanup:
- /* last change to update binary flag */
if (delta->binary == -1)
update_delta_is_binary(delta);
- ctxt->loaded = !error;
+ if (!error) {
+ patch->flags |= GIT_DIFF_PATCH_LOADED;
- /* flag if we would want to diff the contents of these files */
- if (ctxt->loaded)
- ctxt->diffable =
- (delta->binary != 1 &&
- delta->status != GIT_DELTA_UNMODIFIED &&
- (ctxt->old_data.len || ctxt->new_data.len) &&
- git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid));
+ if (delta->binary != 1 &&
+ delta->status != GIT_DELTA_UNMODIFIED &&
+ (patch->old_data.len || patch->new_data.len) &&
+ !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid))
+ patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
+ }
return error;
}
-static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
+static int diff_patch_cb(void *priv, mmbuffer_t *bufs, int len)
{
- diff_delta_context *ctxt = priv;
+ git_diff_patch *patch = priv;
+ diff_context *ctxt = patch->ctxt;
if (len == 1) {
- if ((ctxt->cb_error = parse_hunk_header(&ctxt->range, bufs[0].ptr)) < 0)
+ ctxt->cb_error = parse_hunk_header(&ctxt->cb_range, bufs[0].ptr);
+ if (ctxt->cb_error < 0)
return ctxt->cb_error;
- if (ctxt->per_hunk != NULL &&
- ctxt->per_hunk(ctxt->cb_data, ctxt->delta, &ctxt->range,
+ if (ctxt->hunk_cb != NULL &&
+ ctxt->hunk_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[0].ptr, bufs[0].size))
ctxt->cb_error = GIT_EUSER;
}
@@ -644,9 +602,9 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
GIT_DIFF_LINE_CONTEXT;
- if (ctxt->per_line != NULL &&
- ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin,
- bufs[1].ptr, bufs[1].size))
+ if (ctxt->data_cb != NULL &&
+ ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
+ origin, bufs[1].ptr, bufs[1].size))
ctxt->cb_error = GIT_EUSER;
}
@@ -661,105 +619,268 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
GIT_DIFF_LINE_CONTEXT;
- if (ctxt->per_line != NULL &&
- ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin,
- bufs[2].ptr, bufs[2].size))
+ if (ctxt->data_cb != NULL &&
+ ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
+ origin, bufs[2].ptr, bufs[2].size))
ctxt->cb_error = GIT_EUSER;
}
return ctxt->cb_error;
}
-static int diff_delta_exec(
- diff_delta_context *ctxt,
- void *cb_data,
- git_diff_hunk_fn per_hunk,
- git_diff_data_fn per_line)
+static int diff_patch_generate(
+ diff_context *ctxt, git_diff_patch *patch)
{
int error = 0;
xdemitcb_t xdiff_callback;
mmfile_t old_xdiff_data, new_xdiff_data;
- if (ctxt->diffed || !ctxt->delta)
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
return 0;
- if (!ctxt->loaded && (error = diff_delta_load(ctxt)) < 0)
- goto cleanup;
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
+ if ((error = diff_patch_load(ctxt, patch)) < 0)
+ return error;
- if (!ctxt->diffable)
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
return 0;
- ctxt->cb_data = cb_data;
- ctxt->per_hunk = per_hunk;
- ctxt->per_line = per_line;
- ctxt->cb_error = 0;
+ if (ctxt)
+ patch->ctxt = ctxt;
memset(&xdiff_callback, 0, sizeof(xdiff_callback));
- xdiff_callback.outf = diff_delta_cb;
- xdiff_callback.priv = ctxt;
+ xdiff_callback.outf = diff_patch_cb;
+ xdiff_callback.priv = patch;
- old_xdiff_data.ptr = ctxt->old_data.data;
- old_xdiff_data.size = ctxt->old_data.len;
- new_xdiff_data.ptr = ctxt->new_data.data;
- new_xdiff_data.size = ctxt->new_data.len;
+ old_xdiff_data.ptr = patch->old_data.data;
+ old_xdiff_data.size = patch->old_data.len;
+ new_xdiff_data.ptr = patch->new_data.data;
+ new_xdiff_data.size = patch->new_data.len;
xdl_diff(&old_xdiff_data, &new_xdiff_data,
&ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);
error = ctxt->cb_error;
-cleanup:
- ctxt->diffed = !error;
+ if (!error)
+ patch->flags |= GIT_DIFF_PATCH_DIFFED;
return error;
}
+static void diff_patch_unload(git_diff_patch *patch)
+{
+ if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) {
+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_DIFFED);
+
+ patch->hunks_size = 0;
+ patch->lines_size = 0;
+ }
+
+ if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) {
+ patch->flags = (patch->flags & ~GIT_DIFF_PATCH_LOADED);
+
+ release_content(
+ &patch->delta->old_file, &patch->old_data, patch->old_blob);
+ release_content(
+ &patch->delta->new_file, &patch->new_data, patch->new_blob);
+ }
+}
+
+static void diff_patch_free(git_diff_patch *patch)
+{
+ diff_patch_unload(patch);
+
+ git__free(patch->lines);
+ patch->lines = NULL;
+ patch->lines_asize = 0;
+
+ git__free(patch->hunks);
+ patch->hunks = NULL;
+ patch->hunks_asize = 0;
+
+ if (!(patch->flags & GIT_DIFF_PATCH_ALLOCATED))
+ return;
+
+ patch->flags = 0;
+
+ git_diff_list_free(patch->diff); /* decrements refcount */
+
+ git__free(patch);
+}
+
+#define MAX_HUNK_STEP 128
+#define MIN_HUNK_STEP 8
+#define MAX_LINE_STEP 256
+#define MIN_LINE_STEP 8
+
+static int diff_patch_hunk_cb(
+ void *cb_data,
+ git_diff_delta *delta,
+ git_diff_range *range,
+ const char *header,
+ size_t header_len)
+{
+ git_diff_patch *patch = cb_data;
+ diff_patch_hunk *hunk;
+
+ GIT_UNUSED(delta);
+
+ if (patch->hunks_size >= patch->hunks_asize) {
+ size_t new_size;
+ diff_patch_hunk *new_hunks;
+
+ if (patch->hunks_asize > MAX_HUNK_STEP)
+ new_size = patch->hunks_asize + MAX_HUNK_STEP;
+ else
+ new_size = patch->hunks_asize * 2;
+ if (new_size < MIN_HUNK_STEP)
+ new_size = MIN_HUNK_STEP;
+
+ new_hunks = git__realloc(
+ patch->hunks, new_size * sizeof(diff_patch_hunk));
+ if (!new_hunks)
+ return -1;
+
+ patch->hunks = new_hunks;
+ patch->hunks_asize = new_size;
+ }
+
+ hunk = &patch->hunks[patch->hunks_size++];
+
+ memcpy(&hunk->range, range, sizeof(hunk->range));
+
+ assert(header_len + 1 < sizeof(hunk->header));
+ memcpy(&hunk->header, header, header_len);
+ hunk->header[header_len] = '\0';
+ hunk->header_len = header_len;
+
+ hunk->line_start = patch->lines_size;
+ hunk->line_count = 0;
+
+ return 0;
+}
+
+static int diff_patch_line_cb(
+ void *cb_data,
+ git_diff_delta *delta,
+ git_diff_range *range,
+ char line_origin,
+ const char *content,
+ size_t content_len)
+{
+ git_diff_patch *patch = cb_data;
+ diff_patch_hunk *hunk;
+ diff_patch_line *last, *line;
+
+ GIT_UNUSED(delta);
+ GIT_UNUSED(range);
+
+ assert(patch->hunks_size > 0);
+ assert(patch->hunks != NULL);
+
+ hunk = &patch->hunks[patch->hunks_size - 1];
+
+ if (patch->lines_size >= patch->lines_asize) {
+ size_t new_size;
+ diff_patch_line *new_lines;
+
+ if (patch->lines_asize > MAX_LINE_STEP)
+ new_size = patch->lines_asize + MAX_LINE_STEP;
+ else
+ new_size = patch->lines_asize * 2;
+ if (new_size < MIN_LINE_STEP)
+ new_size = MIN_LINE_STEP;
+
+ new_lines = git__realloc(
+ patch->lines, new_size * sizeof(diff_patch_line));
+ if (!new_lines)
+ return -1;
+
+ patch->lines = new_lines;
+ patch->lines_asize = new_size;
+ }
+
+ last = (patch->lines_size > 0) ?
+ &patch->lines[patch->lines_size - 1] : NULL;
+ line = &patch->lines[patch->lines_size++];
+
+ line->ptr = content;
+ line->len = content_len;
+ line->origin = line_origin;
+
+ /* do some bookkeeping so we can provide old/new line numbers */
+
+ for (line->lines = 0; content_len > 0; --content_len) {
+ if (*content++ == '\n')
+ ++line->lines;
+ }
+
+ if (!last) {
+ line->oldno = hunk->range.old_start;
+ line->newno = hunk->range.new_start;
+ } else {
+ switch (last->origin) {
+ case GIT_DIFF_LINE_ADDITION:
+ line->oldno = last->oldno;
+ line->newno = last->newno + last->lines;
+ break;
+ case GIT_DIFF_LINE_DELETION:
+ line->oldno = last->oldno + last->lines;
+ line->newno = last->newno;
+ break;
+ default:
+ line->oldno = last->oldno + last->lines;
+ line->newno = last->newno + last->lines;
+ break;
+ }
+ }
+
+ return 0;
+}
+
+
int git_diff_foreach(
git_diff_list *diff,
- void *data,
+ void *cb_data,
git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb,
- git_diff_data_fn line_cb)
+ git_diff_data_fn data_cb)
{
- int error = 0;
- diff_delta_context ctxt;
+ int error;
+ diff_context ctxt;
size_t idx;
+ git_diff_patch patch;
- diff_delta_init_context_from_diff_list(&ctxt, diff);
+ diff_context_init(
+ &ctxt, diff, diff->repo, &diff->opts,
+ cb_data, file_cb, hunk_cb, data_cb);
- git_vector_foreach(&diff->deltas, idx, ctxt.delta) {
- if (diff_delta_is_ambiguous(ctxt.delta))
- if ((error = diff_delta_load(&ctxt)) < 0)
- goto cleanup;
+ diff_patch_init(&ctxt, &patch);
- if (diff_delta_should_skip(ctxt.opts, ctxt.delta))
- continue;
+ git_vector_foreach(&diff->deltas, idx, patch.delta) {
- if ((error = diff_delta_load(&ctxt)) < 0)
- goto cleanup;
-
- if (file_cb != NULL &&
- file_cb(data, ctxt.delta, (float)idx / diff->deltas.length) != 0)
- {
- error = GIT_EUSER;
- goto cleanup;
- }
+ /* check flags against patch status */
+ if (diff_delta_should_skip(&ctxt, patch.delta))
+ continue;
- error = diff_delta_exec(&ctxt, data, hunk_cb, line_cb);
+ if (!(error = diff_patch_load(&ctxt, &patch)) &&
+ !(error = diff_delta_file_callback(&ctxt, patch.delta, idx)))
+ error = diff_patch_generate(&ctxt, &patch);
-cleanup:
- diff_delta_unload(&ctxt);
+ diff_patch_unload(&patch);
if (error < 0)
break;
}
if (error == GIT_EUSER)
- giterr_clear();
+ giterr_clear(); /* don't let error message leak */
return error;
}
+
typedef struct {
git_diff_list *diff;
git_diff_data_fn print_cb;
@@ -851,7 +972,6 @@ int git_diff_print_compact(
return error;
}
-
static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
{
char start_oid[8], end_oid[8];
@@ -1024,30 +1144,6 @@ int git_diff_print_patch(
return error;
}
-int git_diff_entrycount(git_diff_list *diff, int delta_t)
-{
- int count = 0;
- unsigned int i;
- git_diff_delta *delta;
-
- assert(diff);
-
- git_vector_foreach(&diff->deltas, i, delta) {
- if (diff_delta_should_skip(&diff->opts, delta))
- continue;
-
- if (delta_t < 0 || delta->status == (git_delta_t)delta_t)
- count++;
- }
-
- /* It is possible that this has overcounted the number of diffs because
- * there may be entries that are marked as MODIFIED due to differences
- * in stat() output that will turn out to be the same once we calculate
- * the actual SHA of the data on disk.
- */
-
- return count;
-}
static void set_data_from_blob(
git_blob *blob, git_map *map, git_diff_file *file)
@@ -1056,6 +1152,7 @@ static void set_data_from_blob(
map->data = (char *)git_blob_rawcontent(blob);
file->size = map->len = git_blob_rawsize(blob);
git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
+ file->mode = 0644;
} else {
map->data = "";
file->size = map->len = 0;
@@ -1070,429 +1167,250 @@ int git_diff_blobs(
void *cb_data,
git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb,
- git_diff_data_fn line_cb)
+ git_diff_data_fn data_cb)
{
int error;
- diff_delta_context ctxt;
- git_diff_delta delta;
- git_blob *new, *old;
git_repository *repo;
-
- new = new_blob;
- old = old_blob;
+ diff_context ctxt;
+ git_diff_delta delta;
+ git_diff_patch patch;
if (options && (options->flags & GIT_DIFF_REVERSE)) {
- git_blob *swap = old;
- old = new;
- new = swap;
+ git_blob *swap = old_blob;
+ old_blob = new_blob;
+ new_blob = swap;
}
- if (new)
- repo = git_object_owner((git_object *)new);
- else if (old)
- repo = git_object_owner((git_object *)old);
+ if (new_blob)
+ repo = git_object_owner((git_object *)new_blob);
+ else if (old_blob)
+ repo = git_object_owner((git_object *)old_blob);
else
repo = NULL;
- diff_delta_init_context(
- &ctxt, repo, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE);
+ diff_context_init(
+ &ctxt, NULL, repo, options,
+ cb_data, file_cb, hunk_cb, data_cb);
+
+ diff_patch_init(&ctxt, &patch);
- /* populate a "fake" delta record */
+ /* create a fake delta record and simulate diff_patch_load */
memset(&delta, 0, sizeof(delta));
+ delta.binary = -1;
- set_data_from_blob(old, &ctxt.old_data, &delta.old_file);
- set_data_from_blob(new, &ctxt.new_data, &delta.new_file);
+ set_data_from_blob(old_blob, &patch.old_data, &delta.old_file);
+ set_data_from_blob(new_blob, &patch.new_data, &delta.new_file);
- delta.status = new ?
- (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
- (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
+ delta.status = new_blob ?
+ (old_blob ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
+ (old_blob ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0)
delta.status = GIT_DELTA_UNMODIFIED;
- ctxt.delta = &delta;
+ patch.delta = &delta;
- if ((error = diff_delta_prep(&ctxt)) < 0)
+ if ((error = diff_delta_is_binary_by_content(
+ &ctxt, &delta, &delta.old_file, &patch.old_data)) < 0 ||
+ (error = diff_delta_is_binary_by_content(
+ &ctxt, &delta, &delta.new_file, &patch.new_data)) < 0)
goto cleanup;
- if (delta.binary == -1) {
- if ((error = diff_delta_is_binary_by_content(
- &ctxt, &delta.old_file, &ctxt.old_data)) < 0 ||
- (error = diff_delta_is_binary_by_content(
- &ctxt, &delta.new_file, &ctxt.new_data)) < 0)
- goto cleanup;
- }
-
- ctxt.loaded = 1;
- ctxt.diffable = (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED);
+ patch.flags |= GIT_DIFF_PATCH_LOADED;
+ if (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED)
+ patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
/* do diffs */
- if (file_cb != NULL && file_cb(cb_data, &delta, 1)) {
- error = GIT_EUSER;
- goto cleanup;
- }
-
- error = diff_delta_exec(&ctxt, cb_data, hunk_cb, line_cb);
+ if (!(error = diff_delta_file_callback(&ctxt, patch.delta, 1)))
+ error = diff_patch_generate(&ctxt, &patch);
cleanup:
+ diff_patch_unload(&patch);
+
if (error == GIT_EUSER)
giterr_clear();
- diff_delta_unload(&ctxt);
-
return error;
}
-typedef struct diffiter_line diffiter_line;
-struct diffiter_line {
- diffiter_line *next;
- char origin;
- const char *ptr;
- size_t len;
-};
-
-typedef struct diffiter_hunk diffiter_hunk;
-struct diffiter_hunk {
- diffiter_hunk *next;
- git_diff_range range;
- diffiter_line *line_head;
- size_t line_count;
-};
-
-struct git_diff_iterator {
- git_diff_list *diff;
- diff_delta_context ctxt;
- size_t file_index;
- size_t next_index;
- git_pool hunks;
- size_t hunk_count;
- diffiter_hunk *hunk_head;
- diffiter_hunk *hunk_curr;
- char hunk_header[128];
- git_pool lines;
- diffiter_line *line_curr;
-};
-
-typedef struct {
- git_diff_iterator *iter;
- diffiter_hunk *last_hunk;
- diffiter_line *last_line;
-} diffiter_cb_info;
-static int diffiter_hunk_cb(
- void *cb_data,
- git_diff_delta *delta,
- git_diff_range *range,
- const char *header,
- size_t header_len)
+size_t git_diff_num_deltas(git_diff_list *diff)
{
- diffiter_cb_info *info = cb_data;
- git_diff_iterator *iter = info->iter;
- diffiter_hunk *hunk;
-
- GIT_UNUSED(delta);
- GIT_UNUSED(header);
- GIT_UNUSED(header_len);
-
- if ((hunk = git_pool_mallocz(&iter->hunks, 1)) == NULL) {
- iter->ctxt.cb_error = -1;
- return -1;
- }
-
- if (info->last_hunk)
- info->last_hunk->next = hunk;
- info->last_hunk = hunk;
- info->last_line = NULL;
-
- memcpy(&hunk->range, range, sizeof(hunk->range));
-
- iter->hunk_count++;
-
- /* adding first hunk to list */
- if (iter->hunk_head == NULL) {
- iter->hunk_head = hunk;
- iter->hunk_curr = NULL;
- }
-
- return 0;
+ assert(diff);
+ return (size_t)diff->deltas.length;
}
-static int diffiter_line_cb(
- void *cb_data,
- git_diff_delta *delta,
- git_diff_range *range,
- char line_origin,
- const char *content,
- size_t content_len)
+size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
{
- diffiter_cb_info *info = cb_data;
- git_diff_iterator *iter = info->iter;
- diffiter_line *line;
+ size_t i, count = 0;
+ git_diff_delta *delta;
- GIT_UNUSED(delta);
- GIT_UNUSED(range);
+ assert(diff);
- if ((line = git_pool_mallocz(&iter->lines, 1)) == NULL) {
- iter->ctxt.cb_error = -1;
- return -1;
+ git_vector_foreach(&diff->deltas, i, delta) {
+ count += (delta->status == type);
}
- if (info->last_line)
- info->last_line->next = line;
- info->last_line = line;
-
- line->origin = line_origin;
- line->ptr = content;
- line->len = content_len;
-
- info->last_hunk->line_count++;
-
- if (info->last_hunk->line_head == NULL)
- info->last_hunk->line_head = line;
-
- return 0;
+ return count;
}
-static int diffiter_do_diff_file(git_diff_iterator *iter)
+int git_diff_get_patch(
+ git_diff_patch **patch_ptr,
+ git_diff_delta **delta_ptr,
+ git_diff_list *diff,
+ size_t idx)
{
int error;
- diffiter_cb_info info;
-
- if (iter->ctxt.diffed || !iter->ctxt.delta)
- return 0;
-
- memset(&info, 0, sizeof(info));
- info.iter = iter;
-
- error = diff_delta_exec(
- &iter->ctxt, &info, diffiter_hunk_cb, diffiter_line_cb);
-
- if (error == GIT_EUSER)
- error = iter->ctxt.cb_error;
-
- return error;
-}
+ diff_context ctxt;
+ git_diff_delta *delta;
+ git_diff_patch *patch;
-static void diffiter_do_unload_file(git_diff_iterator *iter)
-{
- if (iter->ctxt.loaded) {
- diff_delta_unload(&iter->ctxt);
+ if (patch_ptr)
+ *patch_ptr = NULL;
- git_pool_clear(&iter->lines);
- git_pool_clear(&iter->hunks);
+ delta = git_vector_get(&diff->deltas, idx);
+ if (!delta) {
+ if (delta_ptr)
+ *delta_ptr = NULL;
+ giterr_set(GITERR_INVALID, "Index out of range for delta in diff");
+ return GIT_ENOTFOUND;
}
- iter->ctxt.delta = NULL;
- iter->hunk_curr = iter->hunk_head = NULL;
- iter->hunk_count = 0;
- iter->line_curr = NULL;
-}
-
-int git_diff_iterator_new(
- git_diff_iterator **iterator_ptr,
- git_diff_list *diff)
-{
- git_diff_iterator *iter;
-
- assert(diff && iterator_ptr);
-
- *iterator_ptr = NULL;
-
- iter = git__malloc(sizeof(git_diff_iterator));
- GITERR_CHECK_ALLOC(iter);
-
- memset(iter, 0, sizeof(*iter));
+ if (delta_ptr)
+ *delta_ptr = delta;
- iter->diff = diff;
- GIT_REFCOUNT_INC(iter->diff);
+ if (!patch_ptr && delta->binary != -1)
+ return 0;
- diff_delta_init_context_from_diff_list(&iter->ctxt, diff);
+ diff_context_init(
+ &ctxt, diff, diff->repo, &diff->opts,
+ NULL, NULL, diff_patch_hunk_cb, diff_patch_line_cb);
- if (git_pool_init(&iter->hunks, sizeof(diffiter_hunk), 0) < 0 ||
- git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0)
- goto fail;
+ if (diff_delta_should_skip(&ctxt, delta))
+ return 0;
- *iterator_ptr = iter;
+ patch = diff_patch_alloc(&ctxt, delta);
+ if (!patch)
+ return -1;
- return 0;
+ if (!(error = diff_patch_load(&ctxt, patch))) {
+ ctxt.cb_data = patch;
-fail:
- git_diff_iterator_free(iter);
+ error = diff_patch_generate(&ctxt, patch);
- return -1;
-}
-
-void git_diff_iterator_free(git_diff_iterator *iter)
-{
- diffiter_do_unload_file(iter);
- git_diff_list_free(iter->diff); /* decrement ref count */
- git__free(iter);
-}
+ if (error == GIT_EUSER)
+ error = ctxt.cb_error;
+ }
-float git_diff_iterator_progress(git_diff_iterator *iter)
-{
- return (float)iter->next_index / (float)iter->diff->deltas.length;
-}
+ if (error)
+ git_diff_patch_free(patch);
+ else if (patch_ptr)
+ *patch_ptr = patch;
-int git_diff_iterator__max_files(git_diff_iterator *iter)
-{
- return (int)iter->diff->deltas.length;
+ return error;
}
-int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter)
+void git_diff_patch_free(git_diff_patch *patch)
{
- int error = diffiter_do_diff_file(iter);
- return (error != 0) ? error : (int)iter->hunk_count;
+ if (patch)
+ GIT_REFCOUNT_DEC(patch, diff_patch_free);
}
-int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter)
+const git_diff_delta *git_diff_patch_delta(git_diff_patch *patch)
{
- int error = diffiter_do_diff_file(iter);
- if (error)
- return error;
-
- if (iter->hunk_curr)
- return (int)iter->hunk_curr->line_count;
- if (iter->hunk_head)
- return (int)iter->hunk_head->line_count;
- return 0;
+ assert(patch);
+ return patch->delta;
}
-int git_diff_iterator_next_file(
- git_diff_delta **delta_ptr,
- git_diff_iterator *iter)
+size_t git_diff_patch_num_hunks(git_diff_patch *patch)
{
- int error = 0;
-
- assert(iter);
-
- iter->file_index = iter->next_index;
-
- diffiter_do_unload_file(iter);
-
- while (!error) {
- iter->ctxt.delta = git_vector_get(&iter->diff->deltas, iter->file_index);
- if (!iter->ctxt.delta) {
- error = GIT_ITEROVER;
- break;
- }
-
- if (diff_delta_is_ambiguous(iter->ctxt.delta) &&
- (error = diff_delta_load(&iter->ctxt)) < 0)
- break;
-
- if (!diff_delta_should_skip(iter->ctxt.opts, iter->ctxt.delta))
- break;
-
- iter->file_index++;
- }
-
- if (!error) {
- iter->next_index = iter->file_index + 1;
-
- error = diff_delta_prep(&iter->ctxt);
- }
-
- if (iter->ctxt.delta == NULL) {
- iter->hunk_curr = iter->hunk_head = NULL;
- iter->line_curr = NULL;
- }
-
- if (delta_ptr != NULL)
- *delta_ptr = !error ? iter->ctxt.delta : NULL;
-
- return error;
+ assert(patch);
+ return patch->hunks_size;
}
-int git_diff_iterator_next_hunk(
- git_diff_range **range_ptr,
+int git_diff_patch_get_hunk(
+ git_diff_range **range,
const char **header,
size_t *header_len,
- git_diff_iterator *iter)
+ size_t *lines_in_hunk,
+ git_diff_patch *patch,
+ size_t hunk_idx)
{
- int error = diffiter_do_diff_file(iter);
- git_diff_range *range;
+ diff_patch_hunk *hunk;
- if (error)
- return error;
+ assert(patch);
- if (iter->hunk_curr == NULL) {
- if (iter->hunk_head == NULL)
- goto no_more_hunks;
- iter->hunk_curr = iter->hunk_head;
- } else {
- if (iter->hunk_curr->next == NULL)
- goto no_more_hunks;
- iter->hunk_curr = iter->hunk_curr->next;
+ if (hunk_idx >= patch->hunks_size) {
+ if (range) *range = NULL;
+ if (header) *header = NULL;
+ if (header_len) *header_len = 0;
+ if (lines_in_hunk) *lines_in_hunk = 0;
+ return GIT_ENOTFOUND;
}
- range = &iter->hunk_curr->range;
-
- if (range_ptr)
- *range_ptr = range;
-
- if (header) {
- int out = format_hunk_header(
- iter->hunk_header, sizeof(iter->hunk_header), range);
-
- /* TODO: append function name to header */
+ hunk = &patch->hunks[hunk_idx];
- *(iter->hunk_header + out++) = '\n';
+ if (range) *range = &hunk->range;
+ if (header) *header = hunk->header;
+ if (header_len) *header_len = hunk->header_len;
+ if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
- *header = iter->hunk_header;
-
- if (header_len)
- *header_len = (size_t)out;
- }
-
- iter->line_curr = iter->hunk_curr->line_head;
-
- return error;
+ return 0;
+}
-no_more_hunks:
- if (range_ptr) *range_ptr = NULL;
- if (header) *header = NULL;
- if (header_len) *header_len = 0;
- iter->line_curr = NULL;
+int git_diff_patch_num_lines_in_hunk(
+ git_diff_patch *patch,
+ size_t hunk_idx)
+{
+ assert(patch);
- return GIT_ITEROVER;
+ if (hunk_idx >= patch->hunks_size)
+ return GIT_ENOTFOUND;
+ else
+ return patch->hunks[hunk_idx].line_count;
}
-int git_diff_iterator_next_line(
- char *line_origin, /**< GIT_DIFF_LINE_... value from above */
- const char **content_ptr,
+int git_diff_patch_get_line_in_hunk(
+ char *line_origin,
+ const char **content,
size_t *content_len,
- git_diff_iterator *iter)
+ int *old_lineno,
+ int *new_lineno,
+ git_diff_patch *patch,
+ size_t hunk_idx,
+ size_t line_of_hunk)
{
- int error = diffiter_do_diff_file(iter);
+ diff_patch_hunk *hunk;
+ diff_patch_line *line;
- if (error)
- return error;
+ assert(patch);
- /* if the user has not called next_hunk yet, call it implicitly (OK?) */
- if (iter->hunk_curr == NULL) {
- error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter);
- if (error)
- return error;
- }
+ if (hunk_idx >= patch->hunks_size)
+ goto notfound;
+ hunk = &patch->hunks[hunk_idx];
- if (iter->line_curr == NULL) {
- if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
- if (content_ptr) *content_ptr = NULL;
- if (content_len) *content_len = 0;
- return GIT_ITEROVER;
- }
+ if (line_of_hunk >= hunk->line_count)
+ goto notfound;
- if (line_origin)
- *line_origin = iter->line_curr->origin;
- if (content_ptr)
- *content_ptr = iter->line_curr->ptr;
- if (content_len)
- *content_len = iter->line_curr->len;
+ line = &patch->lines[hunk->line_start + line_of_hunk];
- iter->line_curr = iter->line_curr->next;
+ if (line_origin) *line_origin = line->origin;
+ if (content) *content = line->ptr;
+ if (content_len) *content_len = line->len;
+ if (old_lineno) *old_lineno = line->oldno;
+ if (new_lineno) *new_lineno = line->newno;
- return error;
+ return 0;
+
+notfound:
+ if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT;
+ if (content) *content = NULL;
+ if (content_len) *content_len = 0;
+ if (old_lineno) *old_lineno = -1;
+ if (new_lineno) *new_lineno = -1;
+
+ return GIT_ENOTFOUND;
}
+
diff --git a/src/diff_output.h b/src/diff_output.h
new file mode 100644
index 000000000..f54639088
--- /dev/null
+++ b/src/diff_output.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * 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_output_h__
+#define INCLUDE_diff_output_h__
+
+#include "git2/blob.h"
+#include "diff.h"
+#include "map.h"
+#include "xdiff/xdiff.h"
+
+#define MAX_DIFF_FILESIZE 0x20000000
+
+enum {
+ GIT_DIFF_PATCH_ALLOCATED = (1 << 0),
+ GIT_DIFF_PATCH_PREPPED = (1 << 1),
+ GIT_DIFF_PATCH_LOADED = (1 << 2),
+ GIT_DIFF_PATCH_DIFFABLE = (1 << 3),
+ GIT_DIFF_PATCH_DIFFED = (1 << 4),
+};
+
+/* context for performing diffs */
+typedef struct {
+ git_repository *repo;
+ git_diff_list *diff;
+ git_diff_options *opts;
+ git_diff_file_fn file_cb;
+ git_diff_hunk_fn hunk_cb;
+ git_diff_data_fn data_cb;
+ void *cb_data;
+ int cb_error;
+ git_diff_range cb_range;
+ xdemitconf_t xdiff_config;
+ xpparam_t xdiff_params;
+} diff_context;
+
+/* cached information about a single span in a diff */
+typedef struct diff_patch_line diff_patch_line;
+struct diff_patch_line {
+ const char *ptr;
+ size_t len;
+ int lines, oldno, newno;
+ char origin;
+};
+
+/* cached information about a hunk in a diff */
+typedef struct diff_patch_hunk diff_patch_hunk;
+struct diff_patch_hunk {
+ git_diff_range range;
+ char header[128];
+ size_t header_len;
+ size_t line_start;
+ size_t line_count;
+};
+
+struct git_diff_patch {
+ git_refcount rc;
+ git_diff_list *diff; /* for refcount purposes, maybe NULL for blob diffs */
+ git_diff_delta *delta;
+ diff_context *ctxt; /* only valid while generating patch */
+ git_iterator_type_t old_src;
+ git_iterator_type_t new_src;
+ git_blob *old_blob;
+ git_blob *new_blob;
+ git_map old_data;
+ git_map new_data;
+ uint32_t flags;
+ diff_patch_hunk *hunks;
+ size_t hunks_asize, hunks_size;
+ diff_patch_line *lines;
+ size_t lines_asize, lines_size;
+};
+
+/* context for performing diff on a single delta */
+typedef struct {
+ git_diff_patch *patch;
+ uint32_t prepped : 1;
+ uint32_t loaded : 1;
+ uint32_t diffable : 1;
+ uint32_t diffed : 1;
+} diff_delta_context;
+
+#endif
diff --git a/src/submodule.c b/src/submodule.c
index 5ae38bccd..e55e12863 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -1457,7 +1457,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
if (!error) {
- if (git_diff_entrycount(diff, -1) > 0)
+ if (git_diff_num_deltas(diff) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
git_diff_list_free(diff);
@@ -1474,12 +1474,13 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm)
error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
if (!error) {
- int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED);
+ int untracked =
+ git_diff_num_deltas_of_type(diff, GIT_DELTA_UNTRACKED);
if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED;
- if (git_diff_entrycount(diff, -1) - untracked > 0)
+ if ((git_diff_num_deltas(diff) - untracked) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff);
diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c
index 767b34392..1c0435975 100644
--- a/tests-clar/diff/diff_helpers.c
+++ b/tests-clar/diff/diff_helpers.c
@@ -112,64 +112,65 @@ int diff_foreach_via_iterator(
git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb)
{
- int error;
- git_diff_iterator *iter;
- git_diff_delta *delta;
+ size_t d, num_d = git_diff_num_deltas(diff);
- if ((error = git_diff_iterator_new(&iter, diff)) < 0)
- return error;
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ git_diff_delta *delta;
+ size_t h, num_h;
- while (!(error = git_diff_iterator_next_file(&delta, iter))) {
- git_diff_range *range;
- const char *hdr;
- size_t hdr_len;
- float progress = git_diff_iterator_progress(iter);
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
+ cl_assert(delta && patch);
/* call file_cb for this file */
- if (file_cb != NULL && file_cb(data, delta, progress) != 0)
+ if (file_cb != NULL && file_cb(data, delta, (float)d / num_d) != 0) {
+ git_diff_patch_free(patch);
goto abort;
+ }
- if (!hunk_cb && !line_cb)
+ if (!hunk_cb && !line_cb) {
+ git_diff_patch_free(patch);
continue;
+ }
+
+ num_h = git_diff_patch_num_hunks(patch);
- while (!(error = git_diff_iterator_next_hunk(
- &range, &hdr, &hdr_len, iter))) {
- char origin;
- const char *line;
- size_t line_len;
+ for (h = 0; h < num_h; h++) {
+ git_diff_range *range;
+ const char *hdr;
+ size_t hdr_len, l, num_l;
- if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0)
+ cl_git_pass(git_diff_patch_get_hunk(
+ &range, &hdr, &hdr_len, &num_l, patch, h));
+
+ if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0) {
+ git_diff_patch_free(patch);
goto abort;
+ }
- if (!line_cb)
- continue;
+ for (l = 0; l < num_l; ++l) {
+ char origin;
+ const char *line;
+ size_t line_len;
+ int old_lineno, new_lineno;
- while (!(error = git_diff_iterator_next_line(
- &origin, &line, &line_len, iter))) {
+ cl_git_pass(git_diff_patch_get_line_in_hunk(
+ &origin, &line, &line_len, &old_lineno, &new_lineno,
+ patch, h, l));
- if (line_cb(data, delta, range, origin, line, line_len) != 0)
+ if (line_cb(data, delta, range, origin, line, line_len) != 0) {
+ git_diff_patch_free(patch);
goto abort;
+ }
}
-
- if (error && error != GIT_ITEROVER)
- goto done;
}
- if (error && error != GIT_ITEROVER)
- goto done;
+ git_diff_patch_free(patch);
}
-done:
- git_diff_iterator_free(iter);
-
- if (error == GIT_ITEROVER)
- error = 0;
-
- return error;
+ return 0;
abort:
- git_diff_iterator_free(iter);
giterr_clear();
-
return GIT_EUSER;
}
diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c
index 23071e48b..9e33d91e1 100644
--- a/tests-clar/diff/diffiter.c
+++ b/tests-clar/diff/diffiter.c
@@ -14,11 +14,16 @@ void test_diff_diffiter__create(void)
{
git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff;
- git_diff_iterator *iter;
+ size_t d, num_d;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
- git_diff_iterator_free(iter);
+
+ num_d = git_diff_num_deltas(diff);
+ for (d = 0; d < num_d; ++d) {
+ git_diff_delta *delta;
+ cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
+ }
+
git_diff_list_free(diff);
}
@@ -26,24 +31,22 @@ void test_diff_diffiter__iterate_files(void)
{
git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff;
- git_diff_iterator *iter;
- git_diff_delta *delta;
- int error, count = 0;
+ size_t d, num_d;
+ int count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
- while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
+ num_d = git_diff_num_deltas(diff);
+ cl_assert_equal_i(6, num_d);
+
+ for (d = 0; d < num_d; ++d) {
+ git_diff_delta *delta;
+ cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
cl_assert(delta != NULL);
count++;
}
-
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(delta == NULL);
cl_assert_equal_i(6, count);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
}
@@ -51,24 +54,22 @@ void test_diff_diffiter__iterate_files_2(void)
{
git_repository *repo = cl_git_sandbox_init("status");
git_diff_list *diff;
- git_diff_iterator *iter;
- git_diff_delta *delta;
- int error, count = 0;
+ size_t d, num_d;
+ int count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
- while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
+ num_d = git_diff_num_deltas(diff);
+ cl_assert_equal_i(8, num_d);
+
+ for (d = 0; d < num_d; ++d) {
+ git_diff_delta *delta;
+ cl_git_pass(git_diff_get_patch(NULL, &delta, diff, d));
cl_assert(delta != NULL);
count++;
}
-
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(delta == NULL);
cl_assert_equal_i(8, count);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
}
@@ -77,12 +78,8 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0};
git_diff_list *diff = NULL;
- git_diff_iterator *iter;
- git_diff_delta *delta;
- git_diff_range *range;
- const char *header;
- size_t header_len;
- int error, file_count = 0, hunk_count = 0;
+ size_t d, num_d;
+ int file_count = 0, hunk_count = 0;
opts.context_lines = 3;
opts.interhunk_lines = 1;
@@ -90,28 +87,42 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
+ num_d = git_diff_num_deltas(diff);
+
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ git_diff_delta *delta;
+ size_t h, num_h;
+
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
- while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
cl_assert(delta);
+ cl_assert(patch);
file_count++;
- while ((error = git_diff_iterator_next_hunk(
- &range, &header, &header_len, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
+ num_h = git_diff_patch_num_hunks(patch);
+
+ for (h = 0; h < num_h; h++) {
+ git_diff_range *range;
+ const char *header;
+ size_t header_len, num_l;
+
+ cl_git_pass(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, h));
+
cl_assert(range);
+ cl_assert(header);
+
hunk_count++;
}
+
+ git_diff_patch_free(patch);
}
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(delta == NULL);
cl_assert_equal_i(13, file_count);
cl_assert_equal_i(8, hunk_count);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
}
@@ -120,45 +131,42 @@ void test_diff_diffiter__max_size_threshold(void)
git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0};
git_diff_list *diff = NULL;
- git_diff_iterator *iter;
- git_diff_delta *delta;
- int error, file_count = 0, binary_count = 0, hunk_count = 0;
+ int file_count = 0, binary_count = 0, hunk_count = 0;
+ size_t d, num_d;
opts.context_lines = 3;
opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
+ num_d = git_diff_num_deltas(diff);
- while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ git_diff_delta *delta;
+
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
cl_assert(delta);
+ cl_assert(patch);
file_count++;
-
- hunk_count += git_diff_iterator_num_hunks_in_file(iter);
+ hunk_count += git_diff_patch_num_hunks(patch);
assert(delta->binary == 0 || delta->binary == 1);
-
binary_count += delta->binary;
- }
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(delta == NULL);
+ git_diff_patch_free(patch);
+ }
cl_assert_equal_i(13, file_count);
cl_assert_equal_i(0, binary_count);
cl_assert_equal_i(8, hunk_count);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
/* try again with low file size threshold */
- file_count = 0;
- binary_count = 0;
- hunk_count = 0;
+ file_count = binary_count = hunk_count = 0;
opts.context_lines = 3;
opts.interhunk_lines = 1;
@@ -166,36 +174,169 @@ void test_diff_diffiter__max_size_threshold(void)
opts.max_size = 50; /* treat anything over 50 bytes as binary! */
cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
+ num_d = git_diff_num_deltas(diff);
- while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) {
- cl_assert_equal_i(0, error);
- cl_assert(delta);
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ git_diff_delta *delta;
- file_count++;
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
- hunk_count += git_diff_iterator_num_hunks_in_file(iter);
+ file_count++;
+ hunk_count += git_diff_patch_num_hunks(patch);
assert(delta->binary == 0 || delta->binary == 1);
-
binary_count += delta->binary;
- }
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert(delta == NULL);
+ git_diff_patch_free(patch);
+ }
cl_assert_equal_i(13, file_count);
-
/* Three files are over the 50 byte threshold:
* - staged_changes_file_deleted
* - staged_changes_modified_file
* - staged_new_file_modified_file
*/
cl_assert_equal_i(3, binary_count);
-
cl_assert_equal_i(5, hunk_count);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
+}
+
+
+void test_diff_diffiter__iterate_all(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = {0};
+ git_diff_list *diff = NULL;
+ diff_expects exp = {0};
+ size_t d, num_d;
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+
+ num_d = git_diff_num_deltas(diff);
+ for (d = 0; d < num_d; ++d) {
+ git_diff_patch *patch;
+ git_diff_delta *delta;
+ size_t h, num_h;
+
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
+ cl_assert(patch && delta);
+ exp.files++;
+
+ num_h = git_diff_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ git_diff_range *range;
+ const char *header;
+ size_t header_len, l, num_l;
+
+ cl_git_pass(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, h));
+ cl_assert(range && header);
+ exp.hunks++;
+
+ for (l = 0; l < num_l; ++l) {
+ char origin;
+ const char *content;
+ size_t content_len;
+
+ cl_git_pass(git_diff_patch_get_line_in_hunk(
+ &origin, &content, &content_len, NULL, NULL, patch, h, l));
+ cl_assert(content);
+ exp.lines++;
+ }
+ }
+
+ git_diff_patch_free(patch);
+ }
+
+ cl_assert_equal_i(13, exp.files);
+ cl_assert_equal_i(8, exp.hunks);
+ cl_assert_equal_i(13, exp.lines);
+
+ git_diff_list_free(diff);
+}
+
+static void iterate_over_patch(git_diff_patch *patch, diff_expects *exp)
+{
+ size_t h, num_h = git_diff_patch_num_hunks(patch);
+
+ exp->files++;
+ exp->hunks += num_h;
+
+ /* let's iterate in reverse, just because we can! */
+ for (h = 1; h <= num_h; ++h)
+ exp->lines += git_diff_patch_num_lines_in_hunk(patch, num_h - h);
+}
+
+#define PATCH_CACHE 5
+
+void test_diff_diffiter__iterate_randomly_while_saving_state(void)
+{
+ git_repository *repo = cl_git_sandbox_init("status");
+ git_diff_options opts = {0};
+ git_diff_list *diff = NULL;
+ diff_expects exp = {0};
+ git_diff_patch *patches[PATCH_CACHE];
+ size_t p, d, num_d;
+
+ memset(patches, 0, sizeof(patches));
+
+ opts.context_lines = 3;
+ opts.interhunk_lines = 1;
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
+
+ cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff));
+
+ num_d = git_diff_num_deltas(diff);
+
+ /* To make sure that references counts work for diff and patch objects,
+ * this generates patches and randomly caches them. Only when the patch
+ * is removed from the cache are hunks and lines counted. At the end,
+ * there are still patches in the cache, so free the diff and try to
+ * process remaining patches after the diff is freed.
+ */
+
+ srand(121212);
+ p = rand() % PATCH_CACHE;
+
+ for (d = 0; d < num_d; ++d) {
+ /* take old patch */
+ git_diff_patch *patch = patches[p];
+ patches[p] = NULL;
+
+ /* cache new patch */
+ cl_git_pass(git_diff_get_patch(&patches[p], NULL, diff, d));
+ cl_assert(patches[p] != NULL);
+
+ /* process old patch if non-NULL */
+ if (patch != NULL) {
+ iterate_over_patch(patch, &exp);
+ git_diff_patch_free(patch);
+ }
+
+ p = rand() % PATCH_CACHE;
+ }
+
+ /* free diff list now - refcounts should keep things safe */
+ git_diff_list_free(diff);
+
+ /* process remaining unprocessed patches */
+ for (p = 0; p < PATCH_CACHE; p++) {
+ git_diff_patch *patch = patches[p];
+
+ if (patch != NULL) {
+ iterate_over_patch(patch, &exp);
+ git_diff_patch_free(patch);
+ }
+ }
+ /* hopefully it all still added up right */
+ cl_assert_equal_i(13, exp.files);
+ cl_assert_equal_i(8, exp.hunks);
+ cl_assert_equal_i(13, exp.lines);
}
diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c
index f5e72cadc..78ab093ed 100644
--- a/tests-clar/diff/tree.c
+++ b/tests-clar/diff/tree.c
@@ -264,10 +264,12 @@ void test_diff_tree__larger_hunks(void)
git_tree *a, *b;
git_diff_options opts = {0};
git_diff_list *diff = NULL;
- git_diff_iterator *iter = NULL;
+ size_t d, num_d, h, num_h, l, num_l, header_len, line_len;
git_diff_delta *delta;
- diff_expects exp;
- int error, num_files = 0;
+ git_diff_patch *patch;
+ git_diff_range *range;
+ const char *header, *line;
+ char origin;
g_repo = cl_git_sandbox_init("diff");
@@ -277,61 +279,38 @@ void test_diff_tree__larger_hunks(void)
opts.context_lines = 1;
opts.interhunk_lines = 0;
- memset(&exp, 0, sizeof(exp));
-
cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff));
- cl_git_pass(git_diff_iterator_new(&iter, diff));
- /* this should be exact */
- cl_assert(git_diff_iterator_progress(iter) == 0.0f);
+ num_d = git_diff_num_deltas(diff);
+ for (d = 0; d < num_d; ++d) {
+ cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
+ cl_assert(patch && delta);
- /* You wouldn't actually structure an iterator loop this way, but
- * I have here for testing purposes of the return value
- */
- while (!(error = git_diff_iterator_next_file(&delta, iter))) {
- git_diff_range *range;
- const char *header;
- size_t header_len;
- int actual_hunks = 0, num_hunks;
- float expected_progress;
-
- num_files++;
-
- expected_progress = (float)num_files / 2.0f;
- cl_assert(expected_progress == git_diff_iterator_progress(iter));
-
- num_hunks = git_diff_iterator_num_hunks_in_file(iter);
-
- while (!(error = git_diff_iterator_next_hunk(
- &range, &header, &header_len, iter)))
- {
- int actual_lines = 0;
- int num_lines = git_diff_iterator_num_lines_in_hunk(iter);
- char origin;
- const char *line;
- size_t line_len;
-
- while (!(error = git_diff_iterator_next_line(
- &origin, &line, &line_len, iter)))
- {
- actual_lines++;
- }
+ num_h = git_diff_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ cl_git_pass(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, h));
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(actual_lines, num_lines);
+ for (l = 0; l < num_l; ++l) {
+ cl_git_pass(git_diff_patch_get_line_in_hunk(
+ &origin, &line, &line_len, NULL, NULL, patch, h, l));
+ cl_assert(line);
+ }
- actual_hunks++;
+ cl_git_fail(git_diff_patch_get_line_in_hunk(
+ &origin, &line, &line_len, NULL, NULL, patch, h, num_l));
}
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(actual_hunks, num_hunks);
+ cl_git_fail(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, num_h));
+
+ git_diff_patch_free(patch);
}
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(2, num_files);
- cl_assert(git_diff_iterator_progress(iter) == 1.0f);
+ cl_git_fail(git_diff_get_patch(&patch, &delta, diff, num_d));
+
+ cl_assert_equal_i(2, num_d);
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
diff = NULL;
diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c
index 40a888544..612e44303 100644
--- a/tests-clar/diff/workdir.c
+++ b/tests-clar/diff/workdir.c
@@ -678,7 +678,7 @@ void test_diff_workdir__larger_hunks(void)
const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
git_tree *a, *b;
git_diff_options opts = {0};
- int i, error;
+ size_t i, d, num_d, h, num_h, l, num_l, header_len, line_len;
g_repo = cl_git_sandbox_init("diff");
@@ -690,9 +690,10 @@ void test_diff_workdir__larger_hunks(void)
for (i = 0; i <= 2; ++i) {
git_diff_list *diff = NULL;
- git_diff_iterator *iter = NULL;
- git_diff_delta *delta;
- int num_files = 0;
+ git_diff_patch *patch;
+ git_diff_range *range;
+ const char *header, *line;
+ char origin;
/* okay, this is a bit silly, but oh well */
switch (i) {
@@ -707,54 +708,36 @@ void test_diff_workdir__larger_hunks(void)
break;
}
- cl_git_pass(git_diff_iterator_new(&iter, diff));
+ num_d = git_diff_num_deltas(diff);
+ cl_assert_equal_i(2, (int)num_d);
- cl_assert(git_diff_iterator_progress(iter) == 0.0f);
+ for (d = 0; d < num_d; ++d) {
+ cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d));
+ cl_assert(patch);
- while (!(error = git_diff_iterator_next_file(&delta, iter))) {
- git_diff_range *range;
- const char *header;
- size_t header_len;
- int actual_hunks = 0, num_hunks;
- float expected_progress;
+ num_h = git_diff_patch_num_hunks(patch);
+ for (h = 0; h < num_h; h++) {
+ cl_git_pass(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, h));
- num_files++;
-
- expected_progress = (float)num_files / 2.0f;
- cl_assert(expected_progress == git_diff_iterator_progress(iter));
-
- num_hunks = git_diff_iterator_num_hunks_in_file(iter);
-
- while (!(error = git_diff_iterator_next_hunk(
- &range, &header, &header_len, iter)))
- {
- int actual_lines = 0;
- int num_lines = git_diff_iterator_num_lines_in_hunk(iter);
- char origin;
- const char *line;
- size_t line_len;
-
- while (!(error = git_diff_iterator_next_line(
- &origin, &line, &line_len, iter)))
- {
- actual_lines++;
+ for (l = 0; l < num_l; ++l) {
+ cl_git_pass(git_diff_patch_get_line_in_hunk(
+ &origin, &line, &line_len, NULL, NULL, patch, h, l));
+ cl_assert(line);
}
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(actual_lines, num_lines);
-
- actual_hunks++;
+ /* confirm fail after the last item */
+ cl_git_fail(git_diff_patch_get_line_in_hunk(
+ &origin, &line, &line_len, NULL, NULL, patch, h, num_l));
}
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(actual_hunks, num_hunks);
- }
+ /* confirm fail after the last item */
+ cl_git_fail(git_diff_patch_get_hunk(
+ &range, &header, &header_len, &num_l, patch, num_h));
- cl_assert_equal_i(GIT_ITEROVER, error);
- cl_assert_equal_i(2, num_files);
- cl_assert(git_diff_iterator_progress(iter) == 1.0f);
+ git_diff_patch_free(patch);
+ }
- git_diff_iterator_free(iter);
git_diff_list_free(diff);
}