Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/libgit2.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarius Ungureanu <marius.ungureanu@xamarin.com>2015-04-06 20:04:54 +0300
committerMarius Ungureanu <marius.ungureanu@xamarin.com>2015-04-06 20:04:54 +0300
commit14a29d2331669b978d24be29858c8cbca34e76ce (patch)
tree7fd7f4bcaa37b16c49cd37d3d93f04abdd275e95
parent47f37400253210f483d84fb9c2ecf44fb5986849 (diff)
parentb2d02433141ddc4c914a6b0e0027f6c58fc11cb6 (diff)
Merge remote-tracking branch 'ethomson/stash_apply' into monomono
-rw-r--r--AUTHORS1
-rw-r--r--include/git2/checkout.h1
-rw-r--r--include/git2/stash.h68
-rw-r--r--src/checkout.c19
-rw-r--r--src/index.c98
-rw-r--r--src/index.h2
-rw-r--r--src/merge.c86
-rw-r--r--src/merge.h18
-rw-r--r--src/stash.c287
-rw-r--r--tests/index/read_index.c73
-rw-r--r--tests/merge/trees/treediff.c14
-rw-r--r--tests/stash/apply.c215
12 files changed, 835 insertions, 47 deletions
diff --git a/AUTHORS b/AUTHORS
index 2eaec0ff6..61e2113ec 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -49,6 +49,7 @@ Microsoft Corporation
Olivier Ramonat
Peter Drahoš
Pierre Habouzit
+Pierre-Olivier Latour
Przemyslaw Pawelczyk
Ramsay Jones
Robert G. Jakabosky
diff --git a/include/git2/checkout.h b/include/git2/checkout.h
index ed39bd3cb..58f190719 100644
--- a/include/git2/checkout.h
+++ b/include/git2/checkout.h
@@ -273,6 +273,7 @@ typedef struct git_checkout_options {
git_strarray paths;
git_tree *baseline; /**< expected content of workdir, defaults to HEAD */
+ git_index *baseline_index; /**< expected content of workdir, expressed as an index. */
const char *target_directory; /**< alternative checkout path to workdir */
diff --git a/include/git2/stash.h b/include/git2/stash.h
index 280c647e8..93f2aacbc 100644
--- a/include/git2/stash.h
+++ b/include/git2/stash.h
@@ -70,6 +70,47 @@ GIT_EXTERN(int) git_stash_save(
const char *message,
unsigned int flags);
+typedef enum {
+ GIT_APPLY_DEFAULT = 0,
+
+ /* Try to reinstate not only the working tree's changes,
+ * but also the index's ones.
+ */
+ GIT_APPLY_REINSTATE_INDEX = (1 << 0),
+} git_apply_flags;
+
+/**
+ * Apply a single stashed state from the stash list.
+ *
+ * If any untracked or ignored file saved in the stash already exist in the
+ * workdir, the function will return GIT_EEXISTS and both the workdir and index
+ * will be left untouched.
+ *
+ * If local changes in the workdir would be overwritten when applying
+ * modifications saved in the stash, the function will return GIT_EMERGECONFLICT
+ * and the index will be left untouched. The workdir files will be left
+ * unmodified as well but restored untracked or ignored files that were saved
+ * in the stash will be left around in the workdir.
+ *
+ * If passing the GIT_APPLY_REINSTATE_INDEX flag and there would be conflicts
+ * when reinstating the index, the function will return GIT_EMERGECONFLICT and both
+ * the workdir and index will be left untouched.
+ *
+ * @param repo The owning repository.
+ * @param index The position within the stash list. 0 points to the
+ * most recent stashed state.
+ * @param checkout_options Options to control how files are checked out
+ * @param flags Flags to control the applying process. (see GIT_APPLY_* above)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if there's no stashed state for the given
+ * index, or error code. (see details above)
+ */
+GIT_EXTERN(int) git_stash_apply(
+ git_repository *repo,
+ size_t index,
+ const git_checkout_options *checkout_options,
+ unsigned int flags);
+
/**
* This is a callback function you can provide to iterate over all the
* stashed states that will be invoked per entry.
@@ -79,7 +120,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 +140,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 +155,32 @@ 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 checkout_options Options to control how files are checked out
+ * @param flags Flags to control the applying process. (see GIT_APPLY_* above)
+ *
+ * @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_checkout_options *checkout_options,
+ unsigned int flags);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/checkout.c b/src/checkout.c
index 0b6e298a0..d15073601 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -2374,7 +2374,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);
@@ -2477,12 +2477,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));
diff --git a/src/index.c b/src/index.c
index dbcc37a58..56d12d5a7 100644
--- a/src/index.c
+++ b/src/index.c
@@ -2447,6 +2447,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);
diff --git a/src/index.h b/src/index.h
index 6d2904fdc..0f6f4e86e 100644
--- a/src/index.h
+++ b/src/index.h
@@ -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;
diff --git a/src/merge.c b/src/merge.c
index bd676aacf..5e7727429 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -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,
diff --git a/src/merge.h b/src/merge.h
index fe4505f8b..3caf617c6 100644
--- a/src/merge.h
+++ b/src/merge.h
@@ -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);
diff --git a/src/stash.c b/src/stash.c
index 8aa48cafe..e3e025771 100644
--- a/src/stash.c
+++ b/src/stash.c
@@ -8,6 +8,7 @@
#include "common.h"
#include "repository.h"
#include "commit.h"
+#include "message.h"
#include "tree.h"
#include "reflog.h"
#include "git2/diff.h"
@@ -16,7 +17,11 @@
#include "git2/checkout.h"
#include "git2/index.h"
#include "git2/transaction.h"
+#include "git2/merge.h"
+#include "index.h"
#include "signature.h"
+#include "iterator.h"
+#include "merge.h"
static int create_error(int error, const char *msg)
{
@@ -49,23 +54,14 @@ static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit)
static int append_commit_description(git_buf *out, git_commit* commit)
{
- const char *message;
- size_t pos = 0, len;
+ const char *summary = git_commit_summary(commit);
+ GITERR_CHECK_ALLOC(summary);
if (append_abbreviated_oid(out, git_commit_id(commit)) < 0)
return -1;
- message = git_commit_message(commit);
- len = strlen(message);
-
- /* TODO: Replace with proper commit short message
- * when git_commit_message_short() is implemented.
- */
- while (pos < len && message[pos] != '\n')
- pos++;
-
git_buf_putc(out, ' ');
- git_buf_put(out, message, pos);
+ git_buf_puts(out, summary);
git_buf_putc(out, '\n');
return git_buf_oom(out) ? -1 : 0;
@@ -553,6 +549,257 @@ cleanup:
return error;
}
+static int retrieve_stash_commit(
+ git_commit **commit,
+ git_repository *repo,
+ size_t index)
+{
+ git_reference *stash = NULL;
+ git_reflog *reflog = NULL;
+ int error;
+ size_t max;
+ const git_reflog_entry *entry;
+
+ if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
+ goto cleanup;
+
+ max = git_reflog_entrycount(reflog);
+ if (!max || index > max - 1) {
+ error = GIT_ENOTFOUND;
+ giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
+ goto cleanup;
+ }
+
+ entry = git_reflog_entry_byindex(reflog, index);
+ if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0)
+ goto cleanup;
+
+cleanup:
+ git_reference_free(stash);
+ git_reflog_free(reflog);
+ return error;
+}
+
+static int retrieve_stash_trees(
+ git_tree **out_stash_tree,
+ git_tree **out_base_tree,
+ git_tree **out_index_tree,
+ git_tree **out_index_parent_tree,
+ git_tree **out_untracked_tree,
+ git_commit *stash_commit)
+{
+ git_tree *stash_tree = NULL;
+ git_commit *base_commit = NULL;
+ git_tree *base_tree = NULL;
+ git_commit *index_commit = NULL;
+ git_tree *index_tree = NULL;
+ git_commit *index_parent_commit = NULL;
+ git_tree *index_parent_tree = NULL;
+ git_commit *untracked_commit = NULL;
+ git_tree *untracked_tree = NULL;
+ int error;
+
+ if ((error = git_commit_tree(&stash_tree, stash_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&base_tree, base_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&index_tree, index_commit)) < 0)
+ goto cleanup;
+
+ if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&index_parent_tree, index_parent_commit)) < 0)
+ goto cleanup;
+
+ if (git_commit_parentcount(stash_commit) == 3) {
+ if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0)
+ goto cleanup;
+ if ((error = git_commit_tree(&untracked_tree, untracked_commit)) < 0)
+ goto cleanup;
+ }
+
+ *out_stash_tree = stash_tree;
+ *out_base_tree = base_tree;
+ *out_index_tree = index_tree;
+ *out_index_parent_tree = index_parent_tree;
+ *out_untracked_tree = untracked_tree;
+
+cleanup:
+ git_commit_free(untracked_commit);
+ git_commit_free(index_parent_commit);
+ git_commit_free(index_commit);
+ git_commit_free(base_commit);
+ if (error < 0) {
+ git_tree_free(stash_tree);
+ git_tree_free(base_tree);
+ git_tree_free(index_tree);
+ git_tree_free(index_parent_tree);
+ git_tree_free(untracked_tree);
+ }
+ return error;
+}
+
+static int merge_index_and_tree(
+ git_index **out,
+ git_repository *repo,
+ git_tree *ancestor_tree,
+ git_index *ours_index,
+ git_tree *theirs_tree)
+{
+ git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL;
+ const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE;
+ int error;
+
+ if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 ||
+ (error = git_iterator_for_tree(&theirs, theirs_tree, flags, NULL, NULL)) < 0)
+ goto done;
+
+ error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL);
+
+done:
+ git_iterator_free(ancestor);
+ git_iterator_free(ours);
+ git_iterator_free(theirs);
+ return error;
+}
+
+static void normalize_checkout_options(
+ git_checkout_options *checkout_opts,
+ const git_checkout_options *given_checkout_opts)
+{
+ if (given_checkout_opts != NULL) {
+ memcpy(checkout_opts, given_checkout_opts, sizeof(git_checkout_options));
+ } else {
+ git_checkout_options default_checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
+ default_checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+
+ memcpy(checkout_opts, &default_checkout_opts, sizeof(git_checkout_options));
+ }
+
+ if (!checkout_opts->our_label)
+ checkout_opts->our_label = "Updated upstream";
+
+ if (!checkout_opts->their_label)
+ checkout_opts->their_label = "Stashed changes";
+}
+
+int git_stash_apply(
+ git_repository *repo,
+ size_t index,
+ const git_checkout_options *given_checkout_opts,
+ unsigned int flags)
+{
+ git_checkout_options checkout_opts;
+ unsigned int checkout_strategy;
+ git_commit *stash_commit = NULL;
+ git_tree *stash_tree = NULL;
+ git_tree *stash_parent_tree = NULL;
+ git_tree *index_tree = NULL;
+ git_tree *index_parent_tree = NULL;
+ git_tree *untracked_tree = NULL;
+ git_index *repo_index = NULL;
+ git_index *unstashed_index = NULL;
+ git_index *modified_index = NULL;
+ git_index *untracked_index = NULL;
+ int error;
+
+ normalize_checkout_options(&checkout_opts, given_checkout_opts);
+ checkout_strategy = checkout_opts.checkout_strategy;
+
+ /* Retrieve commit corresponding to the given stash */
+ if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0)
+ goto cleanup;
+
+ /* Retrieve all trees in the stash */
+ if ((error = retrieve_stash_trees(
+ &stash_tree, &stash_parent_tree, &index_tree,
+ &index_parent_tree, &untracked_tree, stash_commit)) < 0)
+ goto cleanup;
+
+ /* Load repo index */
+ if ((error = git_repository_index(&repo_index, repo)) < 0)
+ goto cleanup;
+
+ /* Restore index if required */
+ if ((flags & GIT_APPLY_REINSTATE_INDEX) &&
+ git_oid_cmp(git_tree_id(stash_parent_tree), git_tree_id(index_tree))) {
+
+ if ((error = merge_index_and_tree(
+ &unstashed_index, repo, index_parent_tree, repo_index, index_tree)) < 0)
+ goto cleanup;
+
+ if (git_index_has_conflicts(unstashed_index)) {
+ error = GIT_EMERGECONFLICT;
+ goto cleanup;
+ }
+ }
+
+ /* Restore modified files in workdir */
+ if ((error = merge_index_and_tree(
+ &modified_index, repo, stash_parent_tree, repo_index, stash_tree)) < 0)
+ goto cleanup;
+
+ /* If applicable, restore untracked / ignored files in workdir */
+ if (untracked_tree &&
+ (error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0)
+ goto cleanup;
+
+ if (untracked_index) {
+ checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
+
+ if ((error = git_checkout_index(repo, untracked_index, &checkout_opts)) < 0)
+ goto cleanup;
+
+ checkout_opts.checkout_strategy = checkout_strategy;
+ }
+
+
+ /* If there are conflicts in the modified index, then we need to actually
+ * check that out as the repo's index. Otherwise, we don't update the
+ * index.
+ */
+
+ if (!git_index_has_conflicts(modified_index))
+ checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
+
+ /* Check out the modified index using the existing repo index as baseline,
+ * so that existing modifications in the index can be rewritten even when
+ * checking out safely.
+ */
+ checkout_opts.baseline_index = repo_index;
+
+ if ((error = git_checkout_index(repo, modified_index, &checkout_opts)) < 0)
+ goto cleanup;
+
+ if (unstashed_index && !git_index_has_conflicts(modified_index)) {
+ if ((error = git_index_read_index(repo_index, unstashed_index)) < 0)
+ goto cleanup;
+ }
+
+cleanup:
+ git_index_free(untracked_index);
+ git_index_free(modified_index);
+ git_index_free(unstashed_index);
+ git_index_free(repo_index);
+ git_tree_free(untracked_tree);
+ git_tree_free(index_parent_tree);
+ git_tree_free(index_tree);
+ git_tree_free(stash_parent_tree);
+ git_tree_free(stash_tree);
+ git_commit_free(stash_commit);
+ return error;
+}
+
int git_stash_foreach(
git_repository *repo,
git_stash_cb callback,
@@ -620,7 +867,7 @@ int git_stash_drop(
max = git_reflog_entrycount(reflog);
- if (index > max - 1) {
+ if (!max || index > max - 1) {
error = GIT_ENOTFOUND;
giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
goto cleanup;
@@ -651,3 +898,17 @@ cleanup:
git_reflog_free(reflog);
return error;
}
+
+int git_stash_pop(
+ git_repository *repo,
+ size_t index,
+ const git_checkout_options *checkout_options,
+ unsigned int flags)
+{
+ int error;
+
+ if ((error = git_stash_apply(repo, index, checkout_options, flags)) < 0)
+ return error;
+
+ return git_stash_drop(repo, index);
+}
diff --git a/tests/index/read_index.c b/tests/index/read_index.c
new file mode 100644
index 000000000..82a771d54
--- /dev/null
+++ b/tests/index/read_index.c
@@ -0,0 +1,73 @@
+#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);
+ }
+ }
+}
diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c
index eea651de0..b96c4c4db 100644
--- a/tests/merge/trees/treediff.c
+++ b/tests/merge/trees/treediff.c
@@ -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);
diff --git a/tests/stash/apply.c b/tests/stash/apply.c
new file mode 100644
index 000000000..74416cd95
--- /dev/null
+++ b/tests/stash/apply.c
@@ -0,0 +1,215 @@
+#include "clar_libgit2.h"
+#include "fileops.h"
+#include "stash_helpers.h"
+
+static git_signature *signature;
+static git_repository *repo;
+static git_index *repo_index;
+
+void test_stash_apply__initialize(void)
+{
+ git_oid oid;
+
+ cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
+
+ cl_git_pass(git_repository_init(&repo, "stash", 0));
+ cl_git_pass(git_repository_index(&repo_index, repo));
+
+ cl_git_mkfile("stash/what", "hello\n");
+ cl_git_mkfile("stash/how", "small\n");
+ cl_git_mkfile("stash/who", "world\n");
+
+ cl_git_pass(git_index_add_bypath(repo_index, "what"));
+ cl_git_pass(git_index_add_bypath(repo_index, "how"));
+ cl_git_pass(git_index_add_bypath(repo_index, "who"));
+
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit");
+
+ cl_git_rewritefile("stash/what", "goodbye\n");
+ cl_git_rewritefile("stash/who", "funky world\n");
+ cl_git_mkfile("stash/when", "tomorrow\n");
+
+ cl_git_pass(git_index_add_bypath(repo_index, "who"));
+
+ /* Pre-stash state */
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+
+ cl_git_pass(git_stash_save(&oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED));
+
+ /* Post-stash state */
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_ENOTFOUND);
+}
+
+void test_stash_apply__cleanup(void)
+{
+ git_signature_free(signature);
+ signature = NULL;
+
+ git_index_free(repo_index);
+ repo_index = NULL;
+
+ git_repository_free(repo);
+ repo = NULL;
+
+ cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES));
+ cl_fixture_cleanup("sorry-it-is-a-non-bare-only-party");
+}
+
+void test_stash_apply__with_default(void)
+{
+ cl_git_pass(git_stash_apply(repo, 0, NULL, GIT_APPLY_DEFAULT));
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__with_reinstate_index(void)
+{
+ cl_git_pass(git_stash_apply(repo, 0, NULL, GIT_APPLY_REINSTATE_INDEX));
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_index_with_default(void)
+{
+ const git_index_entry *ancestor;
+ const git_index_entry *our;
+ const git_index_entry *their;
+
+ cl_git_rewritefile("stash/who", "nothing\n");
+ cl_git_pass(git_index_add_bypath(repo_index, "who"));
+ cl_git_pass(git_index_write(repo_index));
+
+ cl_git_pass(git_stash_apply(repo, 0, NULL, GIT_APPLY_DEFAULT));
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 1);
+ assert_status(repo, "what", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "who")); /* unmerged */
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_index_with_reinstate_index(void)
+{
+ cl_git_rewritefile("stash/who", "nothing\n");
+ cl_git_pass(git_index_add_bypath(repo_index, "who"));
+ cl_git_pass(git_index_write(repo_index));
+
+ cl_git_fail_with(git_stash_apply(repo, 0, NULL, GIT_APPLY_REINSTATE_INDEX), GIT_EMERGECONFLICT);
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "when", GIT_ENOTFOUND);
+}
+
+void test_stash_apply__conflict_untracked_with_default(void)
+{
+ cl_git_mkfile("stash/when", "nothing\n");
+
+ cl_git_fail_with(git_stash_apply(repo, 0, NULL, GIT_APPLY_DEFAULT), GIT_EMERGECONFLICT);
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_untracked_with_reinstate_index(void)
+{
+ cl_git_mkfile("stash/when", "nothing\n");
+
+ cl_git_fail_with(git_stash_apply(repo, 0, NULL, GIT_APPLY_REINSTATE_INDEX), GIT_EMERGECONFLICT);
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_CURRENT);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_workdir_with_default(void)
+{
+ cl_git_rewritefile("stash/what", "ciao\n");
+
+ cl_git_fail_with(git_stash_apply(repo, 0, NULL, GIT_APPLY_DEFAULT), GIT_EMERGECONFLICT);
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_workdir_with_reinstate_index(void)
+{
+ cl_git_rewritefile("stash/what", "ciao\n");
+
+ cl_git_fail_with(git_stash_apply(repo, 0, NULL, GIT_APPLY_REINSTATE_INDEX), GIT_EMERGECONFLICT);
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 0);
+ assert_status(repo, "what", GIT_STATUS_WT_MODIFIED);
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_CURRENT);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_commit_with_default(void)
+{
+ const git_index_entry *ancestor;
+ const git_index_entry *our;
+ const git_index_entry *their;
+
+ cl_git_rewritefile("stash/what", "ciao\n");
+ cl_git_pass(git_index_add_bypath(repo_index, "what"));
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit");
+
+ cl_git_pass(git_stash_apply(repo, 0, NULL, GIT_APPLY_DEFAULT));
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 1);
+ cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__conflict_commit_with_reinstate_index(void)
+{
+ const git_index_entry *ancestor;
+ const git_index_entry *our;
+ const git_index_entry *their;
+
+ cl_git_rewritefile("stash/what", "ciao\n");
+ cl_git_pass(git_index_add_bypath(repo_index, "what"));
+ cl_repo_commit_from_index(NULL, repo, signature, 0, "Other commit");
+
+ cl_git_pass(git_stash_apply(repo, 0, NULL, GIT_APPLY_REINSTATE_INDEX));
+
+ cl_assert_equal_i(git_index_has_conflicts(repo_index), 1);
+ cl_git_pass(git_index_conflict_get(&ancestor, &our, &their, repo_index, "what")); /* unmerged */
+ assert_status(repo, "how", GIT_STATUS_CURRENT);
+ assert_status(repo, "who", GIT_STATUS_INDEX_MODIFIED);
+ assert_status(repo, "when", GIT_STATUS_WT_NEW);
+}
+
+void test_stash_apply__pop(void)
+{
+ cl_git_pass(git_stash_pop(repo, 0, NULL, GIT_APPLY_DEFAULT));
+
+ cl_git_fail_with(git_stash_pop(repo, 0, NULL, GIT_APPLY_DEFAULT), GIT_ENOTFOUND);
+}