Skip to content

Commit 9919db8

Browse files
committed
stash: handle clean workspace on push()
1 parent 62ac00d commit 9919db8

File tree

7 files changed

+48
-28
lines changed

7 files changed

+48
-28
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ module = [
7272
"pygtrie",
7373
"funcy",
7474
"git",
75+
"gitdb.*",
7576
"fsspec.*",
7677
"pathspec.patterns",
7778
"asyncssh.*",

src/scmrepo/git/backend/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ def _stash_push(
261261
self,
262262
ref: str,
263263
message: Optional[str] = None,
264-
include_untracked: Optional[bool] = False,
264+
include_untracked: bool = False,
265265
) -> Tuple[Optional[str], bool]:
266266
"""Push a commit onto the specified stash.
267267

src/scmrepo/git/backend/dulwich/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,12 +672,18 @@ def _stash_push(
672672
self,
673673
ref: str,
674674
message: Optional[str] = None,
675-
include_untracked: Optional[bool] = False,
675+
include_untracked: bool = False,
676676
) -> Tuple[Optional[str], bool]:
677677
from dulwich.repo import InvalidUserIdentity
678678

679679
from scmrepo.git import Stash
680680

681+
# dulwich will silently generate an empty stash commit if there is
682+
# nothing to stash, we check status here to get consistent behavior
683+
# across backends
684+
if not self.is_dirty(untracked_files=include_untracked):
685+
return None, False
686+
681687
if include_untracked or ref == Stash.DEFAULT_STASH:
682688
# dulwich stash.push does not support include_untracked and does
683689
# not touch working tree

src/scmrepo/git/backend/gitpython.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,11 +359,12 @@ def resolve_commit(self, rev: str) -> "GitCommit":
359359
"""Return Commit object for the specified revision."""
360360
from git.exc import BadName, GitCommandError
361361
from git.objects.tag import TagObject
362+
from gitdb.exc import BadObject
362363

363364
try:
364365
commit = self.repo.rev_parse(rev)
365-
except (BadName, GitCommandError):
366-
raise SCMError(f"Invalid commit '{rev}'")
366+
except (BadName, BadObject, GitCommandError) as exc:
367+
raise SCMError(f"Invalid commit '{rev}'") from exc
367368
if isinstance(commit, TagObject):
368369
commit = commit.object
369370
return GitCommit(
@@ -503,10 +504,13 @@ def _stash_push(
503504
self,
504505
ref: str,
505506
message: Optional[str] = None,
506-
include_untracked: Optional[bool] = False,
507+
include_untracked: bool = False,
507508
) -> Tuple[Optional[str], bool]:
508509
from scmrepo.git import Stash
509510

511+
if not self.is_dirty(untracked_files=include_untracked):
512+
return None, False
513+
510514
args = ["push"]
511515
if message:
512516
args.extend(["-m", message])

src/scmrepo/git/backend/pygit2.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -534,15 +534,19 @@ def _stash_push(
534534
self,
535535
ref: str,
536536
message: Optional[str] = None,
537-
include_untracked: Optional[bool] = False,
537+
include_untracked: bool = False,
538538
) -> Tuple[Optional[str], bool]:
539539
from scmrepo.git import Stash
540540

541-
oid = self.repo.stash(
542-
self.default_signature,
543-
message=message,
544-
include_untracked=include_untracked,
545-
)
541+
try:
542+
oid = self.repo.stash(
543+
self.default_signature,
544+
message=message,
545+
include_untracked=include_untracked,
546+
)
547+
except KeyError:
548+
# GIT_ENOTFOUND, nothing to stash
549+
return None, False
546550
commit = self.repo[oid]
547551

548552
if ref != Stash.DEFAULT_STASH:

src/scmrepo/git/stash.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,13 @@ def list(self):
3232
def push(
3333
self, message: Optional[str] = None, include_untracked: bool = False
3434
) -> Optional[str]:
35-
if not self.scm.is_dirty(untracked_files=include_untracked):
36-
logger.debug("No changes to stash")
37-
return None
38-
3935
logger.debug("Stashing changes in '%s'", self.ref)
4036
rev, reset = self.scm._stash_push( # pylint: disable=protected-access
4137
self.ref, message=message, include_untracked=include_untracked
4238
)
39+
if not rev:
40+
logger.debug("No changes to stash")
41+
return None
4342
if reset:
4443
self.scm.reset(hard=True)
4544
return rev

tests/test_stash.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,13 @@ def test_git_stash_drop(tmp_dir: TmpDir, scm: Git, ref: Optional[str]):
8989
https://github.com/iterative/dvc/pull/5286#issuecomment-792574294"""
9090

9191

92-
@pytest.mark.parametrize(
93-
"ref",
94-
[
95-
pytest.param(
96-
None,
97-
marks=pytest.mark.xfail(
98-
sys.platform in ("linux", "win32"),
99-
raises=AssertionError,
100-
reason=reason,
101-
),
102-
),
103-
"refs/foo/stash",
104-
],
92+
@pytest.mark.xfail(
93+
sys.platform in ("linux", "win32"),
94+
raises=AssertionError,
95+
strict=False,
96+
reason=reason,
10597
)
98+
@pytest.mark.parametrize("ref", [None, "refs/foo/stash"])
10699
def test_git_stash_pop(tmp_dir: TmpDir, scm: Git, ref: Optional[str]):
107100
tmp_dir.gen({"file": "0"})
108101
scm.add_commit("file", message="init")
@@ -165,3 +158,16 @@ def test_git_stash_apply_index(
165158
assert dict(staged) == {"modify": ["file"]}
166159
assert not dict(unstaged)
167160
assert not dict(untracked)
161+
162+
163+
def test_git_stash_push_clean_workspace(
164+
tmp_dir: TmpDir,
165+
scm: Git,
166+
git: Git,
167+
):
168+
tmp_dir.gen("file", "0")
169+
scm.add_commit("file", message="init")
170+
assert git._stash_push("refs/stash") == ( # pylint: disable=protected-access
171+
None,
172+
False,
173+
)

0 commit comments

Comments
 (0)