Unverified Commit 11fbead8 by Edward Thomson Committed by GitHub

Merge pull request #4705 from libgit2/ethomson/apply

Patch (diff) application
parents 2f5f3cfd 4e746d80
......@@ -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"
......
/*
* 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
/**
* When applying a patch, callback that will be made per delta (file).
*
* When the callback:
* - returns < 0, the apply process will be aborted.
* - returns > 0, the delta will not be applied, but the apply process
* continues
* - returns 0, the delta is applied, and the apply process continues.
*
* @param delta The delta to be applied
* @param payload User-specified payload
*/
typedef int (*git_apply_delta_cb)(
const git_diff_delta *delta,
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
* use `git_apply_init_options`.
*
* @see git_apply_to_tree, git_apply
*/
typedef struct {
unsigned int version;
git_apply_delta_cb delta_cb;
git_apply_hunk_cb hunk_cb;
void *payload;
} git_apply_options;
#define GIT_APPLY_OPTIONS_VERSION 1
#define GIT_APPLY_OPTIONS_INIT {GIT_APPLY_OPTIONS_VERSION}
/**
* 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
* @param options the options for the apply (or null for defaults)
*/
GIT_EXTERN(int) git_apply_to_tree(
git_index **out,
git_repository *repo,
git_tree *preimage,
git_diff *diff,
const git_apply_options *options);
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;
/**
* 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 location the location to apply (workdir, index or both)
* @param options the options for the apply (or null for defaults)
*/
GIT_EXTERN(int) git_apply(
git_repository *repo,
git_diff *diff,
git_apply_location_t location,
const git_apply_options *options);
/** @} */
GIT_END_DECL
#endif
......@@ -57,6 +57,7 @@ typedef enum {
GIT_RETRY = -32, /**< Internal only */
GIT_EMISMATCH = -33, /**< Hashsum mismatch in object */
GIT_EINDEXDIRTY = -34, /**< Unsaved changes in the index would be overwritten */
GIT_EAPPLYFAIL = -35, /**< Patch application failed */
} git_error_code;
/**
......
......@@ -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
......@@ -1015,6 +1015,7 @@ typedef struct {
struct stat st;
size_t path_len;
iterator_pathlist_search_t match;
git_oid id;
char path[GIT_FLEX_ARRAY];
} filesystem_iterator_entry;
......@@ -1265,7 +1266,32 @@ GIT_INLINE(bool) filesystem_iterator_is_dot_git(
return (len == 4 || path[len - 5] == '/');
}
static filesystem_iterator_entry *filesystem_iterator_entry_init(
static int filesystem_iterator_entry_hash(
filesystem_iterator *iter,
filesystem_iterator_entry *entry)
{
git_buf fullpath = GIT_BUF_INIT;
int error;
if (S_ISDIR(entry->st.st_mode)) {
memset(&entry->id, 0, GIT_OID_RAWSZ);
return 0;
}
if (iter->base.type == GIT_ITERATOR_TYPE_WORKDIR)
return git_repository_hashfile(&entry->id,
iter->base.repo, entry->path, GIT_OBJ_BLOB, NULL);
if (!(error = git_buf_joinpath(&fullpath, iter->root, entry->path)))
error = git_odb_hashfile(&entry->id, fullpath.ptr, GIT_OBJ_BLOB);
git_buf_dispose(&fullpath);
return error;
}
static int filesystem_iterator_entry_init(
filesystem_iterator_entry **out,
filesystem_iterator *iter,
filesystem_iterator_frame *frame,
const char *path,
size_t path_len,
......@@ -1274,15 +1300,19 @@ static filesystem_iterator_entry *filesystem_iterator_entry_init(
{
filesystem_iterator_entry *entry;
size_t entry_size;
int error = 0;
*out = NULL;
/* Make sure to append two bytes, one for the path's null
* termination, one for a possible trailing '/' for folders.
*/
if (GIT_ADD_SIZET_OVERFLOW(&entry_size,
sizeof(filesystem_iterator_entry), path_len) ||
GIT_ADD_SIZET_OVERFLOW(&entry_size, entry_size, 2) ||
(entry = git_pool_malloc(&frame->entry_pool, entry_size)) == NULL)
return NULL;
GITERR_CHECK_ALLOC_ADD(&entry_size,
sizeof(filesystem_iterator_entry), path_len);
GITERR_CHECK_ALLOC_ADD(&entry_size, entry_size, 2);
entry = git_pool_malloc(&frame->entry_pool, entry_size);
GITERR_CHECK_ALLOC(entry);
entry->path_len = path_len;
entry->match = pathlist_match;
......@@ -1295,7 +1325,13 @@ static filesystem_iterator_entry *filesystem_iterator_entry_init(
entry->path[entry->path_len] = '\0';
return entry;
if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH)
error = filesystem_iterator_entry_hash(iter, entry);
if (!error)
*out = entry;
return error;
}
static int filesystem_iterator_frame_push(
......@@ -1418,9 +1454,9 @@ static int filesystem_iterator_frame_push(
else if (dir_expected)
continue;
entry = filesystem_iterator_entry_init(new_frame,
path, path_len, &statbuf, pathlist_match);
GITERR_CHECK_ALLOC(entry);
if ((error = filesystem_iterator_entry_init(&entry,
iter, new_frame, path, path_len, &statbuf, pathlist_match)) < 0)
goto done;
git_vector_insert(&new_frame->entries, entry);
}
......@@ -1460,7 +1496,7 @@ static void filesystem_iterator_set_current(
iter->entry.ctime.seconds = entry->st.st_ctime;
iter->entry.mtime.seconds = entry->st.st_mtime;
#if defined(GIT_USE_NSEC)
#if defined(GIT_USE_NSEC)
iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec;
iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec;
#else
......@@ -1475,6 +1511,9 @@ static void filesystem_iterator_set_current(
iter->entry.gid = entry->st.st_gid;
iter->entry.file_size = entry->st.st_size;
if (iter->base.flags & GIT_ITERATOR_INCLUDE_HASH)
git_oid_cpy(&iter->entry.id, &entry->id);
iter->entry.path = entry->path;
iter->current_is_ignored = GIT_IGNORE_UNCHECKED;
......@@ -2259,6 +2298,35 @@ void git_iterator_free(git_iterator *iter)
git__free(iter);
}
int git_iterator_foreach(
git_iterator *iterator,
git_iterator_foreach_cb cb,
void *data)
{
const git_index_entry *iterator_item;
int error = 0;
if ((error = git_iterator_current(&iterator_item, iterator)) < 0)
goto done;
if ((error = cb(iterator_item, data)) != 0)
goto done;
while (true) {
if ((error = git_iterator_advance(&iterator_item, iterator)) < 0)
goto done;
if ((error = cb(iterator_item, data)) != 0)
goto done;
}
done:
if (error == GIT_ITEROVER)
error = 0;
return error;
}
int git_iterator_walk(
git_iterator **iterators,
size_t cnt,
......
......@@ -41,6 +41,8 @@ typedef enum {
GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6),
/** descend into symlinked directories */
GIT_ITERATOR_DESCEND_SYMLINKS = (1u << 7),
/** hash files in workdir or filesystem iterators */
GIT_ITERATOR_INCLUDE_HASH = (1u << 8),
} git_iterator_flag_t;
typedef enum {
......@@ -289,6 +291,19 @@ extern int git_iterator_current_workdir_path(
*/
extern git_index *git_iterator_index(git_iterator *iter);
typedef int (*git_iterator_foreach_cb)(
const git_index_entry *entry,
void *data);
/**
* Walk the given iterator and invoke the callback for each path
* contained in the iterator.
*/
extern int git_iterator_foreach(
git_iterator *iterator,
git_iterator_foreach_cb cb,
void *data);
typedef int (*git_iterator_walk_cb)(
const git_index_entry **entries,
void *data);
......
/*
* 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.
*/
#include "reader.h"
#include "fileops.h"
#include "blob.h"
#include "git2/tree.h"
#include "git2/blob.h"
#include "git2/index.h"
#include "git2/repository.h"
/* tree reader */
typedef struct {
git_reader reader;
git_tree *tree;
} tree_reader;
static int tree_reader_read(
git_buf *out,
git_oid *out_id,
git_filemode_t *out_filemode,
git_reader *_reader,
const char *filename)
{
tree_reader *reader = (tree_reader *)_reader;
git_tree_entry *tree_entry = NULL;
git_blob *blob = NULL;
int error;
if ((error = git_tree_entry_bypath(&tree_entry, reader->tree, filename)) < 0 ||
(error = git_blob_lookup(&blob, git_tree_owner(reader->tree), git_tree_entry_id(tree_entry))) < 0 ||
(error = git_buf_set(out, git_blob_rawcontent(blob), git_blob_rawsize(blob))) < 0)
goto done;
if (out_id)
git_oid_cpy(out_id, git_tree_entry_id(tree_entry));
if (out_filemode)
*out_filemode = git_tree_entry_filemode(tree_entry);
done:
git_blob_free(blob);
git_tree_entry_free(tree_entry);
return error;
}
int git_reader_for_tree(git_reader **out, git_tree *tree)
{
tree_reader *reader;
assert(out && tree);
reader = git__calloc(1, sizeof(tree_reader));
GITERR_CHECK_ALLOC(reader);
reader->reader.read = tree_reader_read;
reader->tree = tree;
*out = (git_reader *)reader;
return 0;
}
/* workdir reader */
typedef struct {
git_reader reader;
git_repository *repo;
git_index *index;
} workdir_reader;
static int workdir_reader_read(
git_buf *out,
git_oid *out_id,
git_filemode_t *out_filemode,
git_reader *_reader,
const char *filename)
{
workdir_reader *reader = (workdir_reader *)_reader;
git_buf path = GIT_BUF_INIT;
struct stat st;
git_filemode_t filemode;
git_filter_list *filters = NULL;
const git_index_entry *idx_entry;
git_oid id;
int error;
if ((error = git_buf_joinpath(&path,
git_repository_workdir(reader->repo), filename)) < 0)
goto done;
if ((error = p_lstat(path.ptr, &st)) < 0) {
if (error == -1 && errno == ENOENT)
error = GIT_ENOTFOUND;
giterr_set(GITERR_OS, "could not stat '%s'", path.ptr);
goto done;
}
filemode = git_futils_canonical_mode(st.st_mode);
/*
* Patch application - for example - uses the filtered version of
* the working directory data to match git. So we will run the
* workdir -> ODB filter on the contents in this workdir reader.
*/
if ((error = git_filter_list_load(&filters, reader->repo, NULL, filename,
GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT)) < 0)
goto done;
if ((error = git_filter_list_apply_to_file(out,
filters, reader->repo, path.ptr)) < 0)
goto done;
if (out_id || reader->index) {
if ((error = git_odb_hash(&id, out->ptr, out->size, GIT_OBJ_BLOB)) < 0)
goto done;
}
if (reader->index) {
if (!(idx_entry = git_index_get_bypath(reader->index, filename, 0)) ||
filemode != idx_entry->mode ||
!git_oid_equal(&id, &idx_entry->id)) {
error = GIT_READER_MISMATCH;
goto done;
}
}
if (out_id)
git_oid_cpy(out_id, &id);
if (out_filemode)
*out_filemode = filemode;
done:
git_filter_list_free(filters);
git_buf_dispose(&path);
return error;
}
int git_reader_for_workdir(
git_reader **out,
git_repository *repo,
bool validate_index)
{
workdir_reader *reader;
int error;
assert(out && repo);
reader = git__calloc(1, sizeof(workdir_reader));
GITERR_CHECK_ALLOC(reader);
reader->reader.read = workdir_reader_read;
reader->repo = repo;
if (validate_index &&
(error = git_repository_index__weakptr(&reader->index, repo)) < 0) {
git__free(reader);
return error;
}
*out = (git_reader *)reader;
return 0;
}
/* index reader */
typedef struct {
git_reader reader;
git_repository *repo;
git_index *index;
} index_reader;
static int index_reader_read(
git_buf *out,
git_oid *out_id,
git_filemode_t *out_filemode,
git_reader *_reader,
const char *filename)
{
index_reader *reader = (index_reader *)_reader;
const git_index_entry *entry;
git_blob *blob;
int error;
if ((entry = git_index_get_bypath(reader->index, filename, 0)) == NULL)
return GIT_ENOTFOUND;
if ((error = git_blob_lookup(&blob, reader->repo, &entry->id)) < 0)
goto done;
if (out_id)
git_oid_cpy(out_id, &entry->id);
if (out_filemode)
*out_filemode = entry->mode;
error = git_blob__getbuf(out, blob);
done:
git_blob_free(blob);
return error;
}
int git_reader_for_index(
git_reader **out,
git_repository *repo,
git_index *index)
{
index_reader *reader;
int error;
assert(out && repo);
reader = git__calloc(1, sizeof(index_reader));
GITERR_CHECK_ALLOC(reader);
reader->reader.read = index_reader_read;
reader->repo = repo;
if (index) {
reader->index = index;
} else if ((error = git_repository_index__weakptr(&reader->index, repo)) < 0) {
git__free(reader);
return error;
}
*out = (git_reader *)reader;
return 0;
}
/* generic */
int git_reader_read(
git_buf *out,
git_oid *out_id,
git_filemode_t *out_filemode,
git_reader *reader,
const char *filename)
{
assert(out && reader && filename);
return reader->read(out, out_id, out_filemode, reader, filename);
}
void git_reader_free(git_reader *reader)
{
if (!reader)
return;
git__free(reader);
}
/*
* 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_reader_h__
#define INCLUDE_reader_h__
#include "common.h"
/* Returned when the workdir does not match the index */
#define GIT_READER_MISMATCH 1
typedef struct git_reader git_reader;
/*
* The `git_reader` structure is a generic interface for reading the
* contents of a file by its name, and implementations are provided
* for reading out of a tree, the index, and the working directory.
*
* Note that the reader implementation is meant to have a short
* lifecycle and does not increase the refcount of the object that
* it's reading. Callers should ensure that they do not use a
* reader after disposing the underlying object that it reads.
*/
struct git_reader {
int (*read)(git_buf *out, git_oid *out_oid, git_filemode_t *mode, git_reader *reader, const char *filename);
};
/**
* Create a `git_reader` that will allow random access to the given
* tree. Paths requested via `git_reader_read` will be rooted at this
* tree, callers are not expected to recurse through tree lookups. Thus,
* you can request to read `/src/foo.c` and the tree provided to this
* function will be searched to find another tree named `src`, which
* will then be opened to find `foo.c`.
*
* @param out The reader for the given tree
* @param tree The tree object to read
* @return 0 on success, or an error code < 0
*/
extern int git_reader_for_tree(
git_reader **out,
git_tree *tree);
/**
* Create a `git_reader` that will allow random access to the given
* index, or the repository's index.
*
* @param out The reader for the given index
* @param repo The repository containing the index
* @param index The index to read, or NULL to use the repository's index
* @return 0 on success, or an error code < 0
*/
extern int git_reader_for_index(
git_reader **out,
git_repository *repo,
git_index *index);
/**
* Create a `git_reader` that will allow random access to the given
* repository's working directory. Note that the contents are read
* in repository format, meaning any workdir -> odb filters are
* applied.
*
* If `validate_index` is set to true, reads of files will hash the
* on-disk contents and ensure that the resulting object ID matches
* the repository's index. This ensures that the working directory
* is unmodified from the index contents.
*
* @param out The reader for the given working directory
* @param repo The repository containing the working directory
* @param validate_index If true, the working directory contents will
* be compared to the index contents during read to ensure that
* the working directory is unmodified.
* @return 0 on success, or an error code < 0
*/
extern int git_reader_for_workdir(
git_reader **out,
git_repository *repo,
bool validate_index);
/**
* Read the given filename from the reader and populate the given buffer
* with the contents and the given oid with the object ID.
*
* @param out The buffer to populate with the file contents
* @param out_id The oid to populate with the object ID
* @param reader The reader to read
* @param filename The filename to read from the reader
*/
extern int git_reader_read(
git_buf *out,
git_oid *out_id,
git_filemode_t *out_filemode,
git_reader *reader,
const char *filename);
/**
* Free the given reader and any associated objects.
*
* @param reader The reader to free
*/
extern void git_reader_free(git_reader *reader);
#endif
#include "clar_libgit2.h"
#include "apply_helpers.h"
static git_repository *repo;
#define TEST_REPO_PATH "merge-recursive"
void test_apply_callbacks__initialize(void)
{
git_oid oid;
git_commit *commit;
repo = cl_git_sandbox_init(TEST_REPO_PATH);
git_oid_fromstr(&oid, "539bd011c4822c560c1d17cab095006b7a10f707");
cl_git_pass(git_commit_lookup(&commit, repo, &oid));
cl_git_pass(git_reset(repo, (git_object *)commit, GIT_RESET_HARD, NULL));
git_commit_free(commit);
}
void test_apply_callbacks__cleanup(void)
{
cl_git_sandbox_cleanup();
}
static int delta_abort_cb(const git_diff_delta *delta, void *payload)
{
GIT_UNUSED(payload);
if (!strcmp(delta->old_file.path, "veal.txt"))
return -99;
return 0;
}
void test_apply_callbacks__delta_aborts(void)
{
git_diff *diff;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
opts.delta_cb = delta_abort_cb;
cl_git_pass(git_diff_from_buffer(&diff,
DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES)));
cl_git_fail_with(-99,
git_apply(repo, diff, GIT_APPLY_LOCATION_INDEX, &opts));
validate_index_unchanged(repo);
validate_workdir_unchanged(repo);
git_diff_free(diff);
}
static int delta_skip_cb(const git_diff_delta *delta, void *payload)
{
GIT_UNUSED(payload);
if (!strcmp(delta->old_file.path, "asparagus.txt"))
return 1;
return 0;
}
void test_apply_callbacks__delta_can_skip(void)
{
git_diff *diff;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
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, "a7b066537e6be7109abfe4ff97b675d4e077da20", 0, "veal.txt" },
};
size_t workdir_expected_cnt = sizeof(workdir_expected) /
sizeof(struct merge_index_entry);
opts.delta_cb = delta_skip_cb;
cl_git_pass(git_diff_from_buffer(&diff,
DIFF_MODIFY_TWO_FILES, strlen(DIFF_MODIFY_TWO_FILES)));
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);
}
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);
}
......@@ -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);
......@@ -150,6 +150,52 @@ void test_apply_fromdiff__prepend_nocontext(void)
PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__prepend_and_change(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE, NULL));
}
void test_apply_fromdiff__prepend_and_change_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__delete_and_change(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
PATCH_ORIGINAL_TO_DELETE_AND_CHANGE, NULL));
}
void test_apply_fromdiff__delete_and_change_nocontext(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT, &diff_opts));
}
void test_apply_fromdiff__delete_firstline(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_FIRSTLINE, "file.txt",
PATCH_ORIGINAL_TO_DELETE_FIRSTLINE, NULL));
}
void test_apply_fromdiff__append(void)
{
cl_git_pass(apply_buf(
......
......@@ -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);
......
#include "clar_libgit2.h"
#include "git2/sys/repository.h"
#include "apply.h"
#include "repository.h"
#include "buf_text.h"
#include "../patch/patch_common.h"
static git_repository *repo = NULL;
void test_apply_partial__initialize(void)
{
repo = cl_git_sandbox_init("renames");
}
void test_apply_partial__cleanup(void)
{
cl_git_sandbox_cleanup();
}
static int skip_addition(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines > hunk->old_lines) ? 1 : 0;
}
static int skip_deletion(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines < hunk->old_lines) ? 1 : 0;
}
static int skip_change(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines == hunk->old_lines) ? 1 : 0;
}
static int abort_addition(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines > hunk->old_lines) ? GIT_EUSER : 0;
}
static int abort_deletion(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines < hunk->old_lines) ? GIT_EUSER : 0;
}
static int abort_change(
const git_diff_hunk *hunk,
void *payload)
{
GIT_UNUSED(payload);
return (hunk->new_lines == hunk->old_lines) ? GIT_EUSER : 0;
}
static int apply_buf(
const char *old,
const char *oldname,
const char *new,
const char *newname,
const char *expected,
const git_diff_options *diff_opts,
git_apply_hunk_cb hunk_cb,
void *payload)
{
git_patch *patch;
git_buf result = GIT_BUF_INIT;
git_buf patchbuf = GIT_BUF_INIT;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
char *filename;
unsigned int mode;
int error;
size_t oldsize = strlen(old);
size_t newsize = strlen(new);
opts.hunk_cb = hunk_cb;
opts.payload = payload;
cl_git_pass(git_patch_from_buffers(&patch, old, oldsize, oldname, new, newsize, newname, diff_opts));
if ((error = git_apply__patch(&result, &filename, &mode, old, oldsize, patch, &opts)) == 0) {
cl_assert_equal_s(expected, result.ptr);
cl_assert_equal_s(newname, filename);
cl_assert_equal_i(0100644, mode);
}
git__free(filename);
git_buf_free(&result);
git_buf_free(&patchbuf);
git_patch_free(patch);
return error;
}
void test_apply_partial__prepend_and_change_skip_addition(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_ORIGINAL, NULL, skip_addition, NULL));
}
void test_apply_partial__prepend_and_change_nocontext_skip_addition(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_CHANGE_MIDDLE, &diff_opts, skip_addition, NULL));
}
void test_apply_partial__prepend_and_change_nocontext_abort_addition(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_fail(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_ORIGINAL, &diff_opts, abort_addition, NULL));
}
void test_apply_partial__prepend_and_change_skip_change(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_PREPEND_AND_CHANGE, NULL, skip_change, NULL));
}
void test_apply_partial__prepend_and_change_nocontext_skip_change(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_PREPEND, &diff_opts, skip_change, NULL));
}
void test_apply_partial__prepend_and_change_nocontext_abort_change(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_fail(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_PREPEND_AND_CHANGE, "file.txt",
FILE_PREPEND, &diff_opts, abort_change, NULL));
}
void test_apply_partial__delete_and_change_skip_deletion(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_ORIGINAL, NULL, skip_deletion, NULL));
}
void test_apply_partial__delete_and_change_nocontext_skip_deletion(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_CHANGE_MIDDLE, &diff_opts, skip_deletion, NULL));
}
void test_apply_partial__delete_and_change_nocontext_abort_deletion(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_fail(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_ORIGINAL, &diff_opts, abort_deletion, NULL));
}
void test_apply_partial__delete_and_change_skip_change(void)
{
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_DELETE_AND_CHANGE, NULL, skip_change, NULL));
}
void test_apply_partial__delete_and_change_nocontext_skip_change(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_pass(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_DELETE_FIRSTLINE, &diff_opts, skip_change, NULL));
}
void test_apply_partial__delete_and_change_nocontext_abort_change(void)
{
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
diff_opts.context_lines = 0;
cl_git_fail(apply_buf(
FILE_ORIGINAL, "file.txt",
FILE_DELETE_AND_CHANGE, "file.txt",
FILE_DELETE_FIRSTLINE, &diff_opts, abort_change, NULL));
}
#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, NULL));
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);
}
......@@ -3,6 +3,7 @@
#include "repository.h"
#include "fileops.h"
#include "../submodule/submodule_helpers.h"
#include "../merge/merge_helpers.h"
#include "iterator_helpers.h"
#include <stdarg.h>
......@@ -1474,3 +1475,48 @@ void test_iterator_workdir__pathlist_with_directory_include_trees(void)
git_vector_free(&filelist);
}
void test_iterator_workdir__hash_when_requested(void)
{
git_iterator *iter;
const git_index_entry *entry;
git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT;
git_oid expected_id = {{0}};
size_t i;
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, "7c7e08f9559d9e1551b91e1cf68f1d0066109add", 0, "oyster.txt" },
{ 0100644, "898d12687fb35be271c27c795a6b32c8b51da79e", 0, "veal.txt" },
};
g_repo = cl_git_sandbox_init("merge-recursive");
/* do the iteration normally, ensure there are no hashes */
cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) {
cl_git_pass(git_iterator_advance(&entry, iter));
cl_assert_equal_oid(&expected_id, &entry->id);
cl_assert_equal_s(expected[i].path, entry->path);
}
cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter));
git_iterator_free(iter);
/* do the iteration requesting hashes */
iter_opts.flags |= GIT_ITERATOR_INCLUDE_HASH;
cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts));
for (i = 0; i < sizeof(expected) / sizeof(struct merge_index_entry); i++) {
cl_git_pass(git_iterator_advance(&entry, iter));
cl_git_pass(git_oid_fromstr(&expected_id, expected[i].oid_str));
cl_assert_equal_oid(&expected_id, &entry->id);
cl_assert_equal_s(expected[i].path, entry->path);
}
cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&entry, iter));
git_iterator_free(iter);
}
......@@ -220,6 +220,112 @@
"@@ -0,0 +1 @@\n" \
"+insert at front\n"
/* An insertion at the beginning of the file and change in the middle */
#define FILE_PREPEND_AND_CHANGE \
"insert at front\n" \
"hey!\n" \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(THIS line is changed!)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..f73c8bb 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,9 +1,10 @@\n" \
"+insert at front\n" \
" hey!\n" \
" this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_ORIGINAL_TO_PREPEND_AND_CHANGE_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..f73c8bb 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -0,0 +1 @@\n" \
"+insert at front\n" \
"@@ -6 +7 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
/* A deletion at the beginning of the file and a change in the middle */
#define FILE_DELETE_AND_CHANGE \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(THIS line is changed!)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..1e2dfa6 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,9 +1,8 @@\n" \
"-hey!\n" \
" this is some context!\n" \
" around some lines\n" \
" that will change\n" \
" yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n" \
" and this\n" \
" is additional context\n" \
" below it!\n"
#define PATCH_ORIGINAL_TO_DELETE_AND_CHANGE_NOCONTEXT \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..1e2dfa6 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1 +0,0 @@\n" \
"-hey!\n" \
"@@ -6 +5 @@ yes it is!\n" \
"-(this line is changed)\n" \
"+(THIS line is changed!)\n"
/* A deletion at the beginning of the file */
#define FILE_DELETE_FIRSTLINE \
"this is some context!\n" \
"around some lines\n" \
"that will change\n" \
"yes it is!\n" \
"(this line is changed)\n" \
"and this\n" \
"is additional context\n" \
"below it!\n"
#define PATCH_ORIGINAL_TO_DELETE_FIRSTLINE \
"diff --git a/file.txt b/file.txt\n" \
"index 9432026..f31fa13 100644\n" \
"--- a/file.txt\n" \
"+++ b/file.txt\n" \
"@@ -1,4 +1,3 @@\n" \
"-hey!\n" \
" this is some context!\n" \
" around some lines\n" \
" that will change\n"
/* An insertion at the end of the file (and the resultant patch) */
#define FILE_APPEND \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment