Stash apply by ethomson · Pull Request #3018 · libgit2/libgit2 · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
11 changes: 10 additions & 1 deletion include/git2/checkout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */

Expand Down
139 changes: 135 additions & 4 deletions include/git2/stash.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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
19 changes: 14 additions & 5 deletions src/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));

Expand Down
98 changes: 98 additions & 0 deletions src/index.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/index.h
Loading