mirror of
https://github.com/Stone-Red-Code/dvc.git
synced 2026-11-10 15:28:34 +01:00
implement removing stage (#3881)
This should allow us to be able to remove a stage from a dvcfile, (if it's a single stage file, it removes it), and make us able to delete the overwrite the stage on run as well. This commit: * changes `run` `--overwrite-dvcfile` flag to `--overwrite` * removes prompt in `run` for overwrite and fails hard * changes `remove`: now it by default removes stage entry and unprotect outs * `remove` has `--outs` to remove outputs instead of unprotecting them
This commit is contained in:
+4
-41
@@ -1,7 +1,6 @@
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import dvc.prompt as prompt
|
||||
from dvc.command.base import CmdBase, append_doc_link
|
||||
from dvc.exceptions import DvcException
|
||||
|
||||
@@ -9,30 +8,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CmdRemove(CmdBase):
|
||||
def _is_dvc_only(self, target):
|
||||
if not self.args.purge:
|
||||
return True
|
||||
|
||||
if self.args.force:
|
||||
return False
|
||||
|
||||
msg = "Are you sure you want to remove '{}' with its outputs?".format(
|
||||
target
|
||||
)
|
||||
|
||||
if prompt.confirm(msg):
|
||||
return False
|
||||
|
||||
raise DvcException(
|
||||
"Cannot purge without a confirmation from the user."
|
||||
" Use `-f` to force."
|
||||
)
|
||||
|
||||
def run(self):
|
||||
for target in self.args.targets:
|
||||
try:
|
||||
dvc_only = self._is_dvc_only(target)
|
||||
self.repo.remove(target, dvc_only=dvc_only)
|
||||
self.repo.remove(target, outs=self.args.outs)
|
||||
except DvcException:
|
||||
logger.exception(f"failed to remove '{target}'")
|
||||
return 1
|
||||
@@ -40,7 +19,7 @@ class CmdRemove(CmdBase):
|
||||
|
||||
|
||||
def add_parser(subparsers, parent_parser):
|
||||
REMOVE_HELP = "Remove DVC-tracked files or directories."
|
||||
REMOVE_HELP = "Remove stage entry and unprotect outputs"
|
||||
remove_parser = subparsers.add_parser(
|
||||
"remove",
|
||||
parents=[parent_parser],
|
||||
@@ -48,27 +27,11 @@ def add_parser(subparsers, parent_parser):
|
||||
help=REMOVE_HELP,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
remove_parser_group = remove_parser.add_mutually_exclusive_group()
|
||||
remove_parser_group.add_argument(
|
||||
"-o",
|
||||
remove_parser.add_argument(
|
||||
"--outs",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Only remove DVC-file outputs. (Default)",
|
||||
)
|
||||
remove_parser_group.add_argument(
|
||||
"-p",
|
||||
"--purge",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Remove DVC-file and all its outputs.",
|
||||
)
|
||||
remove_parser.add_argument(
|
||||
"-f",
|
||||
"--force",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Force purge.",
|
||||
help="Remove outputs as well.",
|
||||
)
|
||||
remove_parser.add_argument(
|
||||
"targets", nargs="+", help="DVC-files to remove."
|
||||
|
||||
+3
-3
@@ -45,7 +45,7 @@ class CmdRun(CmdBase):
|
||||
fname=self.args.file,
|
||||
wdir=self.args.wdir,
|
||||
no_exec=self.args.no_exec,
|
||||
overwrite=self.args.overwrite_dvcfile,
|
||||
overwrite=self.args.overwrite,
|
||||
run_cache=not self.args.no_run_cache,
|
||||
no_commit=self.args.no_commit,
|
||||
outs_persist=self.args.outs_persist,
|
||||
@@ -177,10 +177,10 @@ def add_parser(subparsers, parent_parser):
|
||||
help="Only create stage file without actually running it.",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Overwrite existing DVC-file without asking for confirmation.",
|
||||
help="Overwrite existing stage",
|
||||
)
|
||||
run_parser.add_argument(
|
||||
"--no-run-cache",
|
||||
|
||||
+37
-2
@@ -150,7 +150,7 @@ class SingleStageFile(FileMixin):
|
||||
"Saving information to '{file}'.".format(file=relpath(self.path))
|
||||
)
|
||||
dump_stage_file(self.path, serialize.to_single_stage_file(stage))
|
||||
self.repo.scm.track_file(relpath(self.path))
|
||||
self.repo.scm.track_file(self.relpath)
|
||||
|
||||
def remove_with_prompt(self, force=False):
|
||||
if not self.exists():
|
||||
@@ -165,6 +165,9 @@ class SingleStageFile(FileMixin):
|
||||
|
||||
self.remove()
|
||||
|
||||
def remove_stage(self, stage):
|
||||
self.remove()
|
||||
|
||||
|
||||
class PipelineFile(FileMixin):
|
||||
"""Abstraction for pipelines file, .yaml + .lock combined."""
|
||||
@@ -212,7 +215,7 @@ class PipelineFile(FileMixin):
|
||||
"Adding stage '%s' to '%s'", stage.name, self.relpath,
|
||||
)
|
||||
dump_stage_file(self.path, data)
|
||||
self.repo.scm.track_file(relpath(self.path))
|
||||
self.repo.scm.track_file(self.relpath)
|
||||
|
||||
@property
|
||||
def stage(self):
|
||||
@@ -234,6 +237,22 @@ class PipelineFile(FileMixin):
|
||||
super().remove()
|
||||
self._lockfile.remove()
|
||||
|
||||
def remove_stage(self, stage):
|
||||
self._lockfile.remove_stage(stage)
|
||||
if not self.exists():
|
||||
return
|
||||
|
||||
with open(self.path, "r") as f:
|
||||
d = parse_stage_for_update(f.read(), self.path)
|
||||
|
||||
self.validate(d, self.path)
|
||||
if stage.name not in d.get("stages", {}):
|
||||
return
|
||||
|
||||
logger.debug("Removing '%s' from '%s'", stage.name, self.path)
|
||||
del d["stages"][stage.name]
|
||||
dump_stage_file(self.path, d)
|
||||
|
||||
|
||||
class Lockfile(FileMixin):
|
||||
from dvc.schema import COMPILED_LOCKFILE_SCHEMA as SCHEMA
|
||||
@@ -271,6 +290,22 @@ class Lockfile(FileMixin):
|
||||
if modified:
|
||||
self.repo.scm.track_file(self.relpath)
|
||||
|
||||
def remove_stage(self, stage):
|
||||
if not self.exists():
|
||||
return
|
||||
|
||||
with open(self.path) as f:
|
||||
d = parse_stage_for_update(f.read(), self.path)
|
||||
self.validate(d, self.path)
|
||||
|
||||
if stage.name not in d:
|
||||
return
|
||||
|
||||
logger.debug("Removing '%s' from '%s'", stage.name, self.path)
|
||||
del d[stage.name]
|
||||
|
||||
dump_stage_file(self.path, d)
|
||||
|
||||
|
||||
class Dvcfile:
|
||||
def __new__(cls, repo, path, **kwargs):
|
||||
|
||||
+3
-7
@@ -7,15 +7,11 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@locked
|
||||
def remove(self, target, dvc_only=False):
|
||||
from ..dvcfile import Dvcfile, is_valid_filename
|
||||
|
||||
def remove(self, target, outs=False):
|
||||
path, name = parse_target(target)
|
||||
stages = self.get_stages(path, name)
|
||||
for stage in stages:
|
||||
stage.remove_outs(force=True)
|
||||
|
||||
if path and is_valid_filename(path) and not dvc_only:
|
||||
Dvcfile(self, path).remove()
|
||||
for stage in stages:
|
||||
stage.remove(remove_outs=outs, force=outs)
|
||||
|
||||
return stages
|
||||
|
||||
+10
-4
@@ -3,7 +3,11 @@ import os
|
||||
from funcy import concat, first
|
||||
|
||||
from dvc.exceptions import InvalidArgumentError
|
||||
from dvc.stage.exceptions import DuplicateStageName, InvalidStageName
|
||||
from dvc.stage.exceptions import (
|
||||
DuplicateStageName,
|
||||
InvalidStageName,
|
||||
StageFileAlreadyExistsError,
|
||||
)
|
||||
|
||||
from ..exceptions import OutputDuplicationError
|
||||
from . import locked
|
||||
@@ -74,10 +78,12 @@ def run(self, fname=None, no_exec=False, single_stage=False, **kwargs):
|
||||
|
||||
dvcfile = Dvcfile(self, stage.path)
|
||||
if dvcfile.exists():
|
||||
if stage_name and stage_name in dvcfile.stages:
|
||||
if kwargs.get("overwrite", True):
|
||||
dvcfile.remove_stage(stage)
|
||||
elif stage_cls != PipelineStage:
|
||||
raise StageFileAlreadyExistsError(dvcfile.relpath)
|
||||
elif stage_name and stage_name in dvcfile.stages:
|
||||
raise DuplicateStageName(stage_name, dvcfile)
|
||||
if stage_cls != PipelineStage:
|
||||
dvcfile.remove_with_prompt(force=kwargs.get("overwrite", True))
|
||||
|
||||
try:
|
||||
self.check_modified_graph([stage])
|
||||
|
||||
@@ -282,12 +282,13 @@ class Stage(params.StageParams):
|
||||
out.unprotect()
|
||||
|
||||
@rwlocked(write=["outs"])
|
||||
def remove(self, force=False, remove_outs=True):
|
||||
def remove(self, force=False, remove_outs=True, purge=True):
|
||||
if remove_outs:
|
||||
self.remove_outs(ignore_remove=True, force=force)
|
||||
else:
|
||||
self.unprotect_outs()
|
||||
self.dvcfile.remove()
|
||||
if purge:
|
||||
self.dvcfile.remove_stage(self)
|
||||
|
||||
@rwlocked(read=["deps"], write=["outs"])
|
||||
def reproduce(self, interactive=False, **kwargs):
|
||||
|
||||
+132
-2
@@ -1,8 +1,14 @@
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
from dvc.dvcfile import PIPELINE_FILE, Dvcfile
|
||||
from dvc.stage.exceptions import StageFileDoesNotExistError
|
||||
from dvc.dvcfile import PIPELINE_FILE, PIPELINE_LOCK, Dvcfile
|
||||
from dvc.stage.exceptions import (
|
||||
StageFileDoesNotExistError,
|
||||
StageFileFormatError,
|
||||
)
|
||||
from dvc.stage.loader import StageNotFound
|
||||
from dvc.utils.stage import dump_stage_file
|
||||
|
||||
|
||||
def test_run_load_one_for_multistage(tmp_dir, dvc):
|
||||
@@ -160,3 +166,127 @@ def test_stage_collection(tmp_dir, dvc):
|
||||
single_stage=True,
|
||||
)
|
||||
assert {s for s in dvc.stages} == {stage1, stage3, stage2}
|
||||
|
||||
|
||||
def test_remove_stage(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", name="copy-foo-bar")
|
||||
stage2 = run_copy("bar", "foobar", name="copy-bar-foobar")
|
||||
|
||||
dvc_file = Dvcfile(dvc, PIPELINE_FILE)
|
||||
assert dvc_file.exists()
|
||||
assert {"copy-bar-foobar", "copy-foo-bar"} == set(
|
||||
dvc_file._load()[0]["stages"].keys()
|
||||
)
|
||||
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
assert ["copy-bar-foobar"] == list(dvc_file._load()[0]["stages"].keys())
|
||||
|
||||
# sanity check
|
||||
stage2.reload()
|
||||
|
||||
# re-check to see if it fails if there's no stage entry
|
||||
dvc_file.remove_stage(stage)
|
||||
dvc_file.remove(force=True)
|
||||
# should not fail when there's no file at all.
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
|
||||
def test_remove_stage_lockfile(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", name="copy-foo-bar")
|
||||
stage2 = run_copy("bar", "foobar", name="copy-bar-foobar")
|
||||
|
||||
dvc_file = Dvcfile(dvc, PIPELINE_FILE)
|
||||
lock_file = dvc_file._lockfile
|
||||
assert dvc_file.exists()
|
||||
assert lock_file.exists()
|
||||
assert {"copy-bar-foobar", "copy-foo-bar"} == set(lock_file.load().keys())
|
||||
lock_file.remove_stage(stage)
|
||||
|
||||
assert ["copy-bar-foobar"] == list(lock_file.load().keys())
|
||||
|
||||
# sanity check
|
||||
stage2.reload()
|
||||
|
||||
# re-check to see if it fails if there's no stage entry
|
||||
lock_file.remove_stage(stage)
|
||||
lock_file.remove()
|
||||
# should not fail when there's no file at all.
|
||||
lock_file.remove_stage(stage)
|
||||
|
||||
|
||||
def test_remove_stage_dvcfiles(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", single_stage=True)
|
||||
|
||||
dvc_file = Dvcfile(dvc, stage.path)
|
||||
assert dvc_file.exists()
|
||||
dvc_file.remove_stage(stage)
|
||||
assert not dvc_file.exists()
|
||||
|
||||
# re-check to see if it fails if there's no stage entry
|
||||
dvc_file.remove_stage(stage)
|
||||
dvc_file.remove(force=True)
|
||||
|
||||
# should not fail when there's no file at all.
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
|
||||
def test_remove_stage_on_lockfile_format_error(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", name="copy-foo-bar")
|
||||
dvc_file = Dvcfile(dvc, stage.path)
|
||||
lock_file = dvc_file._lockfile
|
||||
|
||||
data = dvc_file._load()[0]
|
||||
lock_data = lock_file.load()
|
||||
lock_data["gibberish"] = True
|
||||
data["gibberish"] = True
|
||||
dump_stage_file(lock_file.relpath, lock_data)
|
||||
with pytest.raises(StageFileFormatError):
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
lock_file.remove()
|
||||
dvc_file.dump(stage)
|
||||
|
||||
dump_stage_file(dvc_file.relpath, data)
|
||||
with pytest.raises(StageFileFormatError):
|
||||
dvc_file.remove_stage(stage)
|
||||
|
||||
|
||||
def test_remove_stage_preserves_comment(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen(
|
||||
"dvc.yaml",
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
stages:
|
||||
generate-foo:
|
||||
cmd: "echo foo > foo"
|
||||
# This copies 'foo' text to 'foo' file.
|
||||
outs:
|
||||
- foo
|
||||
copy-foo-bar:
|
||||
cmd: "python copy.py foo bar"
|
||||
deps:
|
||||
- foo
|
||||
outs:
|
||||
- bar"""
|
||||
),
|
||||
)
|
||||
|
||||
dvc.reproduce(PIPELINE_FILE)
|
||||
|
||||
dvc_file = Dvcfile(dvc, PIPELINE_FILE)
|
||||
|
||||
assert dvc_file.exists()
|
||||
assert (tmp_dir / PIPELINE_LOCK).exists()
|
||||
assert (tmp_dir / "foo").exists()
|
||||
assert (tmp_dir / "bar").exists()
|
||||
|
||||
dvc_file.remove_stage(dvc_file.stages["copy-foo-bar"])
|
||||
assert (
|
||||
"# This copies 'foo' text to 'foo' file."
|
||||
in (tmp_dir / PIPELINE_FILE).read_text()
|
||||
)
|
||||
|
||||
@@ -68,7 +68,7 @@ class TestGCBranchesTags(TestDvcGit):
|
||||
self.dvc.scm.tag("v1.0")
|
||||
|
||||
self.dvc.scm.checkout("test", create_new=True)
|
||||
self.dvc.remove(stages[0].relpath, dvc_only=True)
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+") as fobj:
|
||||
fobj.write("test")
|
||||
stages = self.dvc.add(fname)
|
||||
@@ -77,7 +77,7 @@ class TestGCBranchesTags(TestDvcGit):
|
||||
self.dvc.scm.commit("test")
|
||||
|
||||
self.dvc.scm.checkout("master")
|
||||
self.dvc.remove(stages[0].relpath, dvc_only=True)
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+") as fobj:
|
||||
fobj.write("trash")
|
||||
stages = self.dvc.add(fname)
|
||||
@@ -85,7 +85,7 @@ class TestGCBranchesTags(TestDvcGit):
|
||||
self.dvc.scm.add([".gitignore", stages[0].relpath])
|
||||
self.dvc.scm.commit("trash")
|
||||
|
||||
self.dvc.remove(stages[0].relpath, dvc_only=True)
|
||||
self.dvc.remove(stages[0].relpath)
|
||||
with open(fname, "w+") as fobj:
|
||||
fobj.write("master")
|
||||
stages = self.dvc.add(fname)
|
||||
|
||||
+42
-107
@@ -1,129 +1,64 @@
|
||||
import os
|
||||
|
||||
from mock import patch
|
||||
import pytest
|
||||
|
||||
from dvc.exceptions import DvcException
|
||||
from dvc.main import main
|
||||
from dvc.stage import Stage
|
||||
from dvc.stage.exceptions import StageFileDoesNotExistError
|
||||
from dvc.system import System
|
||||
from dvc.utils.fs import remove
|
||||
from tests.basic_env import TestDvc
|
||||
|
||||
|
||||
class TestRemove(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.FOO)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertTrue(stage is not None)
|
||||
(stage_removed,) = self.dvc.remove(stage.path, dvc_only=True)
|
||||
@pytest.mark.parametrize("remove_outs", [True, False])
|
||||
def test_remove(tmp_dir, dvc, run_copy, remove_outs):
|
||||
(stage1,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
stage2 = run_copy("foo", "bar", single_stage=True)
|
||||
stage3 = run_copy("bar", "foobar", name="copy-bar-foobar")
|
||||
|
||||
self.assertIsInstance(stage_removed, Stage)
|
||||
self.assertEqual(stage.path, stage_removed.path)
|
||||
self.assertFalse(os.path.isfile(self.FOO))
|
||||
|
||||
(stage_removed,) = self.dvc.remove(stage.path)
|
||||
self.assertIsInstance(stage_removed, Stage)
|
||||
self.assertEqual(stage.path, stage_removed.path)
|
||||
self.assertFalse(os.path.isfile(self.FOO))
|
||||
self.assertFalse(os.path.exists(stage.path))
|
||||
for stage in [stage1, stage2, stage3]:
|
||||
dvc.remove(stage.addressing, outs=remove_outs)
|
||||
out_exists = (out.exists for out in stage.outs)
|
||||
assert stage not in dvc._collect_stages()
|
||||
if remove_outs:
|
||||
assert not any(out_exists)
|
||||
else:
|
||||
assert all(out_exists)
|
||||
|
||||
|
||||
class TestRemoveNonExistentFile(TestDvc):
|
||||
def test(self):
|
||||
with self.assertRaises(StageFileDoesNotExistError):
|
||||
self.dvc.remove("non_existent_dvc_file")
|
||||
def test_remove_non_existent_file(tmp_dir, dvc):
|
||||
with pytest.raises(StageFileDoesNotExistError):
|
||||
dvc.remove("non_existent_dvc_file.dvc")
|
||||
with pytest.raises(StageFileDoesNotExistError):
|
||||
dvc.remove("non_existent_stage_name")
|
||||
|
||||
|
||||
class TestRemoveBrokenSymlink(TestDvc):
|
||||
def test(self):
|
||||
ret = main(["config", "cache.type", "symlink"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["add", self.FOO])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
remove(self.dvc.cache.local.cache_dir)
|
||||
|
||||
self.assertTrue(System.is_symlink(self.FOO))
|
||||
|
||||
ret = main(["remove", self.FOO + ".dvc"])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
self.assertFalse(os.path.lexists(self.FOO))
|
||||
|
||||
|
||||
class TestRemoveDirectory(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.DATA_DIR)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage_add = stages[0]
|
||||
self.assertTrue(stage_add is not None)
|
||||
(stage_removed,) = self.dvc.remove(stage_add.path)
|
||||
self.assertEqual(stage_add.path, stage_removed.path)
|
||||
self.assertFalse(os.path.exists(self.DATA_DIR))
|
||||
self.assertFalse(os.path.exists(stage_removed.path))
|
||||
|
||||
|
||||
class TestCmdRemove(TestDvc):
|
||||
def test(self):
|
||||
stages = self.dvc.add(self.FOO)
|
||||
self.assertEqual(len(stages), 1)
|
||||
stage = stages[0]
|
||||
self.assertTrue(stage is not None)
|
||||
ret = main(["remove", stage.path])
|
||||
self.assertEqual(ret, 0)
|
||||
|
||||
ret = main(["remove", "non-existing-dvc-file"])
|
||||
self.assertNotEqual(ret, 0)
|
||||
|
||||
|
||||
class TestRemovePurge(TestDvc):
|
||||
def test(self):
|
||||
dvcfile = self.dvc.add(self.FOO)[0].path
|
||||
ret = main(["remove", "--purge", "--force", dvcfile])
|
||||
|
||||
self.assertEqual(ret, 0)
|
||||
self.assertFalse(os.path.exists(self.FOO))
|
||||
self.assertFalse(os.path.exists(dvcfile))
|
||||
|
||||
@patch("dvc.prompt.confirm", return_value=False)
|
||||
def test_force(self, mock_prompt):
|
||||
dvcfile = self.dvc.add(self.FOO)[0].path
|
||||
ret = main(["remove", "--purge", dvcfile])
|
||||
|
||||
mock_prompt.assert_called()
|
||||
self.assertEqual(ret, 1)
|
||||
self.assertRaises(DvcException)
|
||||
|
||||
|
||||
def test_remove_pipeline_outs(tmp_dir, dvc, run_copy):
|
||||
from dvc.dvcfile import PIPELINE_FILE
|
||||
|
||||
def test_remove_broken_symlink(tmp_dir, dvc):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", name="copy-foo-bar")
|
||||
dvc.cache.local.cache_types = ["symlink"]
|
||||
|
||||
assert main(["remove", stage.path]) == 0
|
||||
assert not (tmp_dir / "bar").exists()
|
||||
assert (tmp_dir / PIPELINE_FILE).exists()
|
||||
(stage,) = dvc.add("foo")
|
||||
remove(dvc.cache.local.cache_dir)
|
||||
assert System.is_symlink("foo")
|
||||
|
||||
stage = run_copy("foo", "foobar", name="copy-foo-foobar")
|
||||
with pytest.raises(DvcException):
|
||||
dvc.remove(stage.addressing)
|
||||
assert os.path.lexists("foo")
|
||||
assert (tmp_dir / stage.relpath).exists()
|
||||
|
||||
dvc.remove(stage.addressing, outs=True)
|
||||
assert not os.path.lexists("foo")
|
||||
assert not (tmp_dir / stage.relpath).exists()
|
||||
|
||||
|
||||
def test_cmd_remove(tmp_dir, dvc):
|
||||
assert main(["remove", "non-existing-dvc-file"]) == 1
|
||||
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
assert main(["remove", stage.addressing]) == 0
|
||||
assert not (tmp_dir / "foobar").exists()
|
||||
assert not (tmp_dir / stage.relpath).exists()
|
||||
assert (tmp_dir / "foo").exists()
|
||||
|
||||
stage = run_copy("foo", "baz", name="copy-foo-baz")
|
||||
assert main(["remove", "--purge", "-f", stage.addressing]) == 0
|
||||
assert not (tmp_dir / "baz").exists()
|
||||
assert (tmp_dir / "foo").exists()
|
||||
assert (tmp_dir / PIPELINE_FILE).exists()
|
||||
|
||||
dvc.reproduce(PIPELINE_FILE)
|
||||
assert main(["remove", PIPELINE_FILE]) == 0
|
||||
for file in ["bar", "foobar", "baz"]:
|
||||
assert not (tmp_dir / file).exists()
|
||||
|
||||
assert (tmp_dir / "foo").exists()
|
||||
assert (tmp_dir / PIPELINE_FILE).exists()
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
assert main(["remove", stage.addressing, "--outs"]) == 0
|
||||
assert not (tmp_dir / stage.relpath).exists()
|
||||
assert not (tmp_dir / "foo").exists()
|
||||
|
||||
@@ -983,7 +983,8 @@ class TestReproExternalBase(SingleStageRun, TestDvc):
|
||||
self.dvc.gc(workspace=True)
|
||||
self.assertEqual(self.dvc.status(), {})
|
||||
|
||||
self.dvc.remove(cmd_stage.path, dvc_only=True)
|
||||
with self.dvc.lock:
|
||||
cmd_stage.remove_outs(force=True)
|
||||
self.assertNotEqual(self.dvc.status([cmd_stage.addressing]), {})
|
||||
|
||||
self.dvc.checkout([cmd_stage.path], force=True)
|
||||
@@ -1203,12 +1204,13 @@ class TestReproExternalHTTP(TestReproExternalBase):
|
||||
self.assertTrue(os.path.exists(import_output))
|
||||
self.assertTrue(filecmp.cmp(import_output, self.FOO, shallow=False))
|
||||
|
||||
self.dvc.remove("imported_file.dvc")
|
||||
self.dvc.remove("imported_file.dvc", outs=True)
|
||||
|
||||
with StaticFileServer(handler_class=ContentMD5Handler) as httpd:
|
||||
import_url = urljoin(self.get_remote(httpd.server_port), self.FOO)
|
||||
import_output = "imported_file"
|
||||
import_stage = self.dvc.imp_url(import_url, import_output)
|
||||
assert import_stage.repo == self.dvc
|
||||
|
||||
self.assertTrue(os.path.exists(import_output))
|
||||
self.assertTrue(filecmp.cmp(import_output, self.FOO, shallow=False))
|
||||
@@ -1225,7 +1227,7 @@ class TestReproExternalHTTP(TestReproExternalBase):
|
||||
self.assertEqual(ret1, 0)
|
||||
self.assertEqual(ret2, 0)
|
||||
|
||||
self.dvc = DvcRepo(".")
|
||||
self.dvc = import_stage.repo = DvcRepo(".")
|
||||
|
||||
run_dependency = urljoin(remote, self.BAR)
|
||||
run_output = "remote_file"
|
||||
@@ -1245,7 +1247,10 @@ class TestReproExternalHTTP(TestReproExternalBase):
|
||||
self.assertTrue(os.path.exists(run_output))
|
||||
|
||||
# Pull
|
||||
self.dvc.remove(import_stage.path, dvc_only=True)
|
||||
with self.dvc.lock:
|
||||
self.assertEqual(import_stage.repo.lock.is_locked, True)
|
||||
self.assertEqual(self.dvc.lock.is_locked, True)
|
||||
import_stage.remove_outs(force=True)
|
||||
self.assertFalse(os.path.exists(import_output))
|
||||
|
||||
shutil.move(self.local_cache, cache_id)
|
||||
|
||||
@@ -218,7 +218,8 @@ def test_run_already_exists(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.dvc_gen("foo", "foo")
|
||||
run_copy("foo", "bar", name="copy")
|
||||
with pytest.raises(DuplicateStageName):
|
||||
run_copy("bar", "foobar", name="copy")
|
||||
run_copy("bar", "foobar", name="copy", overwrite=False)
|
||||
run_copy("bar", "foobar", name="copy", overwrite=True)
|
||||
|
||||
|
||||
supported_params = {
|
||||
|
||||
@@ -350,7 +350,7 @@ class TestRunUnprotectOutsCopy(TestDvc):
|
||||
ret = main(
|
||||
[
|
||||
"run",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--no-run-cache",
|
||||
"--single-stage",
|
||||
"-d",
|
||||
@@ -408,7 +408,7 @@ class TestRunUnprotectOutsSymlink(TestDvc):
|
||||
ret = main(
|
||||
[
|
||||
"run",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--no-run-cache",
|
||||
"--single-stage",
|
||||
"-d",
|
||||
@@ -467,7 +467,7 @@ class TestRunUnprotectOutsHardlink(TestDvc):
|
||||
ret = main(
|
||||
[
|
||||
"run",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--no-run-cache",
|
||||
"--single-stage",
|
||||
"-d",
|
||||
@@ -552,7 +552,7 @@ class TestCmdRunOverwrite(TestDvc):
|
||||
self.FOO,
|
||||
"-d",
|
||||
self.CODE,
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--no-run-cache",
|
||||
"--single-stage",
|
||||
"-o",
|
||||
@@ -576,7 +576,7 @@ class TestCmdRunOverwrite(TestDvc):
|
||||
ret = main(
|
||||
[
|
||||
"run",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--single-stage",
|
||||
"-f",
|
||||
"out.dvc",
|
||||
@@ -676,19 +676,19 @@ def test_rerun_deterministic_ignore_cache(tmp_dir, run_copy):
|
||||
|
||||
|
||||
def test_rerun_callback(dvc):
|
||||
def run_callback():
|
||||
def run_callback(overwrite=False):
|
||||
return dvc.run(
|
||||
cmd=("echo content > out"),
|
||||
cmd="echo content > out",
|
||||
outs=["out"],
|
||||
deps=[],
|
||||
overwrite=False,
|
||||
overwrite=overwrite,
|
||||
single_stage=True,
|
||||
)
|
||||
|
||||
assert run_callback() is not None
|
||||
|
||||
with mock.patch("dvc.prompt.confirm", return_value=True):
|
||||
with pytest.raises(StageFileAlreadyExistsError):
|
||||
assert run_callback() is not None
|
||||
assert run_callback(overwrite=True) is not None
|
||||
|
||||
|
||||
def test_rerun_changed_dep(tmp_dir, run_copy):
|
||||
@@ -698,6 +698,7 @@ def test_rerun_changed_dep(tmp_dir, run_copy):
|
||||
tmp_dir.gen("foo", "changed content")
|
||||
with pytest.raises(StageFileAlreadyExistsError):
|
||||
run_copy("foo", "out", overwrite=False, single_stage=True)
|
||||
assert run_copy("foo", "out", overwrite=True, single_stage=True)
|
||||
|
||||
|
||||
def test_rerun_changed_stage(tmp_dir, run_copy):
|
||||
@@ -788,7 +789,7 @@ class TestRunPersist(TestDvc):
|
||||
self.assertEqual(2, len(lines))
|
||||
|
||||
def should_remove_persistent_outs(self, file, stage_file):
|
||||
ret = main(["remove", stage_file])
|
||||
ret = main(["remove", stage_file, "--outs"])
|
||||
self.assertEqual(0, ret)
|
||||
|
||||
self.assertFalse(os.path.exists(file))
|
||||
@@ -869,7 +870,7 @@ class TestRerunWithSameOutputs(TestDvc):
|
||||
"run",
|
||||
self._outs_command,
|
||||
self.FOO,
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--single-stage",
|
||||
f"echo {self.BAR_CONTENTS} >> {self.FOO}",
|
||||
]
|
||||
@@ -945,7 +946,7 @@ class TestPersistentOutput(TestDvc):
|
||||
|
||||
cmd = [
|
||||
"run",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--single-stage",
|
||||
"--deps",
|
||||
"immutable",
|
||||
|
||||
@@ -241,3 +241,32 @@ def test_stage_on_no_path_string_repr(tmp_dir, dvc):
|
||||
assert p.addressing == "No path:stage_name"
|
||||
assert repr(p) == "Stage: 'No path:stage_name'"
|
||||
assert str(p) == "stage: 'No path:stage_name'"
|
||||
|
||||
|
||||
def test_stage_remove_pipeline_stage(tmp_dir, dvc, run_copy):
|
||||
tmp_dir.gen("foo", "foo")
|
||||
stage = run_copy("foo", "bar", name="copy-foo-bar")
|
||||
run_copy("bar", "foobar", name="copy-bar-foobar")
|
||||
|
||||
dvc_file = stage.dvcfile
|
||||
with dvc.lock:
|
||||
stage.remove(purge=False)
|
||||
assert stage.name in dvc_file.stages
|
||||
|
||||
with dvc.lock:
|
||||
stage.remove()
|
||||
assert stage.name not in dvc_file.stages
|
||||
assert "copy-bar-foobar" in dvc_file.stages
|
||||
|
||||
|
||||
def test_stage_remove_pointer_stage(tmp_dir, dvc, run_copy):
|
||||
(stage,) = tmp_dir.dvc_gen("foo", "foo")
|
||||
|
||||
with dvc.lock:
|
||||
stage.remove(purge=False)
|
||||
assert not (tmp_dir / "foo").exists()
|
||||
assert (tmp_dir / stage.relpath).exists()
|
||||
|
||||
with dvc.lock:
|
||||
stage.remove()
|
||||
assert not (tmp_dir / stage.relpath).exists()
|
||||
|
||||
@@ -27,7 +27,7 @@ def test_run(mocker, dvc):
|
||||
"--wdir",
|
||||
"wdir",
|
||||
"--no-exec",
|
||||
"--overwrite-dvcfile",
|
||||
"--overwrite",
|
||||
"--no-run-cache",
|
||||
"--no-commit",
|
||||
"--outs-persist",
|
||||
|
||||
Reference in New Issue
Block a user