diff --git a/dvc/cli.py b/dvc/cli.py index 9028931ff..3083b8479 100644 --- a/dvc/cli.py +++ b/dvc/cli.py @@ -163,7 +163,12 @@ def parse_args(argv=None): status_parser = subparsers.add_parser( 'status', parents=[parent_cache_parser], - help='Show mismatches between local cache and cloud cache') + help='Show the project status') + status_parser.add_argument('-c', + '--cloud', + action='store_true', + default=False, + help='Show status of a local cache compared to a cloud') status_parser.set_defaults(func=CmdDataStatus) # Repro diff --git a/dvc/command/status.py b/dvc/command/status.py index cb0ddb939..dd3c63c49 100644 --- a/dvc/command/status.py +++ b/dvc/command/status.py @@ -6,27 +6,30 @@ from dvc.command.data_sync import CmdDataBase class CmdDataStatus(CmdDataBase): - def _show(self, status): - for s in status: - target, ret = s + STATUS_LEN = 10 + STATUS_INDENT = '\t' - if ret == cloud.STATUS_UNKNOWN or ret == cloud.STATUS_OK: - continue + def _normalize(self, s): + s+=':' + if len(s) >= self.STATUS_LEN: + return s + return s + (self.STATUS_LEN - len(s))*' ' - prefix_map = { - cloud.STATUS_DELETED : 'deleted: ', - cloud.STATUS_MODIFIED : 'modified:', - cloud.STATUS_NEW : 'new file:', - } + def _show(self, status, indent=0): + ind = indent * self.STATUS_INDENT - path = os.path.relpath(target, self.project.cache.cache_dir) - - self.project.logger.info('\t{}\t{}'.format(prefix_map[ret], path)) + for key, value in status.items(): + if isinstance(value, dict): + self.project.logger.info('{}{}'.format(ind, key)) + self._show(value, indent+1) + else: + self.project.logger.info('{}{}{}'.format(ind, self._normalize(value), key)) def do_run(self, target=None): + indent = 1 if self.args.cloud else 0 try: - status = self.project.status(target=target, jobs=self.args.jobs) - self._show(status) + status = self.project.status(target=target, jobs=self.args.jobs, cloud=self.args.cloud) + self._show(status, indent) except Exception as exc: self.project.logger.error('Failed to obtain data status', exc) return 1 diff --git a/dvc/output.py b/dvc/output.py index 2ff5dfe39..e24c0ed2c 100644 --- a/dvc/output.py +++ b/dvc/output.py @@ -75,6 +75,12 @@ class Dependency(object): def changed(self): return self._changed_md5() + def status(self): + if self.changed(): + #FIXME better msgs + return {self.rel_path: 'changed'} + return {} + @staticmethod def is_dir_cache(cache): return cache.endswith(Output.MD5_DIR_SUFFIX) diff --git a/dvc/project.py b/dvc/project.py index f8bcd5323..7e48eda13 100644 --- a/dvc/project.py +++ b/dvc/project.py @@ -2,6 +2,8 @@ import os import stat import networkx as nx +import dvc.cloud.base as cloud + from dvc.logger import Logger from dvc.exceptions import DvcException from dvc.stage import Stage, Output @@ -209,8 +211,41 @@ class Project(object): self.checkout() return ret - def status(self, target=None, jobs=1): - return self.cloud.status(self._used_cache(target), jobs) + def _local_status(self, target=None): + status = {} + + if target: + stages = [Stage.load(self, target)] + else: + stages = self.stages() + + for stage in self.stages(): + status.update(stage.status()) + + return status + + def _cloud_status(self, target=None, jobs=1): + status = {} + for target, ret in self.cloud.status(self._used_cache(target), jobs): + if ret == cloud.STATUS_UNKNOWN or ret == cloud.STATUS_OK: + continue + + prefix_map = { + cloud.STATUS_DELETED: 'deleted', + cloud.STATUS_MODIFIED: 'modified', + cloud.STATUS_NEW: 'new', + } + + path = os.path.relpath(target, self.cache.cache_dir) + + status[path] = prefix_map[ret] + + return status + + def status(self, target=None, jobs=1, cloud=False): + if cloud: + return self._cloud_status(target, jobs) + return self._local_status(target) def graph(self): G = nx.DiGraph() diff --git a/dvc/stage.py b/dvc/stage.py index 7042e1fc5..4839998cd 100644 --- a/dvc/stage.py +++ b/dvc/stage.py @@ -183,3 +183,24 @@ class Stage(object): def checkout(self): for out in self.outs: out.checkout() + + def _status(self, entries, name): + ret = {} + + for entry in entries: + ret.update(entry.status()) + + if ret: + return {name: ret} + + return {} + + def status(self): + ret = {} + ret.update(self._status(self.deps, 'deps')) + ret.update(self._status(self.outs, 'outs')) + + if ret: + return {self.relpath: ret} + + return {}