apply: introduce `git_apply` Introduce `git_apply`, which will take a `git_diff` and apply it to the working directory (akin to `git apply`), the index (akin to `git apply --cached`), or both (akin to `git apply --index`).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
diff --git a/include/git2/apply.h b/include/git2/apply.h
index a4ec4fc..3bf6aad 100644
--- a/include/git2/apply.h
+++ b/include/git2/apply.h
@@ -36,6 +36,48 @@ GIT_EXTERN(int) git_apply_to_tree(
git_tree *preimage,
git_diff *diff);
+typedef enum {
+ /**
+ * Apply the patch to the workdir, leaving the index untouched.
+ * This is the equivalent of `git apply` with no location argument.
+ */
+ GIT_APPLY_LOCATION_WORKDIR = 0,
+
+ /**
+ * Apply the patch to the index, leaving the working directory
+ * untouched. This is the equivalent of `git apply --cached`.
+ */
+ GIT_APPLY_LOCATION_INDEX = 1,
+
+ /**
+ * Apply the patch to both the working directory and the index.
+ * This is the equivalent of `git apply --index`.
+ */
+ GIT_APPLY_LOCATION_BOTH = 2,
+} git_apply_location_t;
+
+typedef struct {
+ unsigned int version;
+
+ git_apply_location_t location;
+} git_apply_options;
+
+#define GIT_APPLY_OPTIONS_VERSION 1
+#define GIT_APPLY_OPTIONS_INIT {GIT_APPLY_OPTIONS_VERSION}
+
+/**
+ * Apply a `git_diff` to the given repository, making changes directly
+ * in the working directory, the index, or both.
+ *
+ * @param repo the repository to apply to
+ * @param diff the diff to apply
+ * @param options the options for the apply (or null for defaults)
+ */
+GIT_EXTERN(int) git_apply(
+ git_repository *repo,
+ git_diff *diff,
+ git_apply_options *options);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/apply.c b/src/apply.c
index 134a98f..e761b27 100644
--- a/src/apply.c
+++ b/src/apply.c
@@ -9,10 +9,13 @@
#include <assert.h>
+#include "git2/apply.h"
#include "git2/patch.h"
#include "git2/filter.h"
#include "git2/blob.h"
#include "git2/index.h"
+#include "git2/checkout.h"
+#include "git2/repository.h"
#include "array.h"
#include "patch.h"
#include "fileops.h"
@@ -481,3 +484,110 @@ done:
return error;
}
+
+/* normal: apply to workdir: ignore index
+ * --cached: apply to index: ignore workdir
+ * --index: apply to both: validate index == workdir
+ */
+
+int git_apply(
+ git_repository *repo,
+ git_diff *diff,
+ git_apply_options *given_opts)
+{
+ git_index *contents = NULL, *repo_index = NULL;
+ git_reader *pre_reader = NULL;
+ const git_diff_delta *delta;
+ git_vector paths = GIT_VECTOR_INIT;
+ git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
+ git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ bool do_checkout;
+ size_t i;
+ int error;
+
+ assert(repo && diff);
+
+ GITERR_CHECK_VERSION(
+ given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options");
+
+ if (given_opts)
+ memcpy(&opts, given_opts, sizeof(git_apply_options));
+
+ do_checkout = (opts.location != GIT_APPLY_LOCATION_INDEX);
+
+ /*
+ * by default, we apply a patch directly to the working directory;
+ * in `--cached` or `--index` mode, we apply to the contents already
+ * in the index.
+ */
+ if (opts.location == GIT_APPLY_LOCATION_WORKDIR)
+ error = git_reader_for_workdir(&pre_reader, repo);
+ else
+ error = git_reader_for_index(&pre_reader, repo, NULL);
+
+ if (error < 0)
+ goto done;
+
+ /* if we're not checking out, we're writing to the repo's index */
+ if (do_checkout)
+ error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL);
+ else
+ error = git_repository_index(&repo_index, repo);
+
+ if (error < 0)
+ goto done;
+
+ /*
+ * note: this is not the full postimage, this only contains the
+ * new files created during the diffing process. we will limit
+ * checkout to only write the files affected by this diff.
+ */
+ if ((error = git_index_new(&contents)) < 0)
+ goto done;
+
+ for (i = 0; i < git_diff_num_deltas(diff); i++) {
+ delta = git_diff_get_delta(diff, i);
+
+ if ((error = apply_one(repo, pre_reader, contents, diff, i)) < 0)
+ goto done;
+
+ if (do_checkout) {
+ git_vector_insert(&paths, (void *)delta->old_file.path);
+
+ if (strcmp(delta->old_file.path, delta->new_file.path))
+ git_vector_insert(&paths, (void *)delta->new_file.path);
+ }
+ }
+
+ if (do_checkout) {
+ checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
+ checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
+
+ if (opts.location == GIT_APPLY_LOCATION_WORKDIR)
+ checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
+
+ checkout_opts.paths.strings = (char **)paths.contents;
+ checkout_opts.paths.count = paths.length;
+
+ error = git_checkout_index(repo, contents, &checkout_opts);
+ } else {
+ const git_index_entry *entry;
+
+ for (i = 0; i < git_index_entrycount(contents); i++) {
+ entry = git_index_get_byindex(contents, i);
+
+ if ((error = git_index_add(repo_index, entry)) < 0)
+ goto done;
+ }
+
+ error = git_index_write(repo_index);
+ }
+
+done:
+ git_vector_free(&paths);
+ git_index_free(contents);
+ git_reader_free(pre_reader);
+ git_index_free(repo_index);
+
+ return error;
+}