diff options
| -rw-r--r-- | include/git2.h | 1 | ||||
| -rw-r--r-- | include/git2/apply.h | 41 | ||||
| -rw-r--r-- | src/apply.c | 100 | ||||
| -rw-r--r-- | tests/apply/tree.c | 58 |
4 files changed, 200 insertions, 0 deletions
diff --git a/include/git2.h b/include/git2.h index e182ce924..9239b48c8 100644 --- a/include/git2.h +++ b/include/git2.h @@ -9,6 +9,7 @@ #define INCLUDE_git_git_h__ #include "git2/annotated_commit.h" +#include "git2/apply.h" #include "git2/attr.h" #include "git2/blob.h" #include "git2/blame.h" diff --git a/include/git2/apply.h b/include/git2/apply.h new file mode 100644 index 000000000..a4ec4fc08 --- /dev/null +++ b/include/git2/apply.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_apply_h__ +#define INCLUDE_git_apply_h__ + +#include "common.h" +#include "types.h" +#include "oid.h" +#include "diff.h" + +/** + * @file git2/apply.h + * @brief Git patch application routines + * @defgroup git_apply Git patch application routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Apply a `git_diff` to a `git_tree`, and return the resulting image + * as an index. + * + * @param out the postimage of the application + * @param repo the repository to apply + * @param preimage the tree to apply the diff to + * @param diff the diff to apply + */ +GIT_EXTERN(int) git_apply_to_tree( + git_index **out, + git_repository *repo, + git_tree *preimage, + git_diff *diff); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/apply.c b/src/apply.c index 8c7bb6bf3..be8c7b66a 100644 --- a/src/apply.c +++ b/src/apply.c @@ -11,6 +11,8 @@ #include "git2/patch.h" #include "git2/filter.h" +#include "git2/blob.h" +#include "git2/index.h" #include "array.h" #include "patch.h" #include "fileops.h" @@ -376,3 +378,101 @@ done: return error; } + +static int apply_one( + git_repository *repo, + git_tree *preimage, + git_index *postimage, + git_diff *diff, + size_t i) +{ + git_patch *patch = NULL; + git_tree_entry *tree_entry = NULL; + git_blob *blob = NULL; + git_buf contents = GIT_BUF_INIT; + const git_diff_delta *delta; + char *filename = NULL; + unsigned int mode; + git_oid blob_id; + git_index_entry index_entry; + int error; + + if ((error = git_patch_from_diff(&patch, diff, i)) < 0) + goto done; + + delta = git_patch_get_delta(patch); + + if ((error = git_tree_entry_bypath(&tree_entry, preimage, + delta->old_file.path)) < 0 || + (error = git_blob_lookup(&blob, repo, + git_tree_entry_id(tree_entry))) < 0 || + (error = git_apply__patch(&contents, &filename, &mode, + git_blob_rawcontent(blob), git_blob_rawsize(blob), patch)) < 0 || + (error = git_blob_create_frombuffer(&blob_id, repo, + contents.ptr, contents.size)) < 0) + goto done; + + memset(&index_entry, 0, sizeof(git_index_entry)); + index_entry.path = filename; + index_entry.mode = mode; + git_oid_cpy(&index_entry.id, &blob_id); + + if ((error = git_index_add(postimage, &index_entry)) < 0) + goto done; + +done: + git_buf_free(&contents); + git__free(filename); + git_blob_free(blob); + git_tree_entry_free(tree_entry); + git_patch_free(patch); + + return error; +} + +int git_apply_to_tree( + git_index **out, + git_repository *repo, + git_tree *preimage, + git_diff *diff) +{ + git_index *postimage = NULL; + const git_diff_delta *delta; + size_t i; + int error = 0; + + assert(out && repo && preimage && diff); + + *out = NULL; + + if ((error = git_index_new(&postimage)) < 0 || + (error = git_index_read_tree(postimage, preimage)) < 0) + goto done; + + /* + * Remove the old paths from the index before applying diffs - + * we need to do a full pass to remove them before adding deltas, + * in order to handle rename situations. + */ + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if ((error = git_index_remove(postimage, + delta->old_file.path, 0)) < 0) + goto done; + } + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + if ((error = apply_one(repo, preimage, postimage, diff, i)) < 0) + goto done; + } + + *out = postimage; + +done: + if (error < 0) + git_index_free(postimage); + + return error; +} + diff --git a/tests/apply/tree.c b/tests/apply/tree.c new file mode 100644 index 000000000..38c64c0f8 --- /dev/null +++ b/tests/apply/tree.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "../merge/merge_helpers.h" + +static git_repository *repo; + +#define TEST_REPO_PATH "merge-recursive" + + +void test_apply_tree__initialize(void) +{ + repo = cl_git_sandbox_init(TEST_REPO_PATH); +} + +void test_apply_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_apply_tree__one(void) +{ + git_oid a_oid, b_oid; + git_commit *a_commit, *b_commit; + git_tree *a_tree, *b_tree; + git_diff *diff; + git_index *index = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + + struct merge_index_entry expected[] = { + { 0100644, "ffb36e513f5fdf8a6ba850a20142676a2ac4807d", 0, "asparagus.txt" }, + { 0100644, "68f6182f4c85d39e1309d97c7e456156dc9c0096", 0, "beef.txt" }, + { 0100644, "4b7c5650008b2e747fe1809eeb5a1dde0e80850a", 0, "bouilli.txt" }, + { 0100644, "c4e6cca3ec6ae0148ed231f97257df8c311e015f", 0, "gravy.txt" }, + { 0100644, "68af1fc7407fd9addf1701a87eb1c95c7494c598", 0, "oyster.txt" }, + { 0100644, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" }, + }; + + git_oid_fromstr(&a_oid, "539bd011c4822c560c1d17cab095006b7a10f707"); + git_oid_fromstr(&b_oid, "7c7bf85e978f1d18c0566f702d2cb7766b9c8d4f"); + + cl_git_pass(git_commit_lookup(&a_commit, repo, &a_oid)); + cl_git_pass(git_commit_lookup(&b_commit, repo, &b_oid)); + + cl_git_pass(git_commit_tree(&a_tree, a_commit)); + cl_git_pass(git_commit_tree(&b_tree, b_commit)); + + cl_git_pass(git_diff_tree_to_tree(&diff, repo, a_tree, b_tree, &opts)); + + cl_git_pass(git_apply_to_tree(&index, repo, a_tree, diff)); + merge_test_index(index, expected, 6); + + git_index_free(index); + git_diff_free(diff); + git_tree_free(a_tree); + git_tree_free(b_tree); + git_commit_free(a_commit); + git_commit_free(b_commit); +} + |
