Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f9589eb

Browse files
committed
Added git_stash_apply() and git_stash_pop() APIs
1 parent 65f6c1c commit f9589eb

4 files changed

Lines changed: 436 additions & 1 deletion

File tree

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Microsoft Corporation
4949
Olivier Ramonat
5050
Peter Drahoš
5151
Pierre Habouzit
52+
Pierre-Olivier Latour
5253
Przemyslaw Pawelczyk
5354
Ramsay Jones
5455
Robert G. Jakabosky

include/git2/stash.h

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,24 @@ GIT_EXTERN(int) git_stash_save(
6161
const char *message,
6262
unsigned int flags);
6363

64+
/**
65+
* Apply a single stashed state from the stash list.
66+
*
67+
* @param repo The owning repository.
68+
*
69+
* @param index The position within the stash list. 0 points to the
70+
* most recent stashed state.
71+
*
72+
* @param reinstate_index Try to reinstate not only the working tree's changes,
73+
* but also the index's ones.
74+
*
75+
* @return 0 on success, or error code
76+
*/
77+
GIT_EXTERN(int) git_stash_apply(
78+
git_repository *repo,
79+
size_t index,
80+
int reinstate_index);
81+
6482
/**
6583
* This is a callback function you can provide to iterate over all the
6684
* stashed states that will be invoked per entry.
@@ -107,11 +125,29 @@ GIT_EXTERN(int) git_stash_foreach(
107125
*
108126
* @return 0 on success, or error code
109127
*/
110-
111128
GIT_EXTERN(int) git_stash_drop(
112129
git_repository *repo,
113130
size_t index);
114131

132+
/**
133+
* Apply a single stashed state from the stash list and remove it from the list
134+
* if successful.
135+
*
136+
* @param repo The owning repository.
137+
*
138+
* @param index The position within the stash list. 0 points to the
139+
* most recent stashed state.
140+
*
141+
* @param reinstate_index Try to reinstate not only the working tree's changes,
142+
* but also the index's ones.
143+
*
144+
* @return 0 on success, or error code
145+
*/
146+
GIT_EXTERN(int) git_stash_pop(
147+
git_repository *repo,
148+
size_t index,
149+
int reinstate_index);
150+
115151
/** @} */
116152
GIT_END_DECL
117153
#endif

src/stash.c

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "git2/checkout.h"
1717
#include "git2/index.h"
1818
#include "git2/transaction.h"
19+
#include "git2/merge.h"
1920
#include "signature.h"
2021

2122
static int create_error(int error, const char *msg)
@@ -555,6 +556,308 @@ int git_stash_save(
555556
return error;
556557
}
557558

