mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-03 08:53:48 +00:00
diff: add a notify callback to git_diff__from_iterators
The callback will be called for each file, just before the `git_delta_t` gets inserted into the diff list. When the callback: - returns < 0, the diff process will be aborted - returns > 0, the delta will not be inserted into the diff list, but the diff process continues - returns 0, the delta is inserted into the diff list, and the diff process continues
This commit is contained in:
parent
943700ecbb
commit
0d64ba4837
@ -122,40 +122,6 @@ typedef enum {
|
|||||||
GIT_DIFF_IGNORE_FILEMODE = (1 << 17),
|
GIT_DIFF_IGNORE_FILEMODE = (1 << 17),
|
||||||
} git_diff_option_t;
|
} git_diff_option_t;
|
||||||
|
|
||||||
/**
|
|
||||||
* Structure describing options about how the diff should be executed.
|
|
||||||
*
|
|
||||||
* Setting all values of the structure to zero will yield the default
|
|
||||||
* values. Similarly, passing NULL for the options structure will
|
|
||||||
* give the defaults. The default values are marked below.
|
|
||||||
*
|
|
||||||
* - `flags` is a combination of the `git_diff_option_t` values above
|
|
||||||
* - `context_lines` is the number of unchanged lines that define the
|
|
||||||
* boundary of a hunk (and to display before and after)
|
|
||||||
* - `interhunk_lines` is the maximum number of unchanged lines between
|
|
||||||
* hunk boundaries before the hunks will be merged into a one.
|
|
||||||
* - `old_prefix` is the virtual "directory" to prefix to old file names
|
|
||||||
* in hunk headers (default "a")
|
|
||||||
* - `new_prefix` is the virtual "directory" to prefix to new file names
|
|
||||||
* in hunk headers (default "b")
|
|
||||||
* - `pathspec` is an array of paths / fnmatch patterns to constrain diff
|
|
||||||
* - `max_size` is a file size (in bytes) above which a blob will be marked
|
|
||||||
* as binary automatically; pass a negative value to disable.
|
|
||||||
*/
|
|
||||||
typedef struct {
|
|
||||||
unsigned int version; /**< version for the struct */
|
|
||||||
uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */
|
|
||||||
uint16_t context_lines; /**< defaults to 3 */
|
|
||||||
uint16_t interhunk_lines; /**< defaults to 0 */
|
|
||||||
const char *old_prefix; /**< defaults to "a" */
|
|
||||||
const char *new_prefix; /**< defaults to "b" */
|
|
||||||
git_strarray pathspec; /**< defaults to include all paths */
|
|
||||||
git_off_t max_size; /**< defaults to 512MB */
|
|
||||||
} git_diff_options;
|
|
||||||
|
|
||||||
#define GIT_DIFF_OPTIONS_VERSION 1
|
|
||||||
#define GIT_DIFF_OPTIONS_INIT {GIT_DIFF_OPTIONS_VERSION}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The diff list object that contains all individual file deltas.
|
* The diff list object that contains all individual file deltas.
|
||||||
*
|
*
|
||||||
@ -265,6 +231,64 @@ typedef struct {
|
|||||||
int binary;
|
int binary;
|
||||||
} git_diff_delta;
|
} git_diff_delta;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diff notification callback function.
|
||||||
|
*
|
||||||
|
* The callback will be called for each file, just before the `git_delta_t`
|
||||||
|
* gets inserted into the diff list.
|
||||||
|
*
|
||||||
|
* When the callback:
|
||||||
|
* - returns < 0, the diff process will be aborted.
|
||||||
|
* - returns > 0, the delta will not be inserted into the diff list, but the
|
||||||
|
* diff process continues.
|
||||||
|
* - returns 0, the delta is inserted into the diff list, and the diff process
|
||||||
|
* continues.
|
||||||
|
*/
|
||||||
|
typedef int (*git_diff_notify_cb)(
|
||||||
|
const git_diff_list *diff_so_far,
|
||||||
|
const git_diff_delta *delta_to_add,
|
||||||
|
const char *matched_pathspec,
|
||||||
|
void *payload);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure describing options about how the diff should be executed.
|
||||||
|
*
|
||||||
|
* Setting all values of the structure to zero will yield the default
|
||||||
|
* values. Similarly, passing NULL for the options structure will
|
||||||
|
* give the defaults. The default values are marked below.
|
||||||
|
*
|
||||||
|
* - `flags` is a combination of the `git_diff_option_t` values above
|
||||||
|
* - `context_lines` is the number of unchanged lines that define the
|
||||||
|
* boundary of a hunk (and to display before and after)
|
||||||
|
* - `interhunk_lines` is the maximum number of unchanged lines between
|
||||||
|
* hunk boundaries before the hunks will be merged into a one.
|
||||||
|
* - `old_prefix` is the virtual "directory" to prefix to old file names
|
||||||
|
* in hunk headers (default "a")
|
||||||
|
* - `new_prefix` is the virtual "directory" to prefix to new file names
|
||||||
|
* in hunk headers (default "b")
|
||||||
|
* - `pathspec` is an array of paths / fnmatch patterns to constrain diff
|
||||||
|
* - `max_size` is a file size (in bytes) above which a blob will be marked
|
||||||
|
* as binary automatically; pass a negative value to disable.
|
||||||
|
* - `notify_cb` is an optional callback function, notifying the consumer of
|
||||||
|
* which files are being examined as the diff is generated
|
||||||
|
* - `notify_payload` is the payload data to pass to the `notify_cb` function
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
unsigned int version; /**< version for the struct */
|
||||||
|
uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */
|
||||||
|
uint16_t context_lines; /**< defaults to 3 */
|
||||||
|
uint16_t interhunk_lines; /**< defaults to 0 */
|
||||||
|
const char *old_prefix; /**< defaults to "a" */
|
||||||
|
const char *new_prefix; /**< defaults to "b" */
|
||||||
|
git_strarray pathspec; /**< defaults to include all paths */
|
||||||
|
git_off_t max_size; /**< defaults to 512MB */
|
||||||
|
git_diff_notify_cb notify_cb;
|
||||||
|
void *notify_payload;
|
||||||
|
} git_diff_options;
|
||||||
|
|
||||||
|
#define GIT_DIFF_OPTIONS_VERSION 1
|
||||||
|
#define GIT_DIFF_OPTIONS_INIT {GIT_DIFF_OPTIONS_VERSION}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When iterating over a diff, callback that will be made per file.
|
* When iterating over a diff, callback that will be made per file.
|
||||||
*
|
*
|
||||||
|
44
src/diff.c
44
src/diff.c
@ -41,6 +41,18 @@ static git_diff_delta *diff_delta__alloc(
|
|||||||
return delta;
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int diff_notify(
|
||||||
|
const git_diff_list *diff,
|
||||||
|
const git_diff_delta *delta,
|
||||||
|
const char *matched_pathspec)
|
||||||
|
{
|
||||||
|
if (!diff->opts.notify_cb)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return diff->opts.notify_cb(
|
||||||
|
diff, delta, matched_pathspec, diff->opts.notify_payload);
|
||||||
|
}
|
||||||
|
|
||||||
static int diff_delta__from_one(
|
static int diff_delta__from_one(
|
||||||
git_diff_list *diff,
|
git_diff_list *diff,
|
||||||
git_delta_t status,
|
git_delta_t status,
|
||||||
@ -48,6 +60,7 @@ static int diff_delta__from_one(
|
|||||||
{
|
{
|
||||||
git_diff_delta *delta;
|
git_diff_delta *delta;
|
||||||
const char *matched_pathspec;
|
const char *matched_pathspec;
|
||||||
|
int notify_res;
|
||||||
|
|
||||||
if (status == GIT_DELTA_IGNORED &&
|
if (status == GIT_DELTA_IGNORED &&
|
||||||
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
|
(diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0)
|
||||||
@ -85,12 +98,16 @@ static int diff_delta__from_one(
|
|||||||
!git_oid_iszero(&delta->new_file.oid))
|
!git_oid_iszero(&delta->new_file.oid))
|
||||||
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
|
delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID;
|
||||||
|
|
||||||
if (git_vector_insert(&diff->deltas, delta) < 0) {
|
notify_res = diff_notify(diff, delta, matched_pathspec);
|
||||||
|
|
||||||
|
if (notify_res)
|
||||||
|
git__free(delta);
|
||||||
|
else if (git_vector_insert(&diff->deltas, delta) < 0) {
|
||||||
git__free(delta);
|
git__free(delta);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return notify_res < 0 ? GIT_EUSER : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int diff_delta__from_two(
|
static int diff_delta__from_two(
|
||||||
@ -100,9 +117,11 @@ static int diff_delta__from_two(
|
|||||||
uint32_t old_mode,
|
uint32_t old_mode,
|
||||||
const git_index_entry *new_entry,
|
const git_index_entry *new_entry,
|
||||||
uint32_t new_mode,
|
uint32_t new_mode,
|
||||||
git_oid *new_oid)
|
git_oid *new_oid,
|
||||||
|
const char *matched_pathspec)
|
||||||
{
|
{
|
||||||
git_diff_delta *delta;
|
git_diff_delta *delta;
|
||||||
|
int notify_res;
|
||||||
|
|
||||||
if (status == GIT_DELTA_UNMODIFIED &&
|
if (status == GIT_DELTA_UNMODIFIED &&
|
||||||
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
|
(diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0)
|
||||||
@ -139,12 +158,16 @@ static int diff_delta__from_two(
|
|||||||
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;
|
||||||
|
|
||||||
if (git_vector_insert(&diff->deltas, delta) < 0) {
|
notify_res = diff_notify(diff, delta, matched_pathspec);
|
||||||
|
|
||||||
|
if (notify_res)
|
||||||
|
git__free(delta);
|
||||||
|
else if (git_vector_insert(&diff->deltas, delta) < 0) {
|
||||||
git__free(delta);
|
git__free(delta);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return notify_res < 0 ? GIT_EUSER : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static git_diff_delta *diff_delta__last_for_item(
|
static git_diff_delta *diff_delta__last_for_item(
|
||||||
@ -528,7 +551,7 @@ static int maybe_modified(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return diff_delta__from_two(
|
return diff_delta__from_two(
|
||||||
diff, status, oitem, omode, nitem, nmode, use_noid);
|
diff, status, oitem, omode, nitem, nmode, use_noid, matched_pathspec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool entry_is_prefixed(
|
static bool entry_is_prefixed(
|
||||||
@ -749,10 +772,11 @@ int git_diff__from_iterators(
|
|||||||
else {
|
else {
|
||||||
assert(oitem && nitem && cmp == 0);
|
assert(oitem && nitem && cmp == 0);
|
||||||
|
|
||||||
if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 ||
|
if (maybe_modified(
|
||||||
git_iterator_advance(old_iter, &oitem) < 0 ||
|
old_iter, oitem, new_iter, nitem, diff) < 0 ||
|
||||||
git_iterator_advance(new_iter, &nitem) < 0)
|
git_iterator_advance(old_iter, &oitem) < 0 ||
|
||||||
goto fail;
|
git_iterator_advance(new_iter, &nitem) < 0)
|
||||||
|
goto fail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,11 @@ typedef struct {
|
|||||||
int line_dels;
|
int line_dels;
|
||||||
} diff_expects;
|
} diff_expects;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char *path;
|
||||||
|
const char *matched_pathspec;
|
||||||
|
} notify_expected;
|
||||||
|
|
||||||
extern int diff_file_cb(
|
extern int diff_file_cb(
|
||||||
const git_diff_delta *delta,
|
const git_diff_delta *delta,
|
||||||
float progress,
|
float progress,
|
||||||
|
@ -307,6 +307,169 @@ void test_diff_workdir__to_index_with_pathspec(void)
|
|||||||
git_diff_list_free(diff);
|
git_diff_list_free(diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int assert_called_notifications(
|
||||||
|
const git_diff_list *diff_so_far,
|
||||||
|
const git_diff_delta *delta_to_add,
|
||||||
|
const char *matched_pathspec,
|
||||||
|
void *payload)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
notify_expected *exp = (notify_expected*)payload;
|
||||||
|
notify_expected *e;;
|
||||||
|
|
||||||
|
GIT_UNUSED(diff_so_far);
|
||||||
|
|
||||||
|
for (e = exp; e->path != NULL; e++) {
|
||||||
|
if (strcmp(e->path, delta_to_add->new_file.path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
cl_assert_equal_s(e->matched_pathspec, matched_pathspec);
|
||||||
|
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cl_assert(found);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_diff_workdir__to_index_notify(void)
|
||||||
|
{
|
||||||
|
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||||
|
git_diff_list *diff = NULL;
|
||||||
|
diff_expects exp;
|
||||||
|
|
||||||
|
char *searched_pathspecs_solo[] = {
|
||||||
|
"*_deleted",
|
||||||
|
};
|
||||||
|
notify_expected expected_matched_pathspecs_solo[] = {
|
||||||
|
{ "file_deleted", "*_deleted" },
|
||||||
|
{ "staged_changes_file_deleted", "*_deleted" },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
char *searched_pathspecs_multiple[] = {
|
||||||
|
"staged_changes_cant_find_me",
|
||||||
|
"subdir/modified_cant_find_me",
|
||||||
|
"subdir/*",
|
||||||
|
"staged*"
|
||||||
|
};
|
||||||
|
notify_expected expected_matched_pathspecs_multiple[] = {
|
||||||
|
{ "staged_changes_file_deleted", "staged*" },
|
||||||
|
{ "staged_changes_modified_file", "staged*" },
|
||||||
|
{ "staged_delete_modified_file", "staged*" },
|
||||||
|
{ "staged_new_file_deleted_file", "staged*" },
|
||||||
|
{ "staged_new_file_modified_file", "staged*" },
|
||||||
|
{ "subdir/deleted_file", "subdir/*" },
|
||||||
|
{ "subdir/modified_file", "subdir/*" },
|
||||||
|
{ "subdir/new_file", "subdir/*" },
|
||||||
|
{ NULL, NULL }
|
||||||
|
};
|
||||||
|
|
||||||
|
g_repo = cl_git_sandbox_init("status");
|
||||||
|
|
||||||
|
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||||
|
opts.notify_cb = assert_called_notifications;
|
||||||
|
opts.pathspec.strings = searched_pathspecs_solo;
|
||||||
|
opts.pathspec.count = 1;
|
||||||
|
|
||||||
|
opts.notify_payload = &expected_matched_pathspecs_solo;
|
||||||
|
memset(&exp, 0, sizeof(exp));
|
||||||
|
|
||||||
|
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
|
||||||
|
|
||||||
|
cl_assert_equal_i(2, exp.files);
|
||||||
|
|
||||||
|
git_diff_list_free(diff);
|
||||||
|
|
||||||
|
opts.pathspec.strings = searched_pathspecs_multiple;
|
||||||
|
opts.pathspec.count = 4;
|
||||||
|
opts.notify_payload = &expected_matched_pathspecs_multiple;
|
||||||
|
memset(&exp, 0, sizeof(exp));
|
||||||
|
|
||||||
|
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
|
||||||
|
|
||||||
|
cl_assert_equal_i(8, exp.files);
|
||||||
|
|
||||||
|
git_diff_list_free(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int abort_diff(
|
||||||
|
const git_diff_list *diff_so_far,
|
||||||
|
const git_diff_delta *delta_to_add,
|
||||||
|
const char *matched_pathspec,
|
||||||
|
void *payload)
|
||||||
|
{
|
||||||
|
GIT_UNUSED(diff_so_far);
|
||||||
|
GIT_UNUSED(delta_to_add);
|
||||||
|
GIT_UNUSED(matched_pathspec);
|
||||||
|
GIT_UNUSED(payload);
|
||||||
|
|
||||||
|
return -42;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_diff_workdir__to_index_notify_can_be_aborted_by_callback(void)
|
||||||
|
{
|
||||||
|
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||||
|
git_diff_list *diff = NULL;
|
||||||
|
char *pathspec = NULL;
|
||||||
|
|
||||||
|
g_repo = cl_git_sandbox_init("status");
|
||||||
|
|
||||||
|
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||||
|
opts.notify_cb = abort_diff;
|
||||||
|
opts.pathspec.strings = &pathspec;
|
||||||
|
opts.pathspec.count = 1;
|
||||||
|
|
||||||
|
pathspec = "file_deleted";
|
||||||
|
cl_git_fail(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
|
||||||
|
pathspec = "staged_changes_modified_file";
|
||||||
|
cl_git_fail(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int filter_all(
|
||||||
|
const git_diff_list *diff_so_far,
|
||||||
|
const git_diff_delta *delta_to_add,
|
||||||
|
const char *matched_pathspec,
|
||||||
|
void *payload)
|
||||||
|
{
|
||||||
|
GIT_UNUSED(diff_so_far);
|
||||||
|
GIT_UNUSED(delta_to_add);
|
||||||
|
GIT_UNUSED(matched_pathspec);
|
||||||
|
GIT_UNUSED(payload);
|
||||||
|
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_diff_workdir__to_index_notify_can_be_used_as_filtering_function(void)
|
||||||
|
{
|
||||||
|
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
|
||||||
|
git_diff_list *diff = NULL;
|
||||||
|
char *pathspec = NULL;
|
||||||
|
diff_expects exp;
|
||||||
|
|
||||||
|
g_repo = cl_git_sandbox_init("status");
|
||||||
|
|
||||||
|
opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;
|
||||||
|
opts.notify_cb = filter_all;
|
||||||
|
opts.pathspec.strings = &pathspec;
|
||||||
|
opts.pathspec.count = 1;
|
||||||
|
|
||||||
|
pathspec = "*_deleted";
|
||||||
|
memset(&exp, 0, sizeof(exp));
|
||||||
|
|
||||||
|
cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
|
||||||
|
cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, &exp));
|
||||||
|
|
||||||
|
cl_assert_equal_i(0, exp.files);
|
||||||
|
|
||||||
|
git_diff_list_free(diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void test_diff_workdir__filemode_changes(void)
|
void test_diff_workdir__filemode_changes(void)
|
||||||
{
|
{
|
||||||
git_config *cfg;
|
git_config *cfg;
|
||||||
|
Loading…
Reference in New Issue
Block a user