From 8dc78a0f620c252ec37832bc8a75140346f0ad62 Mon Sep 17 00:00:00 2001 From: Jacob Watson Date: Tue, 21 Jun 2022 15:23:33 -0700 Subject: [PATCH 1/6] stash: implement partial stashing by path --- include/git2/stash.h | 66 ++++++++++++- src/libgit2/stash.c | 220 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 260 insertions(+), 26 deletions(-) diff --git a/include/git2/stash.h b/include/git2/stash.h index 32e6f95764c..3b89081953d 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -44,7 +44,12 @@ typedef enum { * All ignored files are also stashed and then cleaned up from * the working directory */ - GIT_STASH_INCLUDE_IGNORED = (1 << 2) + GIT_STASH_INCLUDE_IGNORED = (1 << 2), + + /** + * All changes in the index and working directory are left intact + */ + GIT_STASH_KEEP_ALL = (1 << 3) } git_stash_flags; /** @@ -71,6 +76,65 @@ GIT_EXTERN(int) git_stash_save( const char *message, uint32_t flags); +/** + * Stash save options structure + * + * Initialize with `GIT_STASH_SAVE_OPTIONS_INIT`. Alternatively, you can + * use `git_stash_save_options_init`. + * + */ +typedef struct git_stash_save_options { + unsigned int version; + + /** The identity of the person performing the stashing. */ + const git_signature *stasher; + + /** Optional description along with the stashed state. */ + const char *message; + + /** Flags to control the stashing process. (see GIT_STASH_* above) */ + uint32_t flags; + + /** Optional paths that control which files are stashed. */ + git_strarray paths; +} git_stash_save_options; + +#define GIT_STASH_SAVE_OPTIONS_VERSION 1 +#define GIT_STASH_SAVE_OPTIONS_INIT { \ + GIT_STASH_SAVE_OPTIONS_VERSION, \ + NULL, \ + NULL, \ + GIT_STASH_DEFAULT } + +/** + * Initialize git_stash_save_options structure + * + * Initializes a `git_stash_save_options` with default values. Equivalent to + * creating an instance with `GIT_STASH_SAVE_OPTIONS_INIT`. + * + * @param opts The `git_stash_save_options` struct to initialize. + * @param version The struct version; pass `GIT_STASH_SAVE_OPTIONS_VERSION`. + * @return Zero on success; -1 on failure. + */ +GIT_EXTERN(int) git_stash_save_options_init( + git_stash_save_options *opts, unsigned int version); + +/** + * Save the local modifications to a new stash, with options. + * + * @param out Object id of the commit containing the stashed state. + * This commit is also the target of the direct reference refs/stash. + * + * @param repo The owning repository. + * + * @param opts The stash options. + * + * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash, + * or error code. + */ +GIT_EXTERN(int) git_stash_save_with_opts( + git_oid *out, git_repository *repo, git_stash_save_options *opts); + /** Stash application flags. */ typedef enum { GIT_STASH_APPLY_DEFAULT = 0, diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c index 5fc01ac36dd..91ff89889ac 100644 --- a/src/libgit2/stash.c +++ b/src/libgit2/stash.c @@ -193,6 +193,30 @@ static int stash_to_index( return git_index_add(index, &entry); } +static int stash_update_index_from_paths( + git_repository *repo, + git_index *index, + git_strarray *paths) +{ + unsigned int status_flags; + size_t i, error = 0; + + for(i = 0; i < paths->count; i++) { + git_status_file(&status_flags, repo, paths->strings[i]); + + if (status_flags & (GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_DELETED)) { + if ((error = git_index_remove(index, paths->strings[i], 0)) < 0) + return error; + } + else { + if ((error = stash_to_index(repo, index, paths->strings[i])) < 0) + return error; + } + } + + return error; +} + static int stash_update_index_from_diff( git_repository *repo, git_index *index, @@ -388,24 +412,80 @@ static int build_workdir_tree( return error; } -static int commit_worktree( +static int build_stash_commit_from_tree( git_oid *w_commit_oid, git_repository *repo, const git_signature *stasher, const char *message, git_commit *i_commit, git_commit *b_commit, - git_commit *u_commit) + git_commit *u_commit, + git_tree *tree) { const git_commit *parents[] = { NULL, NULL, NULL }; - git_index *i_index = NULL, *r_index = NULL; - git_tree *w_tree = NULL; - int error = 0, ignorecase; parents[0] = b_commit; parents[1] = i_commit; parents[2] = u_commit; + return git_commit_create( + w_commit_oid, + repo, + NULL, + stasher, + stasher, + NULL, + message, + tree, + u_commit ? 3 : 2, + parents); +} + +static int build_stash_commit_from_index( + git_oid *w_commit_oid, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + git_commit *b_commit, + git_commit *u_commit, + git_index *index) +{ + git_tree *tree; + int error; + + if ((error = build_tree_from_index(&tree, repo, index)) < 0) + goto cleanup; + + error = build_stash_commit_from_tree( + w_commit_oid, + repo, + stasher, + message, + i_commit, + b_commit, + u_commit, + tree + ); + +cleanup: + git_tree_free(tree); + return error; +} + +static int commit_worktree( + git_oid *w_commit_oid, + git_repository *repo, + const git_signature *stasher, + const char *message, + git_commit *i_commit, + git_commit *b_commit, + git_commit *u_commit) +{ + git_index *i_index = NULL, *r_index = NULL; + git_tree *w_tree = NULL; + int error = 0, ignorecase; + if ((error = git_repository_index(&r_index, repo) < 0) || (error = git_index_new(&i_index)) < 0 || (error = git_index__fill(i_index, &r_index->entries) < 0) || @@ -417,17 +497,16 @@ static int commit_worktree( if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0) goto cleanup; - error = git_commit_create( + error = build_stash_commit_from_tree( w_commit_oid, repo, - NULL, - stasher, stasher, - NULL, message, - w_tree, - u_commit ? 3 : 2, - parents); + i_commit, + b_commit, + u_commit, + w_tree + ); cleanup: git_tree_free(w_tree); @@ -520,6 +599,50 @@ static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flag return error; } +static int has_changes_cb(const char *path, unsigned int status, void *payload) { + GIT_UNUSED(path); + GIT_UNUSED(status); + GIT_UNUSED(payload); + + if (status == GIT_STATUS_CURRENT) + return GIT_ENOTFOUND; + + return 0; +} + +static int ensure_there_are_changes_to_stash_paths( + git_repository *repo, + uint32_t flags, + git_strarray *paths) +{ + int error; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES + | GIT_STATUS_OPT_INCLUDE_UNMODIFIED + | GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + if (flags & GIT_STASH_INCLUDE_IGNORED) + opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + + git_strarray_copy(&opts.pathspec, paths); + + error = git_status_foreach_ext(repo, &opts, has_changes_cb, NULL); + + git_strarray_dispose(&opts.pathspec); + + if (error == GIT_ENOTFOUND) + return create_error(GIT_ENOTFOUND, "one of the files does not have any changes to stash."); + + return error; +} + static int reset_index_and_workdir(git_repository *repo, git_commit *commit, uint32_t flags) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; @@ -540,14 +663,25 @@ int git_stash_save( const char *message, uint32_t flags) { - git_index *index = NULL; + git_stash_save_options opts = GIT_STASH_SAVE_OPTIONS_INIT; + opts.stasher = stasher; + opts.message = message; + opts.flags = flags; + return git_stash_save_with_opts(out, repo, &opts); +} + +int git_stash_save_with_opts( + git_oid *out, git_repository *repo, git_stash_save_options *opts) +{ + git_index *index = NULL, *paths_index = NULL; git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; git_str msg = GIT_STR_INIT; + git_tree *tree = NULL; + git_reference *head = NULL; int error; GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(repo); - GIT_ASSERT_ARG(stasher); if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) return error; @@ -555,35 +689,61 @@ int git_stash_save( if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) goto cleanup; - if ((error = ensure_there_are_changes_to_stash(repo, flags)) < 0) + if (opts->paths.count == 0 && + (error = ensure_there_are_changes_to_stash(repo, opts->flags)) < 0) + goto cleanup; + else if (opts->paths.count > 0 && + (error = ensure_there_are_changes_to_stash_paths( + repo, opts->flags, &opts->paths)) < 0) goto cleanup; if ((error = git_repository_index(&index, repo)) < 0) goto cleanup; - if ((error = commit_index(&i_commit, repo, index, stasher, + if ((error = commit_index(&i_commit, repo, index, opts->stasher, git_str_cstr(&msg), b_commit)) < 0) goto cleanup; - if ((flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && - (error = commit_untracked(&u_commit, repo, stasher, - git_str_cstr(&msg), i_commit, flags)) < 0) + if ((opts->flags & (GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)) && + (error = commit_untracked(&u_commit, repo, opts->stasher, + git_str_cstr(&msg), i_commit, opts->flags)) < 0) goto cleanup; - if ((error = prepare_worktree_commit_message(&msg, message)) < 0) + if ((error = prepare_worktree_commit_message(&msg, opts->message)) < 0) goto cleanup; - if ((error = commit_worktree(out, repo, stasher, git_str_cstr(&msg), - i_commit, b_commit, u_commit)) < 0) - goto cleanup; + if (opts->paths.count == 0) { + if ((error = commit_worktree(out, repo, opts->stasher, git_str_cstr(&msg), + i_commit, b_commit, u_commit)) < 0) + goto cleanup; + } else { + if ((error = git_index_new(&paths_index)) < 0) + goto cleanup; + + if ((error = retrieve_head(&head, repo)) < 0) + goto cleanup; + + if ((error = git_reference_peel((git_object**)&tree, head, GIT_OBJECT_TREE)) < 0) + goto cleanup; + + if ((error = git_index_read_tree(paths_index, tree)) < 0) + goto cleanup; + + if ((error = stash_update_index_from_paths(repo, paths_index, &opts->paths)) < 0) + goto cleanup; + + if ((error = build_stash_commit_from_index(out, repo, opts->stasher, git_str_cstr(&msg), + i_commit, b_commit, u_commit, paths_index)) < 0) + goto cleanup; + } git_str_rtrim(&msg); if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) goto cleanup; - if ((error = reset_index_and_workdir(repo, (flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, - flags)) < 0) + if (!(opts->flags & GIT_STASH_KEEP_ALL) && (error = reset_index_and_workdir(repo, + (opts->flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, opts->flags)) < 0) goto cleanup; cleanup: @@ -592,7 +752,10 @@ int git_stash_save( git_commit_free(i_commit); git_commit_free(b_commit); git_commit_free(u_commit); + git_tree_free(tree); + git_reference_free(head); git_index_free(index); + git_index_free(paths_index); return error; } @@ -777,6 +940,13 @@ int git_stash_apply_options_init(git_stash_apply_options *opts, unsigned int ver return 0; } +int git_stash_save_options_init(git_stash_save_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_stash_save_options, GIT_STASH_SAVE_OPTIONS_INIT); + return 0; +} + #ifndef GIT_DEPRECATE_HARD int git_stash_apply_init_options(git_stash_apply_options *opts, unsigned int version) { From d91de9b070da4f5f1bab1007fef5d628a5c27066 Mon Sep 17 00:00:00 2001 From: Jacob Watson Date: Fri, 17 Jun 2022 11:17:04 -0700 Subject: [PATCH 2/6] stash: implement CI testing --- tests/libgit2/stash/save.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/libgit2/stash/save.c b/tests/libgit2/stash/save.c index f574211d794..23f3c1cbb41 100644 --- a/tests/libgit2/stash/save.c +++ b/tests/libgit2/stash/save.c @@ -130,6 +130,19 @@ void test_stash_save__can_keep_index(void) assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); } +void test_stash_save__can_keep_all(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_ALL)); + + assert_status(repo, "what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "how", GIT_STATUS_INDEX_MODIFIED); + assert_status(repo, "who", GIT_STATUS_WT_MODIFIED); + assert_status(repo, "when", GIT_STATUS_WT_NEW); + assert_status(repo, "why", GIT_STATUS_INDEX_NEW); + assert_status(repo, "where", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_NEW); + assert_status(repo, "just.ignore", GIT_STATUS_IGNORED); +} + static void assert_commit_message_contains(const char *revision, const char *fragment) { git_commit *commit; @@ -488,3 +501,27 @@ void test_stash_save__deleted_in_index_modified_in_workdir(void) git_index_free(index); } + +void test_stash_save__option_paths(void) +{ + git_stash_save_options options = GIT_STASH_SAVE_OPTIONS_INIT; + char *paths[2] = { "who", "where" }; + + options.paths = (git_strarray){ + paths, + 2 + }; + options.stasher = signature; + + cl_git_pass(git_stash_save_with_opts(&stash_tip_oid, repo, &options)); + + assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); + assert_blob_oid("refs/stash:where", "e3d6434ec12eb76af8dfa843a64ba6ab91014a0b"); + + assert_blob_oid("refs/stash:what", "ce013625030ba8dba906f756967f9e9ca394464a"); + assert_blob_oid("refs/stash:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); + assert_blob_oid("refs/stash:when", NULL); + assert_blob_oid("refs/stash:why", NULL); + assert_blob_oid("refs/stash:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980"); + assert_blob_oid("refs/stash:just.ignore", NULL); +} From d0155f1089113bb022aa614254b15a7e1ffcd4a2 Mon Sep 17 00:00:00 2001 From: Jacob Watson Date: Thu, 14 Jul 2022 10:23:18 -0700 Subject: [PATCH 3/6] stash: better option validation for stash save --- src/libgit2/stash.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c index 91ff89889ac..d801a04eeaf 100644 --- a/src/libgit2/stash.c +++ b/src/libgit2/stash.c @@ -664,6 +664,9 @@ int git_stash_save( uint32_t flags) { git_stash_save_options opts = GIT_STASH_SAVE_OPTIONS_INIT; + + GIT_ASSERT_ARG(stasher); + opts.stasher = stasher; opts.message = message; opts.flags = flags; @@ -678,10 +681,15 @@ int git_stash_save_with_opts( git_str msg = GIT_STR_INIT; git_tree *tree = NULL; git_reference *head = NULL; + bool has_paths = false; + int error; GIT_ASSERT_ARG(out); GIT_ASSERT_ARG(repo); + GIT_ASSERT_ARG(opts && opts->stasher); + + has_paths = opts->paths.count > 0; if ((error = git_repository__ensure_not_bare(repo, "stash save")) < 0) return error; @@ -689,10 +697,10 @@ int git_stash_save_with_opts( if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) goto cleanup; - if (opts->paths.count == 0 && + if (!has_paths && (error = ensure_there_are_changes_to_stash(repo, opts->flags)) < 0) goto cleanup; - else if (opts->paths.count > 0 && + else if (has_paths && (error = ensure_there_are_changes_to_stash_paths( repo, opts->flags, &opts->paths)) < 0) goto cleanup; @@ -712,7 +720,7 @@ int git_stash_save_with_opts( if ((error = prepare_worktree_commit_message(&msg, opts->message)) < 0) goto cleanup; - if (opts->paths.count == 0) { + if (!has_paths) { if ((error = commit_worktree(out, repo, opts->stasher, git_str_cstr(&msg), i_commit, b_commit, u_commit)) < 0) goto cleanup; @@ -753,9 +761,12 @@ int git_stash_save_with_opts( git_commit_free(b_commit); git_commit_free(u_commit); git_tree_free(tree); - git_reference_free(head); - git_index_free(index); - git_index_free(paths_index); + + if (has_paths) { + git_reference_free(head); + git_index_free(index); + git_index_free(paths_index); + } return error; } From fc9d28970a9fe970025f529c2108a6a3fef03555 Mon Sep 17 00:00:00 2001 From: Jacob Watson Date: Wed, 13 Jul 2022 15:58:52 -0700 Subject: [PATCH 4/6] stash: add `const` to arguments --- include/git2/stash.h | 4 +++- src/libgit2/stash.c | 8 ++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/git2/stash.h b/include/git2/stash.h index 3b89081953d..3d70b36c310 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -133,7 +133,9 @@ GIT_EXTERN(int) git_stash_save_options_init( * or error code. */ GIT_EXTERN(int) git_stash_save_with_opts( - git_oid *out, git_repository *repo, git_stash_save_options *opts); + git_oid *out, + git_repository *repo, + const git_stash_save_options *opts); /** Stash application flags. */ typedef enum { diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c index d801a04eeaf..af65aae3c4d 100644 --- a/src/libgit2/stash.c +++ b/src/libgit2/stash.c @@ -196,7 +196,7 @@ static int stash_to_index( static int stash_update_index_from_paths( git_repository *repo, git_index *index, - git_strarray *paths) + const git_strarray *paths) { unsigned int status_flags; size_t i, error = 0; @@ -420,7 +420,7 @@ static int build_stash_commit_from_tree( git_commit *i_commit, git_commit *b_commit, git_commit *u_commit, - git_tree *tree) + const git_tree *tree) { const git_commit *parents[] = { NULL, NULL, NULL }; @@ -613,7 +613,7 @@ static int has_changes_cb(const char *path, unsigned int status, void *payload) static int ensure_there_are_changes_to_stash_paths( git_repository *repo, uint32_t flags, - git_strarray *paths) + const git_strarray *paths) { int error; git_status_options opts = GIT_STATUS_OPTIONS_INIT; @@ -674,7 +674,7 @@ int git_stash_save( } int git_stash_save_with_opts( - git_oid *out, git_repository *repo, git_stash_save_options *opts) + git_oid *out, git_repository *repo, const git_stash_save_options *opts) { git_index *index = NULL, *paths_index = NULL; git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; From fb9515abc80c945dfe74713989dfb4f41d03d106 Mon Sep 17 00:00:00 2001 From: Jacob Watson Date: Wed, 13 Jul 2022 16:50:02 -0700 Subject: [PATCH 5/6] stash: test save options init --- tests/libgit2/core/structinit.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/libgit2/core/structinit.c b/tests/libgit2/core/structinit.c index 160e2f61218..8a6e48d2a9c 100644 --- a/tests/libgit2/core/structinit.c +++ b/tests/libgit2/core/structinit.c @@ -160,6 +160,11 @@ void test_core_structinit__compare(void) git_stash_apply_options, GIT_STASH_APPLY_OPTIONS_VERSION, \ GIT_STASH_APPLY_OPTIONS_INIT, git_stash_apply_options_init); + /* stash save */ + CHECK_MACRO_FUNC_INIT_EQUAL( \ + git_stash_save_options, GIT_STASH_SAVE_OPTIONS_VERSION, \ + GIT_STASH_SAVE_OPTIONS_INIT, git_stash_save_options_init); + /* status */ CHECK_MACRO_FUNC_INIT_EQUAL( \ git_status_options, GIT_STATUS_OPTIONS_VERSION, \ From 35580d88a834438275adea77785a5f356352ea21 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 16 Feb 2023 09:11:57 +0000 Subject: [PATCH 6/6] stash: fixes from code review --- include/git2/stash.h | 22 +++---------- src/libgit2/stash.c | 73 ++++++++++++++++++++------------------------ 2 files changed, 38 insertions(+), 57 deletions(-) diff --git a/include/git2/stash.h b/include/git2/stash.h index 3d70b36c310..dcfc013dc4e 100644 --- a/include/git2/stash.h +++ b/include/git2/stash.h @@ -57,15 +57,10 @@ typedef enum { * * @param out Object id of the commit containing the stashed state. * This commit is also the target of the direct reference refs/stash. - * * @param repo The owning repository. - * * @param stasher The identity of the person performing the stashing. - * * @param message Optional description along with the stashed state. - * * @param flags Flags to control the stashing process. (see GIT_STASH_* above) - * * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash, * or error code. */ @@ -86,25 +81,21 @@ GIT_EXTERN(int) git_stash_save( typedef struct git_stash_save_options { unsigned int version; + /** Flags to control the stashing process. (see GIT_STASH_* above) */ + uint32_t flags; + /** The identity of the person performing the stashing. */ const git_signature *stasher; /** Optional description along with the stashed state. */ const char *message; - /** Flags to control the stashing process. (see GIT_STASH_* above) */ - uint32_t flags; - /** Optional paths that control which files are stashed. */ git_strarray paths; } git_stash_save_options; #define GIT_STASH_SAVE_OPTIONS_VERSION 1 -#define GIT_STASH_SAVE_OPTIONS_INIT { \ - GIT_STASH_SAVE_OPTIONS_VERSION, \ - NULL, \ - NULL, \ - GIT_STASH_DEFAULT } +#define GIT_STASH_SAVE_OPTIONS_INIT { GIT_STASH_SAVE_OPTIONS_VERSION } /** * Initialize git_stash_save_options structure @@ -121,14 +112,11 @@ GIT_EXTERN(int) git_stash_save_options_init( /** * Save the local modifications to a new stash, with options. - * + * * @param out Object id of the commit containing the stashed state. * This commit is also the target of the direct reference refs/stash. - * * @param repo The owning repository. - * * @param opts The stash options. - * * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash, * or error code. */ diff --git a/src/libgit2/stash.c b/src/libgit2/stash.c index af65aae3c4d..80eaddecf77 100644 --- a/src/libgit2/stash.c +++ b/src/libgit2/stash.c @@ -199,16 +199,16 @@ static int stash_update_index_from_paths( const git_strarray *paths) { unsigned int status_flags; - size_t i, error = 0; + size_t i; + int error = 0; - for(i = 0; i < paths->count; i++) { + for (i = 0; i < paths->count; i++) { git_status_file(&status_flags, repo, paths->strings[i]); if (status_flags & (GIT_STATUS_WT_DELETED | GIT_STATUS_INDEX_DELETED)) { if ((error = git_index_remove(index, paths->strings[i], 0)) < 0) return error; - } - else { + } else { if ((error = stash_to_index(repo, index, paths->strings[i])) < 0) return error; } @@ -453,7 +453,7 @@ static int build_stash_commit_from_index( { git_tree *tree; int error; - + if ((error = build_tree_from_index(&tree, repo, index)) < 0) goto cleanup; @@ -465,8 +465,7 @@ static int build_stash_commit_from_index( i_commit, b_commit, u_commit, - tree - ); + tree); cleanup: git_tree_free(tree); @@ -599,7 +598,11 @@ static int ensure_there_are_changes_to_stash(git_repository *repo, uint32_t flag return error; } -static int has_changes_cb(const char *path, unsigned int status, void *payload) { +static int has_changes_cb( + const char *path, + unsigned int status, + void *payload) +{ GIT_UNUSED(path); GIT_UNUSED(status); GIT_UNUSED(payload); @@ -619,9 +622,9 @@ static int ensure_there_are_changes_to_stash_paths( git_status_options opts = GIT_STATUS_OPTIONS_INIT; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES - | GIT_STATUS_OPT_INCLUDE_UNMODIFIED - | GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.flags = GIT_STATUS_OPT_EXCLUDE_SUBMODULES | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; if (flags & GIT_STASH_INCLUDE_UNTRACKED) opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED | @@ -630,7 +633,7 @@ static int ensure_there_are_changes_to_stash_paths( if (flags & GIT_STASH_INCLUDE_IGNORED) opts.flags |= GIT_STATUS_OPT_INCLUDE_IGNORED | GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; - + git_strarray_copy(&opts.pathspec, paths); error = git_status_foreach_ext(repo, &opts, has_changes_cb, NULL); @@ -664,17 +667,20 @@ int git_stash_save( uint32_t flags) { git_stash_save_options opts = GIT_STASH_SAVE_OPTIONS_INIT; - + GIT_ASSERT_ARG(stasher); - + opts.stasher = stasher; opts.message = message; opts.flags = flags; + return git_stash_save_with_opts(out, repo, &opts); } int git_stash_save_with_opts( - git_oid *out, git_repository *repo, const git_stash_save_options *opts) + git_oid *out, + git_repository *repo, + const git_stash_save_options *opts) { git_index *index = NULL, *paths_index = NULL; git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; @@ -725,22 +731,12 @@ int git_stash_save_with_opts( i_commit, b_commit, u_commit)) < 0) goto cleanup; } else { - if ((error = git_index_new(&paths_index)) < 0) - goto cleanup; - - if ((error = retrieve_head(&head, repo)) < 0) - goto cleanup; - - if ((error = git_reference_peel((git_object**)&tree, head, GIT_OBJECT_TREE)) < 0) - goto cleanup; - - if ((error = git_index_read_tree(paths_index, tree)) < 0) - goto cleanup; - - if ((error = stash_update_index_from_paths(repo, paths_index, &opts->paths)) < 0) - goto cleanup; - - if ((error = build_stash_commit_from_index(out, repo, opts->stasher, git_str_cstr(&msg), + if ((error = git_index_new(&paths_index)) < 0 || + (error = retrieve_head(&head, repo)) < 0 || + (error = git_reference_peel((git_object**)&tree, head, GIT_OBJECT_TREE)) < 0 || + (error = git_index_read_tree(paths_index, tree)) < 0 || + (error = stash_update_index_from_paths(repo, paths_index, &opts->paths)) < 0 || + (error = build_stash_commit_from_index(out, repo, opts->stasher, git_str_cstr(&msg), i_commit, b_commit, u_commit, paths_index)) < 0) goto cleanup; } @@ -750,23 +746,20 @@ int git_stash_save_with_opts( if ((error = update_reflog(out, repo, git_str_cstr(&msg))) < 0) goto cleanup; - if (!(opts->flags & GIT_STASH_KEEP_ALL) && (error = reset_index_and_workdir(repo, - (opts->flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit, opts->flags)) < 0) + if (!(opts->flags & GIT_STASH_KEEP_ALL) && + (error = reset_index_and_workdir(repo, + (opts->flags & GIT_STASH_KEEP_INDEX) ? i_commit : b_commit,opts->flags)) < 0) goto cleanup; cleanup: - git_str_dispose(&msg); git_commit_free(i_commit); git_commit_free(b_commit); git_commit_free(u_commit); git_tree_free(tree); - - if (has_paths) { - git_reference_free(head); - git_index_free(index); - git_index_free(paths_index); - } + git_reference_free(head); + git_index_free(index); + git_index_free(paths_index); return error; }