Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
G
git2
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
lvzhengyang
git2
Commits
05ba3fe4
Unverified
Commit
05ba3fe4
authored
Feb 16, 2023
by
Edward Thomson
Committed by
GitHub
Feb 16, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6330 from gitkraken-jacobw/partial-stashing
stash: partial stash specific files
parents
7108b431
35580d88
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
302 additions
and
32 deletions
+302
-32
include/git2/stash.h
+60
-6
src/libgit2/stash.c
+200
-26
tests/libgit2/core/structinit.c
+5
-0
tests/libgit2/stash/save.c
+37
-0
No files found.
include/git2/stash.h
View file @
05ba3fe4
...
...
@@ -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
;
/**
...
...
@@ -52,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.
*/
...
...
@@ -71,6 +71,60 @@ 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
;
/** 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
;
/** 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 }
/**
* 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
,
const
git_stash_save_options
*
opts
);
/** Stash application flags. */
typedef
enum
{
GIT_STASH_APPLY_DEFAULT
=
0
,
...
...
src/libgit2/stash.c
View file @
05ba3fe4
...
...
@@ -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
,
const
git_strarray
*
paths
)
{
unsigned
int
status_flags
;
size_t
i
;
int
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,79 @@ cleanup:
return
error
;
}
static
int
commit_work
tree
(
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
,
const
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 +496,16 @@ static int commit_worktree(
if
((
error
=
build_workdir_tree
(
&
w_tree
,
repo
,
i_index
,
b_commit
))
<
0
)
goto
cleanup
;
error
=
git_commit_creat
e
(
error
=
build_stash_commit_from_tre
e
(
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 +598,54 @@ 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
,
const
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 +666,36 @@ int git_stash_save(
const
char
*
message
,
uint32_t
flags
)
{
git_index
*
index
=
NULL
;
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_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
;
bool
has_paths
=
false
;
int
error
;
GIT_ASSERT_ARG
(
out
);
GIT_ASSERT_ARG
(
repo
);
GIT_ASSERT_ARG
(
stasher
);
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
;
...
...
@@ -555,44 +703,63 @@ 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
(
!
has_paths
&&
(
error
=
ensure_there_are_changes_to_stash
(
repo
,
opts
->
flags
))
<
0
)
goto
cleanup
;
else
if
(
has_paths
&&
(
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
(
!
has_paths
)
{
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
||
(
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
;
}
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:
git_str_dispose
(
&
msg
);
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 +944,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
)
{
...
...
tests/libgit2/core/structinit.c
View file @
05ba3fe4
...
...
@@ -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
,
\
...
...
tests/libgit2/stash/save.c
View file @
05ba3fe4
...
...
@@ -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
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment