mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-08 06:15:02 +00:00
commit
77a15fe8e1
1
AUTHORS
1
AUTHORS
@ -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));
|
||||
|
||||
|
98
src/index.c
98
src/index.c
@ -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;
|
||||
|
86
src/merge.c
86
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,
|
||||
|
18
src/merge.h
18
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);
|
||||
|
317
src/stash.c
317
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;
|
||||
@ -110,7 +106,7 @@ static int build_tree_from_index(git_tree **out, git_index *index)
|
||||
git_oid i_tree_oid;
|
||||
|
||||
if ((error = git_index_write_tree(&i_tree_oid, index)) < 0)
|
||||
return -1;
|
||||
return error;
|
||||
|
||||
return git_tree_lookup(out, git_index_owner(index), &i_tree_oid);
|
||||
}
|
||||
@ -553,6 +549,286 @@ 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_apply_options(
|
||||
git_stash_apply_options *opts,
|
||||
const git_stash_apply_options *given_apply_opts)
|
||||
{
|
||||
if (given_apply_opts != NULL) {
|
||||
memcpy(opts, given_apply_opts, sizeof(git_stash_apply_options));
|
||||
} else {
|
||||
git_stash_apply_options default_apply_opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
memcpy(opts, &default_apply_opts, sizeof(git_stash_apply_options));
|
||||
}
|
||||
|
||||
if ((opts->checkout_options.checkout_strategy & (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_FORCE)) == 0)
|
||||
opts->checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
|
||||
|
||||
if (!opts->checkout_options.our_label)
|
||||
opts->checkout_options.our_label = "Updated upstream";
|
||||
|
||||
if (!opts->checkout_options.their_label)
|
||||
opts->checkout_options.their_label = "Stashed changes";
|
||||
}
|
||||
|
||||
int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version)
|
||||
{
|
||||
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
|
||||
opts, version, git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_INIT);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define NOTIFY_PROGRESS(opts, progress_type) \
|
||||
if ((opts).progress_cb && \
|
||||
(error = (opts).progress_cb((progress_type), (opts).progress_payload))) \
|
||||
return (error < 0) ? error : -1;
|
||||
|
||||
int git_stash_apply(
|
||||
git_repository *repo,
|
||||
size_t index,
|
||||
const git_stash_apply_options *given_opts)
|
||||
{
|
||||
git_stash_apply_options 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;
|
||||
|
||||
GITERR_CHECK_VERSION(given_opts, GIT_STASH_APPLY_OPTIONS_VERSION, "git_stash_apply_options");
|
||||
|
||||
normalize_apply_options(&opts, given_opts);
|
||||
checkout_strategy = opts.checkout_options.checkout_strategy;
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_LOADING_STASH);
|
||||
|
||||
/* 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;
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX);
|
||||
|
||||
/* Restore index if required */
|
||||
if ((opts.flags & GIT_STASH_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;
|
||||
}
|
||||
}
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED);
|
||||
|
||||
/* 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) {
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED);
|
||||
|
||||
if ((error = merge_index_and_tree(&untracked_index, repo, NULL, repo_index, untracked_tree)) < 0)
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (untracked_index) {
|
||||
opts.checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED);
|
||||
|
||||
if ((error = git_checkout_index(repo, untracked_index, &opts.checkout_options)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
opts.checkout_options.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))
|
||||
opts.checkout_options.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.
|
||||
*/
|
||||
opts.checkout_options.baseline_index = repo_index;
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED);
|
||||
|
||||
if ((error = git_checkout_index(repo, modified_index, &opts.checkout_options)) < 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;
|
||||
}
|
||||
|
||||
NOTIFY_PROGRESS(opts, GIT_STASH_APPLY_PROGRESS_DONE);
|
||||
|
||||
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 +896,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 +927,16 @@ cleanup:
|
||||
git_reflog_free(reflog);
|
||||
return error;
|
||||
}
|
||||
|
||||
int git_stash_pop(
|
||||
git_repository *repo,
|
||||
size_t index,
|
||||
const git_stash_apply_options *options)
|
||||
{
|
||||
int error;
|
||||
|
||||
if ((error = git_stash_apply(repo, index, options)) < 0)
|
||||
return error;
|
||||
|
||||
return git_stash_drop(repo, 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, \
|
||||
|
73
tests/index/read_index.c
Normal file
73
tests/index/read_index.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
333
tests/stash/apply.c
Normal file
333
tests/stash/apply.c
Normal file
@ -0,0 +1,333 @@
|
||||
#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));
|
||||
|
||||
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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
|
||||
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
|
||||
|
||||
cl_git_pass(git_stash_apply(repo, 0, &opts));
|
||||
|
||||
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));
|
||||
|
||||
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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
|
||||
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
|
||||
|
||||
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, &opts), 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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
|
||||
cl_git_mkfile("stash/when", "nothing\n");
|
||||
|
||||
cl_git_fail_with(git_stash_apply(repo, 0, &opts), 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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
|
||||
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
|
||||
|
||||
cl_git_mkfile("stash/when", "nothing\n");
|
||||
|
||||
cl_git_fail_with(git_stash_apply(repo, 0, &opts), 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_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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
|
||||
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
|
||||
|
||||
cl_git_rewritefile("stash/what", "ciao\n");
|
||||
|
||||
cl_git_fail_with(git_stash_apply(repo, 0, &opts), 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));
|
||||
|
||||
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)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
const git_index_entry *ancestor;
|
||||
const git_index_entry *our;
|
||||
const git_index_entry *their;
|
||||
|
||||
opts.flags = GIT_STASH_APPLY_REINSTATE_INDEX;
|
||||
|
||||
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, &opts));
|
||||
|
||||
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));
|
||||
|
||||
cl_git_fail_with(git_stash_pop(repo, 0, NULL), GIT_ENOTFOUND);
|
||||
}
|
||||
|
||||
struct seen_paths {
|
||||
bool what;
|
||||
bool how;
|
||||
bool who;
|
||||
bool when;
|
||||
};
|
||||
|
||||
int checkout_notify(
|
||||
git_checkout_notify_t why,
|
||||
const char *path,
|
||||
const git_diff_file *baseline,
|
||||
const git_diff_file *target,
|
||||
const git_diff_file *workdir,
|
||||
void *payload)
|
||||
{
|
||||
struct seen_paths *seen_paths = (struct seen_paths *)payload;
|
||||
|
||||
if (strcmp(path, "what") == 0)
|
||||
seen_paths->what = 1;
|
||||
else if (strcmp(path, "how") == 0)
|
||||
seen_paths->how = 1;
|
||||
else if (strcmp(path, "who") == 0)
|
||||
seen_paths->who = 1;
|
||||
else if (strcmp(path, "when") == 0)
|
||||
seen_paths->when = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_stash_apply__executes_notify_cb(void)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
struct seen_paths seen_paths = {0};
|
||||
|
||||
opts.checkout_options.notify_cb = checkout_notify;
|
||||
opts.checkout_options.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
|
||||
opts.checkout_options.notify_payload = &seen_paths;
|
||||
|
||||
cl_git_pass(git_stash_apply(repo, 0, &opts));
|
||||
|
||||
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);
|
||||
|
||||
cl_assert_equal_b(true, seen_paths.what);
|
||||
cl_assert_equal_b(false, seen_paths.how);
|
||||
cl_assert_equal_b(true, seen_paths.who);
|
||||
cl_assert_equal_b(true, seen_paths.when);
|
||||
}
|
||||
|
||||
int progress_cb(
|
||||
git_stash_apply_progress_t progress,
|
||||
void *payload)
|
||||
{
|
||||
git_stash_apply_progress_t *p = (git_stash_apply_progress_t *)payload;
|
||||
|
||||
cl_assert_equal_i((*p)+1, progress);
|
||||
|
||||
*p = progress;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_stash_apply__calls_progress_cb(void)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE;
|
||||
|
||||
opts.progress_cb = progress_cb;
|
||||
opts.progress_payload = &progress;
|
||||
|
||||
cl_git_pass(git_stash_apply(repo, 0, &opts));
|
||||
cl_assert_equal_i(progress, GIT_STASH_APPLY_PROGRESS_DONE);
|
||||
}
|
||||
|
||||
int aborting_progress_cb(
|
||||
git_stash_apply_progress_t progress,
|
||||
void *payload)
|
||||
{
|
||||
if (progress == GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED)
|
||||
return -44;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_stash_apply__progress_cb_can_abort(void)
|
||||
{
|
||||
git_stash_apply_options opts = GIT_STASH_APPLY_OPTIONS_INIT;
|
||||
git_stash_apply_progress_t progress = GIT_STASH_APPLY_PROGRESS_NONE;
|
||||
|
||||
opts.progress_cb = aborting_progress_cb;
|
||||
|
||||
cl_git_fail_with(-44, git_stash_apply(repo, 0, &opts));
|
||||
}
|
Loading…
Reference in New Issue
Block a user