Commit 5f69a31f by Russell Belfer

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.
parent 9a12a625
...@@ -98,6 +98,9 @@ enum { ...@@ -98,6 +98,9 @@ enum {
GIT_DIFF_FILE_FREE_PATH = (1 << 1), GIT_DIFF_FILE_FREE_PATH = (1 << 1),
GIT_DIFF_FILE_BINARY = (1 << 2), GIT_DIFF_FILE_BINARY = (1 << 2),
GIT_DIFF_FILE_NOT_BINARY = (1 << 3), 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( ...@@ -425,7 +428,7 @@ GIT_EXTERN(int) git_diff_print_patch(
* @param diff A git_diff_list generated by one of the above functions * @param diff A git_diff_list generated by one of the above functions
* @return Count of number of deltas in the list * @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. * 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); ...@@ -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 * @param type A git_delta_t value to filter the count
* @return Count of number of deltas matching delta_t type * @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_diff_list *diff,
git_delta_t type); git_delta_t type);
...@@ -469,7 +472,7 @@ GIT_EXTERN(size_t) git_diff_entrycount_of_type( ...@@ -469,7 +472,7 @@ GIT_EXTERN(size_t) git_diff_entrycount_of_type(
*/ */
GIT_EXTERN(int) git_diff_get_patch( GIT_EXTERN(int) git_diff_get_patch(
git_diff_patch **patch, git_diff_patch **patch,
const git_diff_delta **delta, git_diff_delta **delta,
git_diff_list *diff, git_diff_list *diff,
size_t idx); size_t idx);
...@@ -482,43 +485,76 @@ GIT_EXTERN(void) git_diff_patch_free( ...@@ -482,43 +485,76 @@ GIT_EXTERN(void) git_diff_patch_free(
/** /**
* Get the delta associated with a patch * Get the delta associated with a patch
*/ */
GIT_EXTERN(void) git_diff_patch_get_delta( GIT_EXTERN(const git_diff_delta *) git_diff_patch_delta(
const git_diff_delta **delta,
git_diff_patch *patch); git_diff_patch *patch);
/** /**
* Get the number of hunks in a 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); git_diff_patch *patch);
/** /**
* Get the information about a hunk in a 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( GIT_EXTERN(int) git_diff_patch_get_hunk(
const git_diff_range **range, git_diff_range **range,
const char **header, const char **header,
size_t *header_len, size_t *header_len,
size_t *lines_in_hunk, size_t *lines_in_hunk,
git_diff_patch *patch, 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, 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( GIT_EXTERN(int) git_diff_patch_get_line_in_hunk(
char *line_origin, char *line_origin,
const char **content, const char **content,
size_t *content_len, size_t *content_len,
int *old_lineno,
int *new_lineno,
git_diff_patch *patch, git_diff_patch *patch,
size_t hunk, size_t hunk_idx,
size_t line_of_hunk); size_t line_of_hunk);
/**@}*/ /**@}*/
......
...@@ -5,8 +5,6 @@ ...@@ -5,8 +5,6 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "common.h" #include "common.h"
#include "git2/diff.h"
#include "git2/oid.h"
#include "diff.h" #include "diff.h"
#include "fileops.h" #include "fileops.h"
#include "config.h" #include "config.h"
...@@ -268,9 +266,17 @@ static int diff_delta__from_two( ...@@ -268,9 +266,17 @@ static int diff_delta__from_two(
delta->old_file.mode = old_mode; delta->old_file.mode = old_mode;
delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; 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.size = new_entry->file_size;
delta->new_file.mode = new_mode; 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)) if (new_oid || !git_oid_iszero(&new_entry->oid))
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
...@@ -425,6 +431,11 @@ void git_diff_list_free(git_diff_list *diff) ...@@ -425,6 +431,11 @@ void git_diff_list_free(git_diff_list *diff)
GIT_REFCOUNT_DEC(diff, diff_list_free); 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( static int oid_for_workdir_item(
git_repository *repo, git_repository *repo,
const git_index_entry *item, const git_index_entry *item,
...@@ -519,17 +530,17 @@ static int maybe_modified( ...@@ -519,17 +530,17 @@ static int maybe_modified(
omode == nmode) omode == nmode)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* if modes match and we have an unknown OID and a workdir iterator, /* if we have an unknown OID and a workdir iterator, then check some
* then check deeper for matching * circumstances that can accelerate things or need special handling
*/ */
else if (omode == nmode && else if (git_oid_iszero(&nitem->oid) &&
git_oid_iszero(&nitem->oid) && new_iter->type == GIT_ITERATOR_WORKDIR)
new_iter->type == GIT_ITERATOR_WORKDIR)
{ {
/* TODO: add check against index file st_mtime to avoid racy-git */ /* 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 the stat data looks exactly alike, then assume the same */
if (oitem->file_size == nitem->file_size && if (omode == nmode &&
oitem->file_size == nitem->file_size &&
(!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) || (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) ||
(oitem->ctime.seconds == nitem->ctime.seconds)) && (oitem->ctime.seconds == nitem->ctime.seconds)) &&
oitem->mtime.seconds == nitem->mtime.seconds && oitem->mtime.seconds == nitem->mtime.seconds &&
...@@ -554,16 +565,15 @@ static int maybe_modified( ...@@ -554,16 +565,15 @@ static int maybe_modified(
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
} }
} }
}
/* TODO: check git attributes so we will not have to read the file /* if we got here and decided that the files are modified, but we
* in if it is marked binary. * haven't calculated the OID of the new item, then calculate it now
*/ */
if (status == GIT_DELTA_MODIFIED && git_oid_iszero(&nitem->oid)) {
else if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0)
return -1; return -1;
else if (omode == nmode && git_oid_equal(&oitem->oid, &noid))
else if (git_oid_cmp(&oitem->oid, &noid) == 0 &&
omode == nmode)
status = GIT_DELTA_UNMODIFIED; status = GIT_DELTA_UNMODIFIED;
/* store calculated oid so we don't have to recalc later */ /* store calculated oid so we don't have to recalc later */
......
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
#ifndef INCLUDE_diff_h__ #ifndef INCLUDE_diff_h__
#define INCLUDE_diff_h__ #define INCLUDE_diff_h__
#include "git2/diff.h"
#include "git2/oid.h"
#include <stdio.h> #include <stdio.h>
#include "vector.h" #include "vector.h"
#include "buffer.h" #include "buffer.h"
...@@ -25,14 +28,17 @@ enum { ...@@ -25,14 +28,17 @@ enum {
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ 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 { struct git_diff_list {
git_refcount rc; git_refcount rc;
git_repository *repo; git_repository *repo;
git_diff_options opts; git_diff_options opts;
git_vector pathspec; 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_pool pool;
git_iterator_type_t old_src; git_iterator_type_t old_src;
git_iterator_type_t new_src; git_iterator_type_t new_src;
...@@ -42,27 +48,7 @@ struct git_diff_list { ...@@ -42,27 +48,7 @@ struct git_diff_list {
extern void git_diff__cleanup_modes( extern void git_diff__cleanup_modes(
uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); uint32_t diffcaps, uint32_t *omode, uint32_t *nmode);
/** extern void git_diff_list_addref(git_diff_list *diff);
* 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);
#endif #endif
...@@ -5,58 +5,13 @@ ...@@ -5,58 +5,13 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "common.h" #include "common.h"
#include "git2/diff.h"
#include "git2/attr.h" #include "git2/attr.h"
#include "git2/blob.h"
#include "git2/oid.h" #include "git2/oid.h"
#include "xdiff/xdiff.h" #include "diff_output.h"
#include <ctype.h> #include <ctype.h>
#include "diff.h"
#include "map.h"
#include "fileops.h" #include "fileops.h"
#include "filter.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) static int read_next_int(const char **str, int *value)
{ {
const char *scan = *str; const char *scan = *str;
...@@ -96,55 +51,28 @@ static int parse_hunk_header(git_diff_range *range, const char *header) ...@@ -96,55 +51,28 @@ static int parse_hunk_header(git_diff_range *range, const char *header)
return 0; 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) { uint32_t flags = ctxt->opts ? ctxt->opts->flags : 0;
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);
}
static bool diff_delta_should_skip(git_diff_options *opts, git_diff_delta *delta)
{
if (delta->status == GIT_DELTA_UNMODIFIED && if (delta->status == GIT_DELTA_UNMODIFIED &&
(opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
return true; return true;
if (delta->status == GIT_DELTA_IGNORED && if (delta->status == GIT_DELTA_IGNORED &&
(opts->flags & GIT_DIFF_INCLUDE_IGNORED) == 0) (flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
return true; return true;
if (delta->status == GIT_DELTA_UNTRACKED && if (delta->status == GIT_DELTA_UNTRACKED &&
(opts->flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0)
return true; return true;
return false; 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( static int update_file_is_binary_by_attr(
git_repository *repo, git_diff_file *file) git_repository *repo, git_diff_file *file)
...@@ -173,8 +101,6 @@ static void update_delta_is_binary(git_diff_delta *delta) ...@@ -173,8 +101,6 @@ static void update_delta_is_binary(git_diff_delta *delta)
(delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0)
delta->binary = 1; 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 && else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 &&
(delta->new_file.flags & NOT_BINARY_FLAGS) != 0) (delta->new_file.flags & NOT_BINARY_FLAGS) != 0)
delta->binary = 0; delta->binary = 0;
...@@ -182,10 +108,11 @@ static void update_delta_is_binary(git_diff_delta *delta) ...@@ -182,10 +108,11 @@ static void update_delta_is_binary(git_diff_delta *delta)
/* otherwise leave delta->binary value untouched */ /* 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; int error = 0, mirror_new;
git_diff_delta *delta = ctxt->delta; git_diff_delta *delta = patch->delta;
delta->binary = -1; delta->binary = -1;
...@@ -200,7 +127,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) ...@@ -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 */ /* 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->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY;
delta->binary = 0; delta->binary = 0;
...@@ -214,7 +141,7 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) ...@@ -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 || mirror_new = (delta->new_file.path == delta->old_file.path ||
strcmp(delta->new_file.path, delta->old_file.path) == 0); strcmp(delta->new_file.path, delta->old_file.path) == 0);
if (mirror_new) 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 else
error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file); 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) ...@@ -224,11 +151,13 @@ static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt)
} }
static int diff_delta_is_binary_by_content( 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; 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.ptr = map->data;
search.size = min(map->len, 4000); search.size = min(map->len, 4000);
...@@ -238,17 +167,17 @@ static int diff_delta_is_binary_by_content( ...@@ -238,17 +167,17 @@ static int diff_delta_is_binary_by_content(
file->flags |= GIT_DIFF_FILE_NOT_BINARY; file->flags |= GIT_DIFF_FILE_NOT_BINARY;
} }
update_delta_is_binary(ctxt->delta); update_delta_is_binary(delta);
return 0; return 0;
} }
static int diff_delta_is_binary_by_size( 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; git_off_t threshold = MAX_DIFF_FILESIZE;
if ((file->flags & BINARY_DIFF_FLAGS) != 0) if ((file->flags & KNOWN_BINARY_FLAGS) != 0)
return 0; return 0;
if (ctxt && ctxt->opts) { if (ctxt && ctxt->opts) {
...@@ -262,7 +191,7 @@ static int diff_delta_is_binary_by_size( ...@@ -262,7 +191,7 @@ static int diff_delta_is_binary_by_size(
if (file->size > threshold) if (file->size > threshold)
file->flags |= GIT_DIFF_FILE_BINARY; file->flags |= GIT_DIFF_FILE_BINARY;
update_delta_is_binary(ctxt->delta); update_delta_is_binary(delta);
return 0; return 0;
} }
...@@ -289,8 +218,10 @@ static void setup_xdiff_options( ...@@ -289,8 +218,10 @@ static void setup_xdiff_options(
param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL; param->flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
} }
static int get_blob_content( static int get_blob_content(
diff_delta_context *ctxt, diff_context *ctxt,
git_diff_delta *delta,
git_diff_file *file, git_diff_file *file,
git_map *map, git_map *map,
git_blob **blob) git_blob **blob)
...@@ -318,9 +249,9 @@ static int get_blob_content( ...@@ -318,9 +249,9 @@ static int get_blob_content(
} }
/* if blob is too large to diff, mark as binary */ /* 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; return error;
if (ctxt->delta->binary == 1) if (delta->binary == 1)
return 0; return 0;
if (odb_obj != NULL) { if (odb_obj != NULL) {
...@@ -336,11 +267,12 @@ static int get_blob_content( ...@@ -336,11 +267,12 @@ static int get_blob_content(
map->data = (void *)git_blob_rawcontent(*blob); map->data = (void *)git_blob_rawcontent(*blob);
map->len = git_blob_rawsize(*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( static int get_workdir_content(
diff_delta_context *ctxt, diff_context *ctxt,
git_diff_delta *delta,
git_diff_file *file, git_diff_file *file,
git_map *map) git_map *map)
{ {
...@@ -386,8 +318,8 @@ static int get_workdir_content( ...@@ -386,8 +318,8 @@ static int get_workdir_content(
if (!file->size) if (!file->size)
file->size = git_futils_filesize(fd); file->size = git_futils_filesize(fd);
if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0 || if ((error = diff_delta_is_binary_by_size(ctxt, delta, file)) < 0 ||
ctxt->delta->binary == 1) delta->binary == 1)
goto close_and_cleanup; goto close_and_cleanup;
error = git_filters_load( error = git_filters_load(
...@@ -428,7 +360,7 @@ close_and_cleanup: ...@@ -428,7 +360,7 @@ close_and_cleanup:
} }
if (!error) if (!error)
error = diff_delta_is_binary_by_content(ctxt, file, map); error = diff_delta_is_binary_by_content(ctxt, delta, file, map);
cleanup: cleanup:
git_buf_free(&path); git_buf_free(&path);
...@@ -454,78 +386,101 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob) ...@@ -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, static void diff_context_init(
git_repository *repo, diff_context *ctxt,
git_diff_list *diff,
git_repository *repo,
git_diff_options *opts, git_diff_options *opts,
git_iterator_type_t old_src, void *data,
git_iterator_type_t new_src) 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->repo = repo;
ctxt->diff = diff;
ctxt->opts = opts; ctxt->opts = opts;
ctxt->old_src = old_src; ctxt->file_cb = file_cb;
ctxt->new_src = new_src; 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( static bool diff_delta_file_callback(
diff_delta_context *ctxt, diff_context *ctxt, git_diff_delta *delta, size_t idx)
git_diff_list *diff)
{ {
diff_delta_init_context( float progress;
ctxt, diff->repo, &diff->opts, diff->old_src, diff->new_src);
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) { patch->diff = ctxt->diff;
release_content(&ctxt->delta->old_file, &ctxt->old_data, ctxt->old_blob); patch->ctxt = ctxt;
release_content(&ctxt->delta->new_file, &ctxt->new_data, ctxt->new_blob);
ctxt->loaded = 0;
}
ctxt->delta = NULL; if (patch->diff) {
ctxt->prepped = 0; 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) diff_patch_init(ctxt, patch);
return 0;
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; int error = 0;
git_diff_delta *delta = ctxt->delta; git_diff_delta *delta = patch->delta;
bool check_if_unmodified = false; bool check_if_unmodified = false;
if (ctxt->loaded || !ctxt->delta) if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0)
return 0; return 0;
if (!ctxt->prepped && (error = diff_delta_prep(ctxt)) < 0) error = diff_delta_is_binary_by_attr(ctxt, patch);
goto cleanup;
ctxt->old_data.data = ""; patch->old_data.data = "";
ctxt->old_data.len = 0; patch->old_data.len = 0;
ctxt->old_blob = NULL; patch->old_blob = NULL;
ctxt->new_data.data = ""; patch->new_data.data = "";
ctxt->new_data.len = 0; patch->new_data.len = 0;
ctxt->new_blob = NULL; patch->new_blob = NULL;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
...@@ -557,36 +512,38 @@ static int diff_delta_load(diff_delta_context *ctxt) ...@@ -557,36 +512,38 @@ static int diff_delta_load(diff_delta_context *ctxt)
*/ */
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && 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( if ((error = get_workdir_content(
ctxt, &delta->old_file, &ctxt->old_data)) < 0) ctxt, delta, &delta->old_file, &patch->old_data)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && 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( if ((error = get_workdir_content(
ctxt, &delta->new_file, &ctxt->new_data)) < 0) ctxt, delta, &delta->new_file, &patch->new_data)) < 0)
goto cleanup; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && 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( 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; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
} }
if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && 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( 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; goto cleanup;
if (delta->binary == 1) if (delta->binary == 1)
goto cleanup; goto cleanup;
...@@ -606,33 +563,34 @@ static int diff_delta_load(diff_delta_context *ctxt) ...@@ -606,33 +563,34 @@ static int diff_delta_load(diff_delta_context *ctxt)
} }
cleanup: cleanup:
/* last change to update binary flag */
if (delta->binary == -1) if (delta->binary == -1)
update_delta_is_binary(delta); 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 (delta->binary != 1 &&
if (ctxt->loaded) delta->status != GIT_DELTA_UNMODIFIED &&
ctxt->diffable = (patch->old_data.len || patch->new_data.len) &&
(delta->binary != 1 && !git_oid_equal(&delta->old_file.oid, &delta->new_file.oid))
delta->status != GIT_DELTA_UNMODIFIED && patch->flags |= GIT_DIFF_PATCH_DIFFABLE;
(ctxt->old_data.len || ctxt->new_data.len) && }
git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid));
return error; 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 (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; return ctxt->cb_error;
if (ctxt->per_hunk != NULL && if (ctxt->hunk_cb != NULL &&
ctxt->per_hunk(ctxt->cb_data, ctxt->delta, &ctxt->range, ctxt->hunk_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[0].ptr, bufs[0].size)) bufs[0].ptr, bufs[0].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
...@@ -644,9 +602,9 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -644,9 +602,9 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
GIT_DIFF_LINE_CONTEXT; GIT_DIFF_LINE_CONTEXT;
if (ctxt->per_line != NULL && if (ctxt->data_cb != NULL &&
ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[1].ptr, bufs[1].size)) origin, bufs[1].ptr, bufs[1].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
...@@ -661,105 +619,268 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) ...@@ -661,105 +619,268 @@ static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len)
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
GIT_DIFF_LINE_CONTEXT; GIT_DIFF_LINE_CONTEXT;
if (ctxt->per_line != NULL && if (ctxt->data_cb != NULL &&
ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, ctxt->data_cb(ctxt->cb_data, patch->delta, &ctxt->cb_range,
bufs[2].ptr, bufs[2].size)) origin, bufs[2].ptr, bufs[2].size))
ctxt->cb_error = GIT_EUSER; ctxt->cb_error = GIT_EUSER;
} }
return ctxt->cb_error; return ctxt->cb_error;
} }
static int diff_delta_exec( static int diff_patch_generate(
diff_delta_context *ctxt, diff_context *ctxt, git_diff_patch *patch)
void *cb_data,
git_diff_hunk_fn per_hunk,
git_diff_data_fn per_line)
{ {
int error = 0; int error = 0;
xdemitcb_t xdiff_callback; xdemitcb_t xdiff_callback;
mmfile_t old_xdiff_data, new_xdiff_data; mmfile_t old_xdiff_data, new_xdiff_data;
if (ctxt->diffed || !ctxt->delta) if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0)
return 0; return 0;
if (!ctxt->loaded && (error = diff_delta_load(ctxt)) < 0) if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0)
goto cleanup; if ((error = diff_patch_load(ctxt, patch)) < 0)
return error;
if (!ctxt->diffable) if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0)
return 0; return 0;
ctxt->cb_data = cb_data; if (ctxt)
ctxt->per_hunk = per_hunk; patch->ctxt = ctxt;
ctxt->per_line = per_line;
ctxt->cb_error = 0;
memset(&xdiff_callback, 0, sizeof(xdiff_callback)); memset(&xdiff_callback, 0, sizeof(xdiff_callback));
xdiff_callback.outf = diff_delta_cb; xdiff_callback.outf = diff_patch_cb;
xdiff_callback.priv = ctxt; xdiff_callback.priv = patch;
old_xdiff_data.ptr = ctxt->old_data.data; old_xdiff_data.ptr = patch->old_data.data;
old_xdiff_data.size = ctxt->old_data.len; old_xdiff_data.size = patch->old_data.len;
new_xdiff_data.ptr = ctxt->new_data.data; new_xdiff_data.ptr = patch->new_data.data;
new_xdiff_data.size = ctxt->new_data.len; new_xdiff_data.size = patch->new_data.len;
xdl_diff(&old_xdiff_data, &new_xdiff_data, xdl_diff(&old_xdiff_data, &new_xdiff_data,
&ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback); &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback);
error = ctxt->cb_error; error = ctxt->cb_error;
cleanup: if (!error)
ctxt->diffed = !error; patch->flags |= GIT_DIFF_PATCH_DIFFED;
return error; 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( int git_diff_foreach(
git_diff_list *diff, git_diff_list *diff,
void *data, void *cb_data,
git_diff_file_fn file_cb, git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn data_cb)
{ {
int error = 0; int error;
diff_delta_context ctxt; diff_context ctxt;
size_t idx; 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) { diff_patch_init(&ctxt, &patch);
if (diff_delta_is_ambiguous(ctxt.delta))
if ((error = diff_delta_load(&ctxt)) < 0)
goto cleanup;
if (diff_delta_should_skip(ctxt.opts, ctxt.delta)) git_vector_foreach(&diff->deltas, idx, patch.delta) {
continue;
if ((error = diff_delta_load(&ctxt)) < 0) /* check flags against patch status */
goto cleanup; if (diff_delta_should_skip(&ctxt, patch.delta))
continue;
if (file_cb != NULL &&
file_cb(data, ctxt.delta, (float)idx / diff->deltas.length) != 0)
{
error = GIT_EUSER;
goto cleanup;
}
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_patch_unload(&patch);
diff_delta_unload(&ctxt);
if (error < 0) if (error < 0)
break; break;
} }
if (error == GIT_EUSER) if (error == GIT_EUSER)
giterr_clear(); giterr_clear(); /* don't let error message leak */
return error; return error;
} }
typedef struct { typedef struct {
git_diff_list *diff; git_diff_list *diff;
git_diff_data_fn print_cb; git_diff_data_fn print_cb;
...@@ -851,7 +972,6 @@ int git_diff_print_compact( ...@@ -851,7 +972,6 @@ int git_diff_print_compact(
return error; return error;
} }
static int print_oid_range(diff_print_info *pi, git_diff_delta *delta) static int print_oid_range(diff_print_info *pi, git_diff_delta *delta)
{ {
char start_oid[8], end_oid[8]; char start_oid[8], end_oid[8];
...@@ -1024,30 +1144,6 @@ int git_diff_print_patch( ...@@ -1024,30 +1144,6 @@ int git_diff_print_patch(
return error; 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( static void set_data_from_blob(
git_blob *blob, git_map *map, git_diff_file *file) git_blob *blob, git_map *map, git_diff_file *file)
...@@ -1056,6 +1152,7 @@ static void set_data_from_blob( ...@@ -1056,6 +1152,7 @@ static void set_data_from_blob(
map->data = (char *)git_blob_rawcontent(blob); map->data = (char *)git_blob_rawcontent(blob);
file->size = map->len = git_blob_rawsize(blob); file->size = map->len = git_blob_rawsize(blob);
git_oid_cpy(&file->oid, git_object_id((const git_object *)blob)); git_oid_cpy(&file->oid, git_object_id((const git_object *)blob));
file->mode = 0644;
} else { } else {
map->data = ""; map->data = "";
file->size = map->len = 0; file->size = map->len = 0;
...@@ -1070,429 +1167,250 @@ int git_diff_blobs( ...@@ -1070,429 +1167,250 @@ int git_diff_blobs(
void *cb_data, void *cb_data,
git_diff_file_fn file_cb, git_diff_file_fn file_cb,
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn data_cb)
{ {
int error; int error;
diff_delta_context ctxt;
git_diff_delta delta;
git_blob *new, *old;
git_repository *repo; git_repository *repo;
diff_context ctxt;
new = new_blob; git_diff_delta delta;
old = old_blob; git_diff_patch patch;
if (options && (options->flags & GIT_DIFF_REVERSE)) { if (options && (options->flags & GIT_DIFF_REVERSE)) {
git_blob *swap = old; git_blob *swap = old_blob;
old = new; old_blob = new_blob;
new = swap; new_blob = swap;
} }
if (new) if (new_blob)
repo = git_object_owner((git_object *)new); repo = git_object_owner((git_object *)new_blob);
else if (old) else if (old_blob)
repo = git_object_owner((git_object *)old); repo = git_object_owner((git_object *)old_blob);
else else
repo = NULL; repo = NULL;
diff_delta_init_context( diff_context_init(
&ctxt, repo, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); &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)); memset(&delta, 0, sizeof(delta));
delta.binary = -1;
set_data_from_blob(old, &ctxt.old_data, &delta.old_file); set_data_from_blob(old_blob, &patch.old_data, &delta.old_file);
set_data_from_blob(new, &ctxt.new_data, &delta.new_file); set_data_from_blob(new_blob, &patch.new_data, &delta.new_file);
delta.status = new ? delta.status = new_blob ?
(old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (old_blob ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) :
(old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); (old_blob ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED);
if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0)
delta.status = GIT_DELTA_UNMODIFIED; 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; goto cleanup;
if (delta.binary == -1) { patch.flags |= GIT_DIFF_PATCH_LOADED;
if ((error = diff_delta_is_binary_by_content( if (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED)
&ctxt, &delta.old_file, &ctxt.old_data)) < 0 || patch.flags |= GIT_DIFF_PATCH_DIFFABLE;
(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);
/* do diffs */ /* do diffs */
if (file_cb != NULL && file_cb(cb_data, &delta, 1)) { if (!(error = diff_delta_file_callback(&ctxt, patch.delta, 1)))
error = GIT_EUSER; error = diff_patch_generate(&ctxt, &patch);
goto cleanup;
}
error = diff_delta_exec(&ctxt, cb_data, hunk_cb, line_cb);
cleanup: cleanup:
diff_patch_unload(&patch);
if (error == GIT_EUSER) if (error == GIT_EUSER)
giterr_clear(); giterr_clear();
diff_delta_unload(&ctxt);
return error; 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( size_t git_diff_num_deltas(git_diff_list *diff)
void *cb_data,
git_diff_delta *delta,
git_diff_range *range,
const char *header,
size_t header_len)
{ {
diffiter_cb_info *info = cb_data; assert(diff);
git_diff_iterator *iter = info->iter; return (size_t)diff->deltas.length;
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;
} }
static int diffiter_line_cb( size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type)
void *cb_data,
git_diff_delta *delta,
git_diff_range *range,
char line_origin,
const char *content,
size_t content_len)
{ {
diffiter_cb_info *info = cb_data; size_t i, count = 0;
git_diff_iterator *iter = info->iter; git_diff_delta *delta;
diffiter_line *line;
GIT_UNUSED(delta); assert(diff);
GIT_UNUSED(range);
if ((line = git_pool_mallocz(&iter->lines, 1)) == NULL) { git_vector_foreach(&diff->deltas, i, delta) {
iter->ctxt.cb_error = -1; count += (delta->status == type);
return -1;
} }
if (info->last_line) return count;
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;
} }
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; int error;
diffiter_cb_info info; diff_context ctxt;
git_diff_delta *delta;
if (iter->ctxt.diffed || !iter->ctxt.delta) git_diff_patch *patch;
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;
}
static void diffiter_do_unload_file(git_diff_iterator *iter) if (patch_ptr)
{ *patch_ptr = NULL;
if (iter->ctxt.loaded) {
diff_delta_unload(&iter->ctxt);
git_pool_clear(&iter->lines); delta = git_vector_get(&diff->deltas, idx);
git_pool_clear(&iter->hunks); 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; if (delta_ptr)
iter->hunk_curr = iter->hunk_head = NULL; *delta_ptr = delta;
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));
iter->diff = diff; if (!patch_ptr && delta->binary != -1)
GIT_REFCOUNT_INC(iter->diff); 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 || if (diff_delta_should_skip(&ctxt, delta))
git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0) return 0;
goto fail;
*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: error = diff_patch_generate(&ctxt, patch);
git_diff_iterator_free(iter);
return -1; if (error == GIT_EUSER)
} error = ctxt.cb_error;
}
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);
}
float git_diff_iterator_progress(git_diff_iterator *iter) if (error)
{ git_diff_patch_free(patch);
return (float)iter->next_index / (float)iter->diff->deltas.length; else if (patch_ptr)
} *patch_ptr = patch;
int git_diff_iterator__max_files(git_diff_iterator *iter) return error;
{
return (int)iter->diff->deltas.length;
} }
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); if (patch)
return (error != 0) ? error : (int)iter->hunk_count; 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); assert(patch);
if (error) return patch->delta;
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;
} }
int git_diff_iterator_next_file( size_t git_diff_patch_num_hunks(git_diff_patch *patch)
git_diff_delta **delta_ptr,
git_diff_iterator *iter)
{ {
int error = 0; assert(patch);
return patch->hunks_size;
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;
} }
int git_diff_iterator_next_hunk( int git_diff_patch_get_hunk(
git_diff_range **range_ptr, git_diff_range **range,
const char **header, const char **header,
size_t *header_len, 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); diff_patch_hunk *hunk;
git_diff_range *range;
if (error) assert(patch);
return error;
if (iter->hunk_curr == NULL) { if (hunk_idx >= patch->hunks_size) {
if (iter->hunk_head == NULL) if (range) *range = NULL;
goto no_more_hunks; if (header) *header = NULL;
iter->hunk_curr = iter->hunk_head; if (header_len) *header_len = 0;
} else { if (lines_in_hunk) *lines_in_hunk = 0;
if (iter->hunk_curr->next == NULL) return GIT_ENOTFOUND;
goto no_more_hunks;
iter->hunk_curr = iter->hunk_curr->next;
} }
range = &iter->hunk_curr->range; hunk = &patch->hunks[hunk_idx];
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 */
*(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; return 0;
}
if (header_len)
*header_len = (size_t)out;
}
iter->line_curr = iter->hunk_curr->line_head;
return error;
no_more_hunks: int git_diff_patch_num_lines_in_hunk(
if (range_ptr) *range_ptr = NULL; git_diff_patch *patch,
if (header) *header = NULL; size_t hunk_idx)
if (header_len) *header_len = 0; {
iter->line_curr = NULL; 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( int git_diff_patch_get_line_in_hunk(
char *line_origin, /**< GIT_DIFF_LINE_... value from above */ char *line_origin,
const char **content_ptr, const char **content,
size_t *content_len, 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) assert(patch);
return error;
/* if the user has not called next_hunk yet, call it implicitly (OK?) */ if (hunk_idx >= patch->hunks_size)
if (iter->hunk_curr == NULL) { goto notfound;
error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter); hunk = &patch->hunks[hunk_idx];
if (error)
return error;
}
if (iter->line_curr == NULL) { if (line_of_hunk >= hunk->line_count)
if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; goto notfound;
if (content_ptr) *content_ptr = NULL;
if (content_len) *content_len = 0;
return GIT_ITEROVER;
}
if (line_origin) line = &patch->lines[hunk->line_start + line_of_hunk];
*line_origin = iter->line_curr->origin;
if (content_ptr)
*content_ptr = iter->line_curr->ptr;
if (content_len)
*content_len = iter->line_curr->len;
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;
} }
/*
* 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
...@@ -1457,7 +1457,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) ...@@ -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); error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff);
if (!error) { if (!error) {
if (git_diff_entrycount(diff, -1) > 0) if (git_diff_num_deltas(diff) > 0)
*status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED;
git_diff_list_free(diff); git_diff_list_free(diff);
...@@ -1474,12 +1474,13 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) ...@@ -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); error = git_diff_workdir_to_index(sm_repo, &opt, &diff);
if (!error) { 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) if (untracked > 0)
*status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; *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; *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED;
git_diff_list_free(diff); git_diff_list_free(diff);
......
...@@ -112,64 +112,65 @@ int diff_foreach_via_iterator( ...@@ -112,64 +112,65 @@ int diff_foreach_via_iterator(
git_diff_hunk_fn hunk_cb, git_diff_hunk_fn hunk_cb,
git_diff_data_fn line_cb) git_diff_data_fn line_cb)
{ {
int error; size_t d, num_d = git_diff_num_deltas(diff);
git_diff_iterator *iter;
git_diff_delta *delta;
if ((error = git_diff_iterator_new(&iter, diff)) < 0) for (d = 0; d < num_d; ++d) {
return error; git_diff_patch *patch;
git_diff_delta *delta;
size_t h, num_h;
while (!(error = git_diff_iterator_next_file(&delta, iter))) { cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
git_diff_range *range; cl_assert(delta && patch);
const char *hdr;
size_t hdr_len;
float progress = git_diff_iterator_progress(iter);
/* call file_cb for this file */ /* 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; goto abort;
}
if (!hunk_cb && !line_cb) if (!hunk_cb && !line_cb) {
git_diff_patch_free(patch);
continue; continue;
}
num_h = git_diff_patch_num_hunks(patch);
while (!(error = git_diff_iterator_next_hunk( for (h = 0; h < num_h; h++) {
&range, &hdr, &hdr_len, iter))) { git_diff_range *range;
char origin; const char *hdr;
const char *line; size_t hdr_len, l, num_l;
size_t line_len;
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; goto abort;
}
if (!line_cb) for (l = 0; l < num_l; ++l) {
continue; char origin;
const char *line;
size_t line_len;
int old_lineno, new_lineno;
while (!(error = git_diff_iterator_next_line( cl_git_pass(git_diff_patch_get_line_in_hunk(
&origin, &line, &line_len, iter))) { &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; goto abort;
}
} }
if (error && error != GIT_ITEROVER)
goto done;
} }
if (error && error != GIT_ITEROVER) git_diff_patch_free(patch);
goto done;
} }
done: return 0;
git_diff_iterator_free(iter);
if (error == GIT_ITEROVER)
error = 0;
return error;
abort: abort:
git_diff_iterator_free(iter);
giterr_clear(); giterr_clear();
return GIT_EUSER; return GIT_EUSER;
} }
...@@ -14,11 +14,16 @@ void test_diff_diffiter__create(void) ...@@ -14,11 +14,16 @@ void test_diff_diffiter__create(void)
{ {
git_repository *repo = cl_git_sandbox_init("attr"); git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff; 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_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); git_diff_list_free(diff);
} }
...@@ -26,24 +31,22 @@ void test_diff_diffiter__iterate_files(void) ...@@ -26,24 +31,22 @@ void test_diff_diffiter__iterate_files(void)
{ {
git_repository *repo = cl_git_sandbox_init("attr"); git_repository *repo = cl_git_sandbox_init("attr");
git_diff_list *diff; git_diff_list *diff;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int count = 0;
int error, count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); 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) { num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(0, error); 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); cl_assert(delta != NULL);
count++; count++;
} }
cl_assert_equal_i(GIT_ITEROVER, error);
cl_assert(delta == NULL);
cl_assert_equal_i(6, count); cl_assert_equal_i(6, count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -51,24 +54,22 @@ void test_diff_diffiter__iterate_files_2(void) ...@@ -51,24 +54,22 @@ void test_diff_diffiter__iterate_files_2(void)
{ {
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_list *diff; git_diff_list *diff;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int count = 0;
int error, count = 0;
cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); 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) { num_d = git_diff_num_deltas(diff);
cl_assert_equal_i(0, error); 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); cl_assert(delta != NULL);
count++; count++;
} }
cl_assert_equal_i(GIT_ITEROVER, error);
cl_assert(delta == NULL);
cl_assert_equal_i(8, count); cl_assert_equal_i(8, count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -77,12 +78,8 @@ void test_diff_diffiter__iterate_files_and_hunks(void) ...@@ -77,12 +78,8 @@ void test_diff_diffiter__iterate_files_and_hunks(void)
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter; size_t d, num_d;
git_diff_delta *delta; int file_count = 0, hunk_count = 0;
git_diff_range *range;
const char *header;
size_t header_len;
int error, file_count = 0, hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
...@@ -90,28 +87,42 @@ void test_diff_diffiter__iterate_files_and_hunks(void) ...@@ -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_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(delta);
cl_assert(patch);
file_count++; file_count++;
while ((error = git_diff_iterator_next_hunk( num_h = git_diff_patch_num_hunks(patch);
&range, &header, &header_len, iter)) != GIT_ITEROVER) {
cl_assert_equal_i(0, error); 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(range);
cl_assert(header);
hunk_count++; 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(13, file_count);
cl_assert_equal_i(8, hunk_count); cl_assert_equal_i(8, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
...@@ -120,45 +131,42 @@ void test_diff_diffiter__max_size_threshold(void) ...@@ -120,45 +131,42 @@ void test_diff_diffiter__max_size_threshold(void)
git_repository *repo = cl_git_sandbox_init("status"); git_repository *repo = cl_git_sandbox_init("status");
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter; int file_count = 0, binary_count = 0, hunk_count = 0;
git_diff_delta *delta; size_t d, num_d;
int error, file_count = 0, binary_count = 0, hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; 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_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) { for (d = 0; d < num_d; ++d) {
cl_assert_equal_i(0, error); git_diff_patch *patch;
git_diff_delta *delta;
cl_git_pass(git_diff_get_patch(&patch, &delta, diff, d));
cl_assert(delta); cl_assert(delta);
cl_assert(patch);
file_count++; file_count++;
hunk_count += git_diff_patch_num_hunks(patch);
hunk_count += git_diff_iterator_num_hunks_in_file(iter);
assert(delta->binary == 0 || delta->binary == 1); assert(delta->binary == 0 || delta->binary == 1);
binary_count += delta->binary; binary_count += delta->binary;
}
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert(delta == NULL); }
cl_assert_equal_i(13, file_count); cl_assert_equal_i(13, file_count);
cl_assert_equal_i(0, binary_count); cl_assert_equal_i(0, binary_count);
cl_assert_equal_i(8, hunk_count); cl_assert_equal_i(8, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
/* try again with low file size threshold */ /* try again with low file size threshold */
file_count = 0; file_count = binary_count = hunk_count = 0;
binary_count = 0;
hunk_count = 0;
opts.context_lines = 3; opts.context_lines = 3;
opts.interhunk_lines = 1; opts.interhunk_lines = 1;
...@@ -166,36 +174,169 @@ void test_diff_diffiter__max_size_threshold(void) ...@@ -166,36 +174,169 @@ void test_diff_diffiter__max_size_threshold(void)
opts.max_size = 50; /* treat anything over 50 bytes as binary! */ 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_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) { for (d = 0; d < num_d; ++d) {
cl_assert_equal_i(0, error); git_diff_patch *patch;
cl_assert(delta); 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); assert(delta->binary == 0 || delta->binary == 1);
binary_count += delta->binary; binary_count += delta->binary;
}
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert(delta == NULL); }
cl_assert_equal_i(13, file_count); cl_assert_equal_i(13, file_count);
/* Three files are over the 50 byte threshold: /* Three files are over the 50 byte threshold:
* - staged_changes_file_deleted * - staged_changes_file_deleted
* - staged_changes_modified_file * - staged_changes_modified_file
* - staged_new_file_modified_file * - staged_new_file_modified_file
*/ */
cl_assert_equal_i(3, binary_count); cl_assert_equal_i(3, binary_count);
cl_assert_equal_i(5, hunk_count); cl_assert_equal_i(5, hunk_count);
git_diff_iterator_free(iter);
git_diff_list_free(diff); 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);
} }
...@@ -264,10 +264,12 @@ void test_diff_tree__larger_hunks(void) ...@@ -264,10 +264,12 @@ void test_diff_tree__larger_hunks(void)
git_tree *a, *b; git_tree *a, *b;
git_diff_options opts = {0}; git_diff_options opts = {0};
git_diff_list *diff = NULL; 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; git_diff_delta *delta;
diff_expects exp; git_diff_patch *patch;
int error, num_files = 0; git_diff_range *range;
const char *header, *line;
char origin;
g_repo = cl_git_sandbox_init("diff"); g_repo = cl_git_sandbox_init("diff");
...@@ -277,61 +279,38 @@ void test_diff_tree__larger_hunks(void) ...@@ -277,61 +279,38 @@ void test_diff_tree__larger_hunks(void)
opts.context_lines = 1; opts.context_lines = 1;
opts.interhunk_lines = 0; 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_tree_to_tree(g_repo, &opts, a, b, &diff));
cl_git_pass(git_diff_iterator_new(&iter, diff));
/* this should be exact */ num_d = git_diff_num_deltas(diff);
cl_assert(git_diff_iterator_progress(iter) == 0.0f); 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 num_h = git_diff_patch_num_hunks(patch);
* I have here for testing purposes of the return value for (h = 0; h < num_h; h++) {
*/ cl_git_pass(git_diff_patch_get_hunk(
while (!(error = git_diff_iterator_next_file(&delta, iter))) { &range, &header, &header_len, &num_l, patch, h));
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++;
}
cl_assert_equal_i(GIT_ITEROVER, error); for (l = 0; l < num_l; ++l) {
cl_assert_equal_i(actual_lines, num_lines); 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_git_fail(git_diff_patch_get_hunk(
cl_assert_equal_i(actual_hunks, num_hunks); &range, &header, &header_len, &num_l, patch, num_h));
git_diff_patch_free(patch);
} }
cl_assert_equal_i(GIT_ITEROVER, error); cl_git_fail(git_diff_get_patch(&patch, &delta, diff, num_d));
cl_assert_equal_i(2, num_files);
cl_assert(git_diff_iterator_progress(iter) == 1.0f); cl_assert_equal_i(2, num_d);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
diff = NULL; diff = NULL;
......
...@@ -678,7 +678,7 @@ void test_diff_workdir__larger_hunks(void) ...@@ -678,7 +678,7 @@ void test_diff_workdir__larger_hunks(void)
const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10";
git_tree *a, *b; git_tree *a, *b;
git_diff_options opts = {0}; 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"); g_repo = cl_git_sandbox_init("diff");
...@@ -690,9 +690,10 @@ void test_diff_workdir__larger_hunks(void) ...@@ -690,9 +690,10 @@ void test_diff_workdir__larger_hunks(void)
for (i = 0; i <= 2; ++i) { for (i = 0; i <= 2; ++i) {
git_diff_list *diff = NULL; git_diff_list *diff = NULL;
git_diff_iterator *iter = NULL; git_diff_patch *patch;
git_diff_delta *delta; git_diff_range *range;
int num_files = 0; const char *header, *line;
char origin;
/* okay, this is a bit silly, but oh well */ /* okay, this is a bit silly, but oh well */
switch (i) { switch (i) {
...@@ -707,54 +708,36 @@ void test_diff_workdir__larger_hunks(void) ...@@ -707,54 +708,36 @@ void test_diff_workdir__larger_hunks(void)
break; 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))) { num_h = git_diff_patch_num_hunks(patch);
git_diff_range *range; for (h = 0; h < num_h; h++) {
const char *header; cl_git_pass(git_diff_patch_get_hunk(
size_t header_len; &range, &header, &header_len, &num_l, patch, h));
int actual_hunks = 0, num_hunks;
float expected_progress;
num_files++; for (l = 0; l < num_l; ++l) {
cl_git_pass(git_diff_patch_get_line_in_hunk(
expected_progress = (float)num_files / 2.0f; &origin, &line, &line_len, NULL, NULL, patch, h, l));
cl_assert(expected_progress == git_diff_iterator_progress(iter)); cl_assert(line);
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++;
} }
cl_assert_equal_i(GIT_ITEROVER, error); /* confirm fail after the last item */
cl_assert_equal_i(actual_lines, num_lines); cl_git_fail(git_diff_patch_get_line_in_hunk(
&origin, &line, &line_len, NULL, NULL, patch, h, num_l));
actual_hunks++;
} }
cl_assert_equal_i(GIT_ITEROVER, error); /* confirm fail after the last item */
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));
cl_assert_equal_i(GIT_ITEROVER, error); git_diff_patch_free(patch);
cl_assert_equal_i(2, num_files); }
cl_assert(git_diff_iterator_progress(iter) == 1.0f);
git_diff_iterator_free(iter);
git_diff_list_free(diff); git_diff_list_free(diff);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment