in src/apply.c [440:581]
static int apply_one(
git_repository *repo,
git_reader *preimage_reader,
git_index *preimage,
git_reader *postimage_reader,
git_index *postimage,
git_diff *diff,
git_strmap *removed_paths,
size_t i,
const git_apply_options *opts)
{
git_patch *patch = NULL;
git_buf pre_contents = GIT_BUF_INIT, post_contents = GIT_BUF_INIT;
const git_diff_delta *delta;
char *filename = NULL;
unsigned int mode;
git_oid pre_id, post_id;
git_filemode_t pre_filemode;
git_index_entry pre_entry, post_entry;
bool skip_preimage = false;
int error;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
goto done;
delta = git_patch_get_delta(patch);
if (opts->delta_cb) {
error = opts->delta_cb(delta, opts->payload);
if (error) {
if (error > 0)
error = 0;
goto done;
}
}
/*
* Ensure that the file has not been deleted or renamed if we're
* applying a modification delta.
*/
if (delta->status != GIT_DELTA_RENAMED &&
delta->status != GIT_DELTA_ADDED) {
if (git_strmap_exists(removed_paths, delta->old_file.path)) {
error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path);
goto done;
}
}
/*
* We may be applying a second delta to an already seen file. If so,
* use the already modified data in the postimage instead of the
* content from the index or working directory. (Don't do this in
* the case of a rename, which must be specified before additional
* deltas since we apply deltas to the target filename.)
*/
if (delta->status != GIT_DELTA_RENAMED) {
if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
postimage_reader, delta->old_file.path)) == 0) {
skip_preimage = true;
} else if (error == GIT_ENOTFOUND) {
git_error_clear();
error = 0;
} else {
goto done;
}
}
if (!skip_preimage && delta->status != GIT_DELTA_ADDED) {
error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
preimage_reader, delta->old_file.path);
/* ENOTFOUND means the preimage was not found; apply failed. */
if (error == GIT_ENOTFOUND)
error = GIT_EAPPLYFAIL;
/* When applying to BOTH, the index did not match the workdir. */
if (error == GIT_READER_MISMATCH)
error = apply_err("%s: does not match index", delta->old_file.path);
if (error < 0)
goto done;
/*
* We need to populate the preimage data structure with the
* contents that we are using as the preimage for this file.
* This allows us to apply patches to files that have been
* modified in the working directory. During checkout,
* we will use this expected preimage as the baseline, and
* limit checkout to only the paths affected by patch
* application. (Without this, we would fail to write the
* postimage contents to any file that had been modified
* from HEAD on-disk, even if the patch application succeeded.)
* Use the contents from the delta where available - some
* fields may not be available, like the old file mode (eg in
* an exact rename situation) so trust the patch parsing to
* validate and use the preimage data in that case.
*/
if (preimage) {
memset(&pre_entry, 0, sizeof(git_index_entry));
pre_entry.path = delta->old_file.path;
pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode;
git_oid_cpy(&pre_entry.id, &pre_id);
if ((error = git_index_add(preimage, &pre_entry)) < 0)
goto done;
}
}
if (delta->status != GIT_DELTA_DELETED) {
if ((error = git_apply__patch(&post_contents, &filename, &mode,
pre_contents.ptr, pre_contents.size, patch, opts)) < 0 ||
(error = git_blob_create_from_buffer(&post_id, repo,
post_contents.ptr, post_contents.size)) < 0)
goto done;
memset(&post_entry, 0, sizeof(git_index_entry));
post_entry.path = filename;
post_entry.mode = mode;
git_oid_cpy(&post_entry.id, &post_id);
if ((error = git_index_add(postimage, &post_entry)) < 0)
goto done;
}
if (delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_DELETED)
error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path);
if (delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_ADDED)
git_strmap_delete(removed_paths, delta->new_file.path);
done:
git_buf_dispose(&pre_contents);
git_buf_dispose(&post_contents);
git__free(filename);
git_patch_free(patch);
return error;
}