diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2018-09-29 19:32:51 +0100 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-05 15:53:59 +0000 |
commit | 47cc5f85e9b95880e03cfa04d9708a643c587a13 (patch) | |
tree | b475bd7e31ed7e8b56545cfaceb86c706054b95a | |
parent | 398d8bfe606feddf8db738250fc1960887f53685 (diff) | |
download | libgit2-47cc5f85e9b95880e03cfa04d9708a643c587a13.tar.gz |
apply: introduce a hunk callback
Introduce a callback to patch application that allows consumers to
cancel hunk application.
-rw-r--r-- | include/git2/apply.h | 17 | ||||
-rw-r--r-- | src/apply.c | 48 | ||||
-rw-r--r-- | src/apply.h | 4 | ||||
-rw-r--r-- | tests/apply/callbacks.c | 38 | ||||
-rw-r--r-- | tests/apply/fromdiff.c | 2 | ||||
-rw-r--r-- | tests/apply/fromfile.c | 2 |
6 files changed, 101 insertions, 10 deletions
diff --git a/include/git2/apply.h b/include/git2/apply.h index eb9cad4d1..7cc1c22a8 100644 --- a/include/git2/apply.h +++ b/include/git2/apply.h @@ -38,6 +38,22 @@ typedef int (*git_apply_delta_cb)( void *payload); /** + * When applying a patch, callback that will be made per hunk. + * + * When the callback: + * - returns < 0, the apply process will be aborted. + * - returns > 0, the hunk will not be applied, but the apply process + * continues + * - returns 0, the hunk is applied, and the apply process continues. + * + * @param hunk The hunk to be applied + * @param payload User-specified payload + */ +typedef int (*git_apply_hunk_cb)( + const git_diff_hunk *hunk, + void *payload); + +/** * Apply options structure * * Initialize with `GIT_APPLY_OPTIONS_INIT`. Alternatively, you can @@ -49,6 +65,7 @@ typedef struct { unsigned int version; git_apply_delta_cb delta_cb; + git_apply_hunk_cb hunk_cb; void *payload; } git_apply_options; diff --git a/src/apply.c b/src/apply.c index 95342676c..8d94e78f1 100644 --- a/src/apply.c +++ b/src/apply.c @@ -167,15 +167,36 @@ static int update_hunk( return 0; } +typedef struct { + git_apply_options opts; + size_t skipped_new_lines; + size_t skipped_old_lines; +} apply_hunks_ctx; + static int apply_hunk( patch_image *image, git_patch *patch, - git_patch_hunk *hunk) + git_patch_hunk *hunk, + apply_hunks_ctx *ctx) { patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; size_t line_num, i; int error = 0; + if (ctx->opts.hunk_cb) { + error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload); + + if (error) { + if (error > 0) { + ctx->skipped_new_lines += hunk->hunk.new_lines; + ctx->skipped_old_lines += hunk->hunk.old_lines; + error = 0; + } + + goto done; + } + } + for (i = 0; i < hunk->line_count; i++) { size_t linenum = hunk->line_start + i; git_diff_line *line = git_array_get(patch->lines, linenum); @@ -198,7 +219,14 @@ static int apply_hunk( } } - line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0; + if (hunk->hunk.new_start) { + line_num = hunk->hunk.new_start - + ctx->skipped_new_lines + + ctx->skipped_old_lines - + 1; + } else { + line_num = 0; + } if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { error = apply_err("hunk at line %d did not apply", @@ -219,7 +247,8 @@ static int apply_hunks( git_buf *out, const char *source, size_t source_len, - git_patch *patch) + git_patch *patch, + apply_hunks_ctx *ctx) { git_patch_hunk *hunk; git_diff_line *line; @@ -231,7 +260,7 @@ static int apply_hunks( goto done; git_array_foreach(patch->hunks, i, hunk) { - if ((error = apply_hunk(&image, patch, hunk)) < 0) + if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0) goto done; } @@ -339,14 +368,19 @@ int git_apply__patch( unsigned int *mode_out, const char *source, size_t source_len, - git_patch *patch) + git_patch *patch, + const git_apply_options *given_opts) { + apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT }; char *filename = NULL; unsigned int mode = 0; int error = 0; assert(contents_out && filename_out && mode_out && (source || !source_len) && patch); + if (given_opts) + memcpy(&ctx.opts, given_opts, sizeof(git_apply_options)); + *filename_out = NULL; *mode_out = 0; @@ -361,7 +395,7 @@ int git_apply__patch( if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) error = apply_binary(contents_out, source, source_len, patch); else if (patch->hunks.size) - error = apply_hunks(contents_out, source, source_len, patch); + error = apply_hunks(contents_out, source, source_len, patch, &ctx); else error = git_buf_put(contents_out, source, source_len); @@ -457,7 +491,7 @@ static int apply_one( if (delta->status != GIT_DELTA_DELETED) { if ((error = git_apply__patch(&post_contents, &filename, &mode, - pre_contents.ptr, pre_contents.size, patch)) < 0 || + pre_contents.ptr, pre_contents.size, patch, opts)) < 0 || (error = git_blob_create_frombuffer(&post_id, repo, post_contents.ptr, post_contents.size)) < 0) goto done; diff --git a/src/apply.h b/src/apply.h index b29460c0b..11ec75637 100644 --- a/src/apply.h +++ b/src/apply.h @@ -10,6 +10,7 @@ #include "common.h" #include "git2/patch.h" +#include "git2/apply.h" #include "buffer.h" extern int git_apply__patch( @@ -18,6 +19,7 @@ extern int git_apply__patch( unsigned int *mode, const char *source, size_t source_len, - git_patch *patch); + git_patch *patch, + const git_apply_options *opts); #endif diff --git a/tests/apply/callbacks.c b/tests/apply/callbacks.c index 91bdfa3b8..1b759dc9b 100644 --- a/tests/apply/callbacks.c +++ b/tests/apply/callbacks.c @@ -88,3 +88,41 @@ void test_apply_callbacks__delta_can_skip(void) git_diff_free(diff); } + +static int hunk_skip_odds_cb(const git_diff_hunk *hunk, void *payload) +{ + int *count = (int *)payload; + GIT_UNUSED(hunk); + + return ((*count)++ % 2 == 1); +} + +void test_apply_callbacks__hunk_can_skip(void) +{ + git_diff *diff; + git_apply_options opts = GIT_APPLY_OPTIONS_INIT; + int count = 0; + + struct merge_index_entry workdir_expected[] = { + { 0100644, "f51658077d85f2264fa179b4d0848268cb3475c3", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "06f751b6ba4f017ddbf4248015768300268e092a", 0, "veal.txt" }, + }; + size_t workdir_expected_cnt = sizeof(workdir_expected) / + sizeof(struct merge_index_entry); + + opts.hunk_cb = hunk_skip_odds_cb; + opts.payload = &count; + + cl_git_pass(git_diff_from_buffer(&diff, + DIFF_MANY_CHANGES_ONE, strlen(DIFF_MANY_CHANGES_ONE))); + cl_git_pass(git_apply(repo, diff, GIT_APPLY_LOCATION_WORKDIR, &opts)); + + validate_index_unchanged(repo); + validate_apply_workdir(repo, workdir_expected, workdir_expected_cnt); + + git_diff_free(diff); +} diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c index 611060988..3b156025e 100644 --- a/tests/apply/fromdiff.c +++ b/tests/apply/fromdiff.c @@ -49,7 +49,7 @@ static int apply_gitbuf( cl_assert_equal_s(patch_expected, patchbuf.ptr); } - error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch); + error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch, NULL); if (error == 0 && new == NULL) { cl_assert_equal_i(0, result.size); diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c index 0ace639fc..6d4379b1b 100644 --- a/tests/apply/fromfile.c +++ b/tests/apply/fromfile.c @@ -39,7 +39,7 @@ static int apply_patchfile( cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); - error = git_apply__patch(&result, &filename, &mode, old, old_len, patch); + error = git_apply__patch(&result, &filename, &mode, old, old_len, patch, NULL); if (error == 0) { cl_assert_equal_i(new_len, result.size); |