Commit 77a15fe8 by Carlos Martín Nieto

Merge pull request #3018 from ethomson/stash_apply

Stash apply
parents 9cdd6578 1f1f5c63
......@@ -49,6 +49,7 @@ Microsoft Corporation
Olivier Ramonat
Peter Drahoš
Pierre Habouzit
Pierre-Olivier Latour
Przemyslaw Pawelczyk
Ramsay Jones
Robert G. Jakabosky
......
......@@ -34,6 +34,9 @@ v0.22 + 1
* On Mac OS X, we now use SecureTransport to provide the cryptographic
support for HTTPS connections insead of OpenSSL.
* Checkout can now accept an index for the baseline computations via the
`baseline_index` member.
### API additions
* The `git_merge_options` gained a `file_flags` member.
......@@ -63,6 +66,12 @@ support for HTTPS connections insead of OpenSSL.
* `git_index_add_frombuffer()` can now create a blob from memory
buffer and add it to the index which is attached to a repository.
* `git_stash_apply()` can now apply a stashed state from the stash list,
placing the data into the working directory and index.
* `git_stash_pop()` will apply a stashed state (like `git_stash_apply()`)
but will remove the stashed state after a successful application.
### API removals
### Breaking API changes
......
......@@ -272,7 +272,16 @@ typedef struct git_checkout_options {
*/
git_strarray paths;
git_tree *baseline; /**< expected content of workdir, defaults to HEAD */
/** The expected content of the working directory; defaults to HEAD.
* If the working directory does not match this baseline information,
* that will produce a checkout conflict.
*/
git_tree *baseline;
/** Like `baseline` above, though expressed as an index. This
* option overrides `baseline`.
*/
git_index *baseline_index; /**< expected content of workdir, expressed as an index. */
const char *target_directory; /**< alternative checkout path to workdir */
......
......@@ -70,6 +70,120 @@ GIT_EXTERN(int) git_stash_save(
const char *message,
unsigned int flags);
/** Stash application flags. */
typedef enum {
GIT_STASH_APPLY_DEFAULT = 0,
/* Try to reinstate not only the working tree's changes,
* but also the index's changes.
*/
GIT_STASH_APPLY_REINSTATE_INDEX = (1 << 0),
} git_stash_apply_flags;
typedef enum {
GIT_STASH_APPLY_PROGRESS_NONE = 0,
/** Loading the stashed data from the object database. */
GIT_STASH_APPLY_PROGRESS_LOADING_STASH,
/** The stored index is being analyzed. */
GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX,
/** The modified files are being analyzed. */
GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED,
/** The untracked and ignored files are being analyzed. */
GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED,
/** The untracked files are being written to disk. */
GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED,
/** The modified files are being written to disk. */
GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED,
/** The stash was applied successfully. */
GIT_STASH_APPLY_PROGRESS_DONE,
} git_stash_apply_progress_t;
/**
* Stash application progress notification function.
* Return 0 to continue processing, or a negative value to
* abort the stash application.
*/
typedef int (*git_stash_apply_progress_cb)(
git_stash_apply_progress_t progress,
void *payload);
/** Stash application options structure.
*
* Initialize with the `GIT_STASH_APPLY_OPTIONS_INIT` macro to set
* sensible defaults; for example:
*
* git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
*/
typedef struct git_stash_apply_options {
unsigned int version;
/** See `git_stash_apply_flags_t`, above. */
git_stash_apply_flags flags;
/** Options to use when writing files to the working directory. */
git_checkout_options checkout_options;
/** Optional callback to notify the consumer of application progress. */
git_stash_apply_progress_cb progress_cb;
void *progress_payload;
} git_stash_apply_options;
#define GIT_STASH_APPLY_OPTIONS_VERSION 1
#define GIT_STASH_APPLY_OPTIONS_INIT { \
GIT_STASH_APPLY_OPTIONS_VERSION, \
GIT_STASH_APPLY_DEFAULT, \
GIT_CHECKOUT_OPTIONS_INIT }
/**
* Initializes a `git_stash_apply_options` with default values. Equivalent to
* creating an instance with GIT_STASH_APPLY_OPTIONS_INIT.
*
* @param opts the `git_stash_apply_options` instance to initialize.
* @param version the version of the struct; you should pass
* `GIT_STASH_APPLY_OPTIONS_INIT` here.
* @return Zero on success; -1 on failure.
*/
int git_stash_apply_init_options(
git_stash_apply_options *opts, unsigned int version);
/**
* Apply a single stashed state from the stash list.
*
* If local changes in the working directory conflict with changes in the
* stash then GIT_EMERGECONFLICT will be returned. In this case, the index
* will always remain unmodified and all files in the working directory will
* remain unmodified. However, if you are restoring untracked files or
* ignored files and there is a conflict when applying the modified files,
* then those files will remain in the working directory.
*
* If passing the GIT_STASH_APPLY_REINSTATE_INDEX flag and there would be
* conflicts when reinstating the index, the function will return
* GIT_EMERGECONFLICT and both the working directory and index will be left
* unmodified.
*
* Note that a minimum checkout strategy of `GIT_CHECKOUT_SAFE` is implied.
*
* @param repo The owning repository.
* @param index The position within the stash list. 0 points to the
* most recent stashed state.
* @param options Options to control how stashes are applied.
*
* @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the
* given index, GIT_EMERGECONFLICT if changes exist in the working
* directory, or an error code
*/
GIT_EXTERN(int) git_stash_apply(
git_repository *repo,
size_t index,
const git_stash_apply_options *options);
/**
* This is a callback function you can provide to iterate over all the
* stashed states that will be invoked per entry.
......@@ -79,7 +193,7 @@ GIT_EXTERN(int) git_stash_save(
* @param message The stash message.
* @param stash_id The commit oid of the stashed state.
* @param payload Extra parameter to callback function.
* @return 0 to continue iterating or non-zero to stop
* @return 0 to continue iterating or non-zero to stop.
*/
typedef int (*git_stash_cb)(
size_t index,
......@@ -99,7 +213,7 @@ typedef int (*git_stash_cb)(
*
* @param payload Extra parameter to callback function.
*
* @return 0 on success, non-zero callback return value, or error code
* @return 0 on success, non-zero callback return value, or error code.
*/
GIT_EXTERN(int) git_stash_foreach(
git_repository *repo,
......@@ -114,13 +228,30 @@ GIT_EXTERN(int) git_stash_foreach(
* @param index The position within the stash list. 0 points to the
* most recent stashed state.
*
* @return 0 on success, or error code
* @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given
* index, or error code.
*/
GIT_EXTERN(int) git_stash_drop(
git_repository *repo,
size_t index);
/**
* Apply a single stashed state from the stash list and remove it from the list
* if successful.
*
* @param repo The owning repository.
* @param index The position within the stash list. 0 points to the
* most recent stashed state.
* @param options Options to control how stashes are applied.
*
* @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given
* index, or error code. (see git_stash_apply() above for details)
*/
GIT_EXTERN(int) git_stash_pop(
git_repository *repo,
size_t index,
const git_stash_apply_options *options);
/** @} */
GIT_END_DECL
#endif
......@@ -2397,7 +2397,7 @@ static int checkout_data_init(
&data->can_symlink, repo, GIT_CVAR_SYMLINKS)) < 0)
goto cleanup;
if (!data->opts.baseline) {
if (!data->opts.baseline && !data->opts.baseline_index) {
data->opts_free_baseline = true;
error = checkout_lookup_head_tree(&data->opts.baseline, repo);
......@@ -2501,12 +2501,21 @@ int git_checkout_iterator(
(error = git_iterator_for_workdir_ext(
&workdir, data.repo, data.opts.target_directory, index, NULL,
iterflags | GIT_ITERATOR_DONT_AUTOEXPAND,
data.pfx, data.pfx)) < 0 ||
(error = git_iterator_for_tree(
&baseline, data.opts.baseline,
iterflags, data.pfx, data.pfx)) < 0)
data.pfx, data.pfx)) < 0)
goto cleanup;
if (data.opts.baseline_index) {
if ((error = git_iterator_for_index(
&baseline, data.opts.baseline_index,
iterflags, data.pfx, data.pfx)) < 0)
goto cleanup;
} else {
if ((error = git_iterator_for_tree(
&baseline, data.opts.baseline,
iterflags, data.pfx, data.pfx)) < 0)
goto cleanup;
}
/* Should not have case insensitivity mismatch */
assert(git_iterator_ignore_case(workdir) == git_iterator_ignore_case(baseline));
......
......@@ -2451,6 +2451,104 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
return error;
}
int git_index_read_index(
git_index *index,
const git_index *new_index)
{
git_vector new_entries = GIT_VECTOR_INIT,
remove_entries = GIT_VECTOR_INIT;
git_iterator *index_iterator = NULL;
git_iterator *new_iterator = NULL;
const git_index_entry *old_entry, *new_entry;
git_index_entry *entry;
size_t i;
int error;
if ((error = git_vector_init(&new_entries, new_index->entries.length, index->entries._cmp)) < 0 ||
(error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0)
goto done;
if ((error = git_iterator_for_index(&index_iterator,
index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_index(&new_iterator,
(git_index *)new_index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
goto done;
if (((error = git_iterator_current(&old_entry, index_iterator)) < 0 &&
error != GIT_ITEROVER) ||
((error = git_iterator_current(&new_entry, new_iterator)) < 0 &&
error != GIT_ITEROVER))
goto done;
while (true) {
int diff;
if (old_entry && new_entry)
diff = git_index_entry_cmp(old_entry, new_entry);
else if (!old_entry && new_entry)
diff = 1;
else if (old_entry && !new_entry)
diff = -1;
else
break;
if (diff < 0) {
git_vector_insert(&remove_entries, (git_index_entry *)old_entry);
} else if (diff > 0) {
if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0)
goto done;
git_vector_insert(&new_entries, entry);
} else {
/* Path and stage are equal, if the OID is equal, keep it to
* keep the stat cache data.
*/
if (git_oid_equal(&old_entry->id, &new_entry->id)) {
git_vector_insert(&new_entries, (git_index_entry *)old_entry);
} else {
if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0)
goto done;
git_vector_insert(&new_entries, entry);
git_vector_insert(&remove_entries, (git_index_entry *)old_entry);
}
}
if (diff <= 0) {
if ((error = git_iterator_advance(&old_entry, index_iterator)) < 0 &&
error != GIT_ITEROVER)
goto done;
}
if (diff >= 0) {
if ((error = git_iterator_advance(&new_entry, new_iterator)) < 0 &&
error != GIT_ITEROVER)
goto done;
}
}
git_index_name_clear(index);
git_index_reuc_clear(index);
git_vector_swap(&new_entries, &index->entries);
git_vector_foreach(&remove_entries, i, entry) {
if (index->tree)
git_tree_cache_invalidate_path(index->tree, entry->path);
index_entry_free(entry);
}
error = 0;
done:
git_vector_free(&new_entries);
git_vector_free(&remove_entries);
git_iterator_free(index_iterator);
git_iterator_free(new_iterator);
return error;
}
git_repository *git_index_owner(const git_index *index)
{
return INDEX_OWNER(index);
......
......@@ -93,6 +93,8 @@ extern int git_index_snapshot_find(
size_t *at_pos, git_vector *snap, git_vector_cmp entry_srch,
const char *path, size_t path_len, int stage);
/* Replace an index with a new index */
int git_index_read_index(git_index *index, const git_index *new_index);
typedef struct {
git_index *index;
......
......@@ -1451,11 +1451,11 @@ static int merge_diff_list_insert_unmodified(
int git_merge_diff_list__find_differences(
git_merge_diff_list *diff_list,
const git_tree *ancestor_tree,
const git_tree *our_tree,
const git_tree *their_tree)
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter)
{
git_iterator *iterators[3] = {0};
git_iterator *iterators[3] = { ancestor_iter, our_iter, their_iter };
const git_index_entry *items[3] = {0}, *best_cur_item, *cur_items[3];
git_vector_cmp entry_compare = git_index_entry_cmp;
struct merge_diff_df_data df_data = {0};
......@@ -1463,12 +1463,7 @@ int git_merge_diff_list__find_differences(
size_t i, j;
int error = 0;
assert(diff_list && (our_tree || their_tree));
if ((error = git_iterator_for_tree(&iterators[TREE_IDX_ANCESTOR], (git_tree *)ancestor_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_tree(&iterators[TREE_IDX_OURS], (git_tree *)our_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_tree(&iterators[TREE_IDX_THEIRS], (git_tree *)their_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
goto done;
assert(diff_list && (our_iter || their_iter));
/* Set up the iterators */
for (i = 0; i < 3; i++) {
......@@ -1544,9 +1539,6 @@ int git_merge_diff_list__find_differences(
}
done:
for (i = 0; i < 3; i++)
git_iterator_free(iterators[i]);
if (error == GIT_ITEROVER)
error = 0;
......@@ -1757,14 +1749,28 @@ on_error:
return error;
}
int git_merge_trees(
static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given)
{
if (given)
return given;
if (git_iterator_for_nothing(empty, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL) < 0)
return NULL;
return *empty;
}
int git_merge__iterators(
git_index **out,
git_repository *repo,
const git_tree *ancestor_tree,
const git_tree *our_tree,
const git_tree *their_tree,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *theirs_iter,
const git_merge_options *given_opts)
{
git_iterator *empty_ancestor = NULL,
*empty_ours = NULL,
*empty_theirs = NULL;
git_merge_diff_list *diff_list;
git_merge_options opts;
git_merge_diff *conflict;
......@@ -1772,11 +1778,12 @@ int git_merge_trees(
size_t i;
int error = 0;
assert(out && repo && (our_tree || their_tree));
assert(out && repo);
*out = NULL;
GITERR_CHECK_VERSION(given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
GITERR_CHECK_VERSION(
given_opts, GIT_MERGE_OPTIONS_VERSION, "git_merge_options");
if ((error = merge_normalize_opts(repo, &opts, given_opts)) < 0)
return error;
......@@ -1784,7 +1791,12 @@ int git_merge_trees(
diff_list = git_merge_diff_list__alloc(repo);
GITERR_CHECK_ALLOC(diff_list);
if ((error = git_merge_diff_list__find_differences(diff_list, ancestor_tree, our_tree, their_tree)) < 0 ||
ancestor_iter = iterator_given_or_empty(&empty_ancestor, ancestor_iter);
our_iter = iterator_given_or_empty(&empty_ours, our_iter);
theirs_iter = iterator_given_or_empty(&empty_theirs, theirs_iter);
if ((error = git_merge_diff_list__find_differences(
diff_list, ancestor_iter, our_iter, theirs_iter)) < 0 ||
(error = git_merge_diff_list__find_renames(repo, diff_list, &opts)) < 0)
goto done;
......@@ -1808,10 +1820,44 @@ int git_merge_trees(
done:
git_merge_diff_list__free(diff_list);
git_iterator_free(empty_ancestor);
git_iterator_free(empty_ours);
git_iterator_free(empty_theirs);
return error;
}
int git_merge_trees(
git_index **out,
git_repository *repo,
const git_tree *ancestor_tree,
const git_tree *our_tree,
const git_tree *their_tree,
const git_merge_options *merge_opts)
{
git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL;
int error;
if ((error = git_iterator_for_tree(&ancestor_iter, (git_tree *)ancestor_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_tree(&our_iter, (git_tree *)our_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 ||
(error = git_iterator_for_tree(&their_iter, (git_tree *)their_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0)
goto done;
error = git_merge__iterators(
out, repo, ancestor_iter, our_iter, their_iter, merge_opts);
done:
git_iterator_free(ancestor_iter);
git_iterator_free(our_iter);
git_iterator_free(their_iter);
return error;
}
int git_merge_commits(
git_index **out,
git_repository *repo,
......
......@@ -10,6 +10,7 @@
#include "vector.h"
#include "commit_list.h"
#include "pool.h"
#include "iterator.h"
#include "git2/merge.h"
#include "git2/types.h"
......@@ -121,10 +122,11 @@ int git_merge__bases_many(
git_merge_diff_list *git_merge_diff_list__alloc(git_repository *repo);
int git_merge_diff_list__find_differences(git_merge_diff_list *merge_diff_list,
const git_tree *ancestor_tree,
const git_tree *ours_tree,
const git_tree *theirs_tree);
int git_merge_diff_list__find_differences(
git_merge_diff_list *merge_diff_list,
git_iterator *ancestor_iterator,
git_iterator *ours_iter,
git_iterator *theirs_iter);
int git_merge_diff_list__find_renames(git_repository *repo, git_merge_diff_list *merge_diff_list, const git_merge_options *opts);
......@@ -138,6 +140,14 @@ int git_merge__setup(
const git_annotated_commit *heads[],
size_t heads_len);
int git_merge__iterators(
git_index **out,
git_repository *repo,
git_iterator *ancestor_iter,
git_iterator *our_iter,
git_iterator *their_iter,
const git_merge_options *given_opts);
int git_merge__check_result(git_repository *repo, git_index *index_new);
int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index);
......
......@@ -131,6 +131,11 @@ void test_core_structinit__compare(void)
git_revert_options, GIT_REVERT_OPTIONS_VERSION, \
GIT_REVERT_OPTIONS_INIT, git_revert_init_options);
/* stash apply */
CHECK_MACRO_FUNC_INIT_EQUAL( \
git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_VERSION, \
GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_init_options);
/* status */
CHECK_MACRO_FUNC_INIT_EQUAL( \
git_status_options, GIT_STATUS_OPTIONS_VERSION, \
......
#include "clar_libgit2.h"
#include "posix.h"
#include "index.h"
static git_repository *_repo;
static git_index *_index;
void test_index_read_index__initialize(void)
{
git_object *head;
git_reference *head_ref;
_repo = cl_git_sandbox_init("testrepo");
cl_git_pass(git_revparse_ext(&head, &head_ref, _repo, "HEAD"));
cl_git_pass(git_reset(_repo, head, GIT_RESET_HARD, NULL));
cl_git_pass(git_repository_index(&_index, _repo));
git_reference_free(head_ref);
git_object_free(head);
}
void test_index_read_index__cleanup(void)
{
git_index_free(_index);
cl_git_sandbox_cleanup();
}
void test_index_read_index__maintains_stat_cache(void)
{
git_index *new_index;
git_oid index_id;
git_index_entry new_entry;
const git_index_entry *e;
git_tree *tree;
size_t i;
cl_assert_equal_i(4, git_index_entrycount(_index));
/* write-tree */
cl_git_pass(git_index_write_tree(&index_id, _index));
/* read-tree, then read index */
git_tree_lookup(&tree, _repo, &index_id);
cl_git_pass(git_index_new(&new_index));
cl_git_pass(git_index_read_tree(new_index, tree));
git_tree_free(tree);
/* add a new entry that will not have stat data */
memset(&new_entry, 0, sizeof(git_index_entry));
new_entry.path = "Hello";
git_oid_fromstr(&new_entry.id, "0123456789012345678901234567890123456789");
new_entry.file_size = 1234;
new_entry.mode = 0100644;
cl_git_pass(git_index_add(new_index, &new_entry));
cl_assert_equal_i(5, git_index_entrycount(new_index));
cl_git_pass(git_index_read_index(_index, new_index));
git_index_free(new_index);
cl_assert_equal_i(5, git_index_entrycount(_index));
for (i = 0; i < git_index_entrycount(_index); i++) {
e = git_index_get_byindex(_index, i);
if (strcmp(e->path, "Hello") == 0) {
cl_assert_equal_i(0, e->ctime.seconds);
cl_assert_equal_i(0, e->mtime.seconds);
} else {
cl_assert(0 != e->ctime.seconds);
cl_assert(0 != e->mtime.seconds);
}
}
}
......@@ -43,6 +43,7 @@ static void test_find_differences(
git_merge_diff_list *merge_diff_list = git_merge_diff_list__alloc(repo);
git_oid ancestor_oid, ours_oid, theirs_oid;
git_tree *ancestor_tree, *ours_tree, *theirs_tree;
git_iterator *ancestor_iter, *ours_iter, *theirs_iter;
git_merge_options opts = GIT_MERGE_OPTIONS_INIT;
opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES;
......@@ -66,7 +67,14 @@ static void test_find_differences(
cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid));
cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid));
cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_tree, ours_tree, theirs_tree));
cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree,
GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL));
cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_iter, ours_iter, theirs_iter));
cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts));
/*
......@@ -77,6 +85,10 @@ static void test_find_differences(
cl_assert(merge_test_merge_conflicts(&merge_diff_list->conflicts, treediff_conflict_data, treediff_conflict_data_len));
git_iterator_free(ancestor_iter);
git_iterator_free(ours_iter);
git_iterator_free(theirs_iter);
git_tree_free(ancestor_tree);
git_tree_free(ours_tree);
git_tree_free(theirs_tree);
......
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