Merge pull request #805 from efiop/master

dvc: introduce `dvc move`
This commit is contained in:
Ruslan Kuprieiev
2018-06-24 23:48:05 +03:00
committed by GitHub
8 changed files with 123 additions and 1 deletions
+14
View File
@@ -8,6 +8,7 @@ from multiprocessing import cpu_count
from dvc.logger import Logger
from dvc.command.init import CmdInit
from dvc.command.remove import CmdRemove
from dvc.command.move import CmdMove
from dvc.command.run import CmdRun
from dvc.command.repro import CmdRepro
from dvc.command.data_sync import CmdDataPush, CmdDataPull, CmdDataFetch
@@ -289,6 +290,19 @@ def parse_args(argv=None):
help='DVC files.')
remove_parser.set_defaults(func=CmdRemove)
# Move
move_parser = subparsers.add_parser(
'move',
parents=[parent_parser],
help='Move output of DVC file.')
move_parser.add_argument(
'src',
help='Source')
move_parser.add_argument(
'dst',
help='Destination')
move_parser.set_defaults(func=CmdMove)
# Garbage collector
gc_parser = subparsers.add_parser(
'gc',
+13
View File
@@ -0,0 +1,13 @@
from dvc.exceptions import DvcException
from dvc.command.common.base import CmdBase
class CmdMove(CmdBase):
def run(self):
try:
self.project.move(self.args.src, self.args.dst)
except DvcException as ex:
self.project.logger.error('Failed to move \'{}\' -> \'{}\''.format(self.args.src,
self.args.dst), ex)
return 1
return 0
+1 -1
View File
@@ -33,7 +33,7 @@ class DependencyLOCAL(DependencyBase):
if not os.path.isabs(path):
path = self.ospath(path)
path = os.path.join(stage.cwd, path)
self.path = os.path.normpath(path)
self.path = os.path.abspath(os.path.normpath(path))
self.path_info = {'scheme': 'local',
'path': self.path}
+12
View File
@@ -82,3 +82,15 @@ class OutputLOCAL(DependencyLOCAL):
self.remote.remove(self.path_info)
if ignore_remove and self.use_cache:
self.project.scm.ignore_remove(self.path)
def move(self, out):
if self.use_cache:
self.project.scm.ignore_remove(self.path)
self.remote.move(self.path_info, out.path_info)
self.path = out.path
self.path_info = out.path_info
self.save()
if self.use_cache:
self.project.scm.ignore(self.path)
+30
View File
@@ -5,6 +5,7 @@ import json
import networkx as nx
from jsonpath_rw import parse
import dvc.output as Output
from dvc.logger import Logger
from dvc.exceptions import DvcException
from dvc.stage import Stage
@@ -137,6 +138,35 @@ class Project(object):
return stage
def move(self, from_path, to_path):
from_out = Output.loads_from(Stage(self, cwd=os.curdir), [from_path])[0]
found = False
for stage in self.stages():
for out in stage.outs:
if out.path != from_out.path:
continue
if not stage.is_data_source:
raise DvcException('Dvcfile \'{}\' is not a data source.'.format(stage.rel_path))
found = True
to_out = Output.loads_from(out.stage, [to_path], out.cache, out.metric)[0]
out.move(to_out)
stage_base = os.path.basename(stage.path).rstrip(Stage.STAGE_FILE_SUFFIX)
stage_dir = os.path.dirname(stage.path)
from_base = os.path.basename(from_path)
to_base = os.path.basename(to_path)
if stage_base == from_base:
os.unlink(stage.path)
stage.path = os.path.join(stage_dir, to_base + Stage.STAGE_FILE_SUFFIX)
stage.dump()
if not found:
raise DvcException('Unable to find dvcfile with output \'{}\''.format(from_path))
def run(self,
cmd=None,
deps=[],
+6
View File
@@ -79,6 +79,12 @@ class RemoteBase(object):
def upload(self, path, path_info, name=None):
raise NotImplementedError
def remove(self, path_info):
raise NotImplementedError
def move(self, path_info):
raise NotImplementedError
def cache_file_key(self, fname):
""" Key of a file within the bucket """
relpath = os.path.relpath(fname, self.project.cache.local.cache_dir)
+6
View File
@@ -246,6 +246,12 @@ class RemoteLOCAL(RemoteBase):
remove(path_info['path'])
def move(self, from_info, to_info):
if from_info['scheme'] != 'local' or to_info['scheme'] != 'local':
raise NotImplementedError
move(from_info['path'], to_info['path'])
def cache_file_key(self, path):
relpath = os.path.relpath(os.path.abspath(path), self.project.cache.local.cache_dir)
return os.path.join(self.prefix, relpath)
+41
View File
@@ -0,0 +1,41 @@
import os
from dvc.main import main
from dvc.exceptions import DvcException
from tests.basic_env import TestDvc
class TestMove(TestDvc):
def test(self):
dst = self.FOO + '1'
self.dvc.add(self.FOO)
self.dvc.move(self.FOO, dst)
self.assertFalse(os.path.isfile(self.FOO))
self.assertTrue(os.path.isfile(dst))
class TestMoveNonExistentFile(TestDvc):
def test(self):
with self.assertRaises(DvcException) as cx:
self.dvc.move('non_existent_file', 'dst')
class TestMoveDirectory(TestDvc):
def test(self):
dst = 'dst'
stage_add = self.dvc.add(self.DATA_DIR)
self.dvc.move(self.DATA_DIR, dst)
self.assertFalse(os.path.exists(self.DATA_DIR))
self.assertTrue(os.path.exists(dst))
class TestCmdMove(TestDvc):
def test(self):
stage = self.dvc.add(self.FOO)
ret = main(['move', self.FOO, self.FOO + '1'])
self.assertEqual(ret, 0)
ret = main(['move', 'non-existing-file', 'dst'])
self.assertNotEqual(ret, 0)