|
| 1 | +/* |
| 2 | + * Copyright (C) the libgit2 contributors. All rights reserved. |
| 3 | + * |
| 4 | + * This file is part of libgit2, distributed under the GNU GPL v2 with |
| 5 | + * a Linking Exception. For full terms see the included COPYING file. |
| 6 | + */ |
| 7 | + |
| 8 | +#include <assert.h> |
| 9 | + |
| 10 | +#include "git2/patch.h" |
| 11 | +#include "git2/filter.h" |
| 12 | +#include "array.h" |
| 13 | +#include "diff_patch.h" |
| 14 | +#include "fileops.h" |
| 15 | +#include "apply.h" |
| 16 | + |
| 17 | +#define apply_err(...) \ |
| 18 | + ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) |
| 19 | + |
| 20 | +typedef struct { |
| 21 | + /* The lines that we allocate ourself are allocated out of the pool. |
| 22 | + * (Lines may have been allocated out of the diff.) |
| 23 | + */ |
| 24 | + git_pool pool; |
| 25 | + git_vector lines; |
| 26 | +} patch_image; |
| 27 | + |
| 28 | +static void patch_line_init( |
| 29 | + git_diff_line *out, |
| 30 | + const char *in, |
| 31 | + size_t in_len, |
| 32 | + size_t in_offset) |
| 33 | +{ |
| 34 | + out->content = in; |
| 35 | + out->content_len = in_len; |
| 36 | + out->content_offset = in_offset; |
| 37 | +} |
| 38 | + |
| 39 | +static unsigned int patch_image_init(patch_image *out) |
| 40 | +{ |
| 41 | + memset(out, 0x0, sizeof(patch_image)); |
| 42 | + return 0; |
| 43 | +} |
| 44 | + |
| 45 | +static int patch_image_init_fromstr( |
| 46 | + patch_image *out, const char *in, size_t in_len) |
| 47 | +{ |
| 48 | + git_diff_line *line; |
| 49 | + const char *start, *end; |
| 50 | + |
| 51 | + memset(out, 0x0, sizeof(patch_image)); |
| 52 | + |
| 53 | + git_pool_init(&out->pool, sizeof(git_diff_line)); |
| 54 | + |
| 55 | + for (start = in; start < in + in_len; start = end) { |
| 56 | + end = memchr(start, '\n', in_len); |
| 57 | + |
| 58 | + if (end < in + in_len) |
| 59 | + end++; |
| 60 | + |
| 61 | + line = git_pool_mallocz(&out->pool, 1); |
| 62 | + GITERR_CHECK_ALLOC(line); |
| 63 | + |
| 64 | + if (git_vector_insert(&out->lines, line) < 0) |
| 65 | + return -1; |
| 66 | + |
| 67 | + patch_line_init(line, start, (end - start), (start - in)); |
| 68 | + } |
| 69 | + |
| 70 | + return 0; |
| 71 | +} |
| 72 | + |
| 73 | +static void patch_image_free(patch_image *image) |
| 74 | +{ |
| 75 | + if (image == NULL) |
| 76 | + return; |
| 77 | + |
| 78 | + git_pool_clear(&image->pool); |
| 79 | + git_vector_free(&image->lines); |
| 80 | +} |
| 81 | + |
| 82 | +static bool match_hunk( |
| 83 | + patch_image *image, |
| 84 | + patch_image *preimage, |
| 85 | + size_t linenum) |
| 86 | +{ |
| 87 | + bool match = 0; |
| 88 | + size_t i; |
| 89 | + |
| 90 | + /* Ensure this hunk is within the image boundaries. */ |
| 91 | + if (git_vector_length(&preimage->lines) + linenum > |
| 92 | + git_vector_length(&image->lines)) |
| 93 | + return 0; |
| 94 | + |
| 95 | + match = 1; |
| 96 | + |
| 97 | + /* Check exact match. */ |
| 98 | + for (i = 0; i < git_vector_length(&preimage->lines); i++) { |
| 99 | + git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); |
| 100 | + git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); |
| 101 | + |
| 102 | + if (preimage_line->content_len != preimage_line->content_len || |
| 103 | + memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { |
| 104 | + match = 0; |
| 105 | + break; |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + return match; |
| 110 | +} |
| 111 | + |
| 112 | +static bool find_hunk_linenum( |
| 113 | + size_t *out, |
| 114 | + patch_image *image, |
| 115 | + patch_image *preimage, |
| 116 | + size_t linenum) |
| 117 | +{ |
| 118 | + size_t max = git_vector_length(&image->lines); |
| 119 | + bool match; |
| 120 | + |
| 121 | + if (linenum > max) |
| 122 | + linenum = max; |
| 123 | + |
| 124 | + match = match_hunk(image, preimage, linenum); |
| 125 | + |
| 126 | + *out = linenum; |
| 127 | + return match; |
| 128 | +} |
| 129 | + |
| 130 | +static int update_hunk( |
| 131 | + patch_image *image, |
| 132 | + unsigned int linenum, |
| 133 | + patch_image *preimage, |
| 134 | + patch_image *postimage) |
| 135 | +{ |
| 136 | + size_t postlen = git_vector_length(&postimage->lines); |
| 137 | + size_t prelen = git_vector_length(&preimage->lines); |
| 138 | + size_t i; |
| 139 | + int error = 0; |
| 140 | + |
| 141 | + if (postlen > prelen) |
| 142 | + error = git_vector_grow_at( |
| 143 | + &image->lines, linenum, (postlen - prelen)); |
| 144 | + else if (prelen > postlen) |
| 145 | + error = git_vector_shrink_at( |
| 146 | + &image->lines, linenum, (prelen - postlen)); |
| 147 | + |
| 148 | + if (error) { |
| 149 | + giterr_set_oom(); |
| 150 | + return -1; |
| 151 | + } |
| 152 | + |
| 153 | + for (i = 0; i < git_vector_length(&postimage->lines); i++) { |
| 154 | + image->lines.contents[linenum + i] = |
| 155 | + git_vector_get(&postimage->lines, i); |
| 156 | + } |
| 157 | + |
| 158 | + return 0; |
| 159 | +} |
| 160 | + |
| 161 | +static int apply_hunk( |
| 162 | + patch_image *image, |
| 163 | + git_patch *patch, |
| 164 | + diff_patch_hunk *hunk) |
| 165 | +{ |
| 166 | + patch_image preimage, postimage; |
| 167 | + size_t line_num, i; |
| 168 | + int error = 0; |
| 169 | + |
| 170 | + if ((error = patch_image_init(&preimage)) < 0 || |
| 171 | + (error = patch_image_init(&postimage)) < 0) |
| 172 | + goto done; |
| 173 | + |
| 174 | + for (i = 0; i < hunk->line_count; i++) { |
| 175 | + size_t linenum = hunk->line_start + i; |
| 176 | + git_diff_line *line = git_array_get(patch->lines, linenum); |
| 177 | + |
| 178 | + if (!line) { |
| 179 | + error = apply_err("Preimage does not contain line %d", linenum); |
| 180 | + goto done; |
| 181 | + } |
| 182 | + |
| 183 | + if (line->origin == GIT_DIFF_LINE_CONTEXT || |
| 184 | + line->origin == GIT_DIFF_LINE_DELETION) { |
| 185 | + if ((error = git_vector_insert(&preimage.lines, line)) < 0) |
| 186 | + goto done; |
| 187 | + } |
| 188 | + |
| 189 | + if (line->origin == GIT_DIFF_LINE_CONTEXT || |
| 190 | + line->origin == GIT_DIFF_LINE_ADDITION) { |
| 191 | + if ((error = git_vector_insert(&postimage.lines, line)) < 0) |
| 192 | + goto done; |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0; |
| 197 | + |
| 198 | + if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { |
| 199 | + error = apply_err("Hunk at line %d did not apply", |
| 200 | + hunk->hunk.new_start); |
| 201 | + goto done; |
| 202 | + } |
| 203 | + |
| 204 | + error = update_hunk(image, line_num, &preimage, &postimage); |
| 205 | + |
| 206 | +done: |
| 207 | + patch_image_free(&preimage); |
| 208 | + patch_image_free(&postimage); |
| 209 | + |
| 210 | + return error; |
| 211 | +} |
| 212 | + |
| 213 | +static int apply_hunks( |
| 214 | + git_buf *out, |
| 215 | + const char *source, |
| 216 | + size_t source_len, |
| 217 | + git_patch *patch) |
| 218 | +{ |
| 219 | + diff_patch_hunk *hunk; |
| 220 | + git_diff_line *line; |
| 221 | + patch_image image; |
| 222 | + size_t i; |
| 223 | + int error = 0; |
| 224 | + |
| 225 | + if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) |
| 226 | + goto done; |
| 227 | + |
| 228 | + git_array_foreach(patch->hunks, i, hunk) { |
| 229 | + if ((error = apply_hunk(&image, patch, hunk)) < 0) |
| 230 | + goto done; |
| 231 | + } |
| 232 | + |
| 233 | + git_vector_foreach(&image.lines, i, line) |
| 234 | + git_buf_put(out, line->content, line->content_len); |
| 235 | + |
| 236 | +done: |
| 237 | + patch_image_free(&image); |
| 238 | + |
| 239 | + return error; |
| 240 | +} |
| 241 | + |
| 242 | +int git_apply__patch( |
| 243 | + git_buf *contents_out, |
| 244 | + char **filename_out, |
| 245 | + unsigned int *mode_out, |
| 246 | + const char *source, |
| 247 | + size_t source_len, |
| 248 | + git_patch *patch) |
| 249 | +{ |
| 250 | + char *filename = NULL; |
| 251 | + unsigned int mode = 0; |
| 252 | + int error = 0; |
| 253 | + |
| 254 | + assert(contents_out && filename_out && mode_out && (source || !source_len) && patch); |
| 255 | + |
| 256 | + *filename_out = NULL; |
| 257 | + *mode_out = 0; |
| 258 | + |
| 259 | + if (patch->delta->status != GIT_DELTA_DELETED) { |
| 260 | + filename = git__strdup(patch->nfile.file->path); |
| 261 | + mode = patch->nfile.file->mode ? |
| 262 | + patch->nfile.file->mode : GIT_FILEMODE_BLOB; |
| 263 | + } |
| 264 | + |
| 265 | + if ((error = apply_hunks(contents_out, source, source_len, patch)) < 0) |
| 266 | + goto done; |
| 267 | + |
| 268 | + if (patch->delta->status == GIT_DELTA_DELETED && |
| 269 | + git_buf_len(contents_out) > 0) { |
| 270 | + error = apply_err("removal patch leaves file contents"); |
| 271 | + goto done; |
| 272 | + } |
| 273 | + |
| 274 | + *filename_out = filename; |
| 275 | + *mode_out = mode; |
| 276 | + |
| 277 | +done: |
| 278 | + if (error < 0) |
| 279 | + git__free(filename); |
| 280 | + |
| 281 | + return error; |
| 282 | +} |
0 commit comments