559+
static int retrieve_stash_commit(
560+
git_commit **commit,
561+
git_repository *repo,
562+
size_t index)
563+
{
564+
git_reference *stash = NULL;
565+
git_reflog *reflog = NULL;
566+
int error;
567+
size_t max;
568+
const git_reflog_entry *entry;
569+
570+
if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0)
571+
goto cleanup;
572+
573+
if ((error = git_reflog_read(&reflog, repo, GIT_REFS_STASH_FILE)) < 0)
574+
goto cleanup;
575+
576+
max = git_reflog_entrycount(reflog);
577+
if (index > max - 1) {
578+
error = GIT_ENOTFOUND;
579+
giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index);
580+
goto cleanup;
581+
}
582+
583+
entry = git_reflog_entry_byindex(reflog, index);
584+
if ((error = git_commit_lookup(commit, repo, git_reflog_entry_id_new(entry))) < 0)
585+
goto cleanup;
586+
587+
cleanup:
588+
git_reference_free(stash);
589+
git_reflog_free(reflog);
590+
return error;
591+
}
592+
593+
static int retrieve_stash_trees(
594+
git_tree **stash_tree,
595+
git_tree **base_tree,
596+
git_tree **index_tree,
597+
git_tree **index_parent_tree,
598+
git_tree **untracked_tree,
599+
git_commit *stash_commit)
600+
{
601+
git_commit *base_commit = NULL;
602+
git_commit *index_commit = NULL;
603+
git_commit *index_parent_commit = NULL;
604+
git_commit *untracked_commit = NULL;
605+
int error;
606+
607+
if ((error = git_commit_tree(stash_tree, stash_commit)) < 0)
608+
goto cleanup;
609+
610+
if ((error = git_commit_parent(&base_commit, stash_commit, 0)) < 0)
611+
goto cleanup;
612+
if ((error = git_commit_tree(base_tree, base_commit)) < 0)
613+
goto cleanup;
614+
615+
if ((error = git_commit_parent(&index_commit, stash_commit, 1)) < 0)
616+
goto cleanup;
617+
if ((error = git_commit_tree(index_tree, index_commit)) < 0)
618+
goto cleanup;
619+
620+
if ((error = git_commit_parent(&index_parent_commit, index_commit, 0)) < 0)
621+
goto cleanup;
622+
if ((error = git_commit_tree(index_parent_tree, index_parent_commit)) < 0)
623+
goto cleanup;
624+
625+
if (git_commit_parentcount(stash_commit) == 3) {
626+
if ((error = git_commit_parent(&untracked_commit, stash_commit, 2)) < 0)
627+
goto cleanup;
628+
if ((error = git_commit_tree(untracked_tree, untracked_commit)) < 0)
629+
goto cleanup;
630+
} else {
631+
*untracked_tree = NULL;
632+
}
633+
634+
cleanup:
635+
git_commit_free(untracked_commit);
636+
git_commit_free(index_parent_commit);
637+
git_commit_free(index_commit);
638+
git_commit_free(base_commit);
639+
return error;
640+
}
641+
642+
static int apply_index(
643+
git_tree **unstashed_tree,
644+
git_repository *repo,
645+
git_tree *start_index_tree,
646+
git_tree *index_parent_tree,
647+
git_tree *index_tree)
648+
{
649+
git_index* unstashed_index = NULL;
650+
git_merge_options options = GIT_MERGE_OPTIONS_INIT;
651+
int error;
652+
git_oid oid;
653+
654+
if ((error = git_merge_trees(
655+
&unstashed_index, repo, index_parent_tree,
656+
start_index_tree, index_tree, &options)) < 0)
657+
goto cleanup;
658+
659+
if ((error = git_index_write_tree_to(&oid, unstashed_index, repo)) < 0)
660+
goto cleanup;
661+
662+
if ((error = git_tree_lookup(unstashed_tree, repo, &oid)) < 0)
663+
goto cleanup;
664+
665+
cleanup:
666+
git_index_free(unstashed_index);
667+
return error;
668+
}
669+
670+
static int apply_untracked(
671+
git_repository *repo,
672+
git_tree *untracked_tree)
673+
{
674+
git_checkout_options options = GIT_CHECKOUT_OPTIONS_INIT;
675+
int run;
676+
size_t i, count;
677+
int error;
678+
679+
/*
680+
The untracked tree only contains the untracked / ignores files so checking
681+
it out would remove all other files in the workdir. Since git_checkout_tree()
682+
does not have a mode to leave removed files alone, we emulate it by checking
683+
out files from the untracked tree one by one.
684+
685+
A dry run is first needed to ensure we don't end up with only the first
686+
files checked out due to a conflict later on.
687+
*/
688+
689+
options.paths.count = 1;
690+
for (run = 0; run <= 1; ++run) {
691+
if (run) {
692+
options.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_DONT_UPDATE_INDEX;
693+
} else {
694+
options.checkout_strategy = GIT_CHECKOUT_NONE | GIT_CHECKOUT_DONT_UPDATE_INDEX;
695+
}
696+
for (i = 0, count = git_tree_entrycount(untracked_tree); i < count; ++i) {
697+
const git_tree_entry *entry = git_tree_entry_byindex(untracked_tree, i);
698+
699+
const char* name = git_tree_entry_name(entry);
700+
options.paths.strings = (char**)&name;
701+
if ((error = git_checkout_tree(
702+
repo, (git_object*)untracked_tree, &options)) < 0)
703+
return error;
704+
}
705+
}
706+
707+
return GIT_OK;
708+
}
709+
710+
static int apply_modified(
711+
git_repository *repo,
712+
git_tree *base_tree,
713+
git_tree *start_index_tree,
714+
git_tree *stash_tree,
715+
int update_index)
716+
{
717+
git_index *index = NULL;
718+
git_merge_options merge_options = GIT_MERGE_OPTIONS_INIT;
719+
git_checkout_options checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
720+
int error;
721+
722+
if ((error = git_merge_trees(
723+
&index, repo, base_tree,
724+
start_index_tree, stash_tree, &merge_options)) < 0)
725+
goto cleanup;
726+
727+
checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
728+
if (!update_index) {
729+
checkout_options.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
730+
}
731+
if ((error = git_checkout_index(repo, index, &checkout_options)) < 0)
732+
goto cleanup;
733+
734+
cleanup:
735+
git_index_free(index);
736+
return error;
737+
}
738+
739+
static int unstage_modified_files(
740+
git_repository *repo,
741+
git_index *repo_index,
742+
git_tree *unstashed_tree,
743+
git_tree *start_index_tree)
744+
{
745+
git_diff *diff = NULL;
746+
git_diff_options options = GIT_DIFF_OPTIONS_INIT;
747+
size_t i, count;
748+
int error;
749+
750+
if (unstashed_tree) {
751+
if ((error = git_index_read_tree(repo_index, unstashed_tree)) < 0)
752+
goto cleanup;
753+
} else {
754+
options.flags = GIT_DIFF_FORCE_BINARY;
755+
if ((error = git_diff_tree_to_index(&diff, repo, start_index_tree,
756+
repo_index, &options)) < 0)
757+
goto cleanup;
758+
759+
/*
760+
This behavior is not 100% similar to "git stash apply" as the latter uses
761+
"git-read-tree --reset {treeish}" which preserves the stat()s from the
762+
index instead of replacing them with the tree ones for identical files.
763+
*/
764+
765+
if ((error = git_index_read_tree(repo_index, start_index_tree)) < 0)
766+
goto cleanup;
767+
768+
for (i = 0, count = git_diff_num_deltas(diff); i < count; ++i) {
769+
const git_diff_delta* delta = git_diff_get_delta(diff, i);
770+
if (delta->status == GIT_DELTA_ADDED) {
771+
if ((error = git_index_add_bypath(
772+
repo_index, delta->new_file.path)) < 0)
773+
goto cleanup;
774+
}
775+
}
776+
}
777+
778+
cleanup:
779+
git_diff_free(diff);
780+
return error;
781+
}
782+
783+
int git_stash_apply(
784+
git_repository *repo,
785+
size_t index,
786+
int reinstate_index)
787+
{
788+
git_commit *stash_commit = NULL;
789+
git_tree *stash_tree = NULL;
790+
git_tree *base_tree = NULL;
791+
git_tree *index_tree = NULL;
792+
git_tree *index_parent_tree = NULL;
793+
git_tree *untracked_tree = NULL;
794+
git_index *repo_index = NULL;
795+
git_tree *start_index_tree = NULL;
796+
git_tree *unstashed_tree = NULL;
797+
int error;
798+
799+
/* Retrieve commit corresponding to the given stash */
800+
if ((error = retrieve_stash_commit(&stash_commit, repo, index)) < 0)
801+
goto cleanup;
802+
803+
/* Retrieve all trees in the stash */
804+
if ((error = retrieve_stash_trees(
805+
&stash_tree, &base_tree, &index_tree,
806+
&index_parent_tree, &untracked_tree, stash_commit)) < 0)
807+
goto cleanup;
808+
809+
/* Load repo index */
810+
if ((error = git_repository_index(&repo_index, repo)) < 0)
811+
goto cleanup;
812+
813+
/* Create tree from index */
814+
if ((error = build_tree_from_index(&start_index_tree, repo_index)) < 0)
815+
goto cleanup;
816+
817+
/* Restore index if required */
818+
if (reinstate_index &&
819+
git_oid_cmp(git_tree_id(base_tree), git_tree_id(index_tree)) &&
820+
git_oid_cmp(git_tree_id(start_index_tree), git_tree_id(index_tree))) {
821+
822+
if ((error = apply_index(
823+
&unstashed_tree, repo, start_index_tree,
824+
index_parent_tree, index_tree)) < 0)
825+
goto cleanup;
826+
}
827+
828+
/* If applicable, restore untracked / ignored files in workdir */
829+
if (untracked_tree) {
830+
if ((error = apply_untracked(repo, untracked_tree)) < 0)
831+
goto cleanup;
832+
}
833+
834+
/* Restore modified files in workdir */
835+
if ((error = apply_modified(
836+
repo, base_tree, start_index_tree, stash_tree, unstashed_tree ? 0 : 1)) < 0)
837+
goto cleanup;
838+
839+
/* Unstage modified files from index */
840+
if ((error = unstage_modified_files(
841+
repo, repo_index, unstashed_tree, start_index_tree)) < 0)
842+
goto cleanup;
843+
844+
/* Write updated index */
845+
if ((error = git_index_write(repo_index)) < 0)
846+
goto cleanup;
847+
848+
cleanup:
849+
git_tree_free(unstashed_tree);
850+
git_tree_free(start_index_tree);
851+
git_index_free(repo_index);
852+
git_tree_free(untracked_tree);
853+
git_tree_free(index_parent_tree);
854+
git_tree_free(index_tree);
855+
git_tree_free(base_tree);
856+
git_tree_free(stash_tree);
857+
git_commit_free(stash_commit);
858+
return error;
859+
}
860+
558861
int git_stash_foreach(
559862
git_repository *repo,
560863
git_stash_cb callback,
@@ -653,3 +956,16 @@ int git_stash_drop(
653956
git_reflog_free(reflog);
654957
return error;
655958
}
959+
960+
int git_stash_pop(
961+
git_repository *repo,
962+
size_t index,
963+
int reinstate_index)
964+
{
965+
int error;
966+
967+
if ((error = git_stash_apply(repo, index, reinstate_index)) < 0)
968+
return error;
969+
970+
return git_stash_drop(repo, index);
971+
}

0 commit comments

Comments
 (0)