diff --git a/dvc/cli.py b/dvc/cli.py index df02b5ea5..9db4f3841 100644 --- a/dvc/cli.py +++ b/dvc/cli.py @@ -25,21 +25,34 @@ from dvc import VERSION def parse_args(argv=None): # Common args parent_parser = argparse.ArgumentParser(add_help=False) - parent_parser.add_argument('-q', + parent_parser.add_argument( + '-q', '--quiet', action='store_true', default=False, help='Be quiet.') - parent_parser.add_argument('-v', + parent_parser.add_argument( + '-v', '--verbose', action='store_true', default=False, help='Be verbose.') - parent_parser.add_argument('-G', + parent_parser.add_argument( + '-G', '--no-git-actions', action='store_true', default=False, help='Skip all git actions including reproducibility check and commits.') + parent_parser.add_argument( + '-b', + '--branch', + metavar='BRANCH', + help='Execute a command in the branch.') + parent_parser.add_argument( + '-n', + '--new-branch', + metavar='BRANCH', + help='Create a new branch and execute a command in the branch.') # Main parser desc = 'Data Version Control' diff --git a/dvc/command/checkout.py b/dvc/command/checkout.py index eb51a824b..efa58fa9c 100644 --- a/dvc/command/checkout.py +++ b/dvc/command/checkout.py @@ -1,6 +1,6 @@ import os -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.logger import Logger from dvc.system import System @@ -34,19 +34,18 @@ class CmdCheckout(CmdBase): Logger.info('Checkout \'{}\''.format(item.data.relative)) def run(self): - with DvcLock(self.is_locker, self.git): - try: - states = self.git.state_files_for_previous_commit() - prev_items = self.settings.path_factory.data_items_from_states(states, existing=False) - except Exception as ex: - Logger.error(u'Unable to get data files from previous commit: {}'.format(ex)) - return 1 + try: + states = self.git.state_files_for_previous_commit() + prev_items = self.settings.path_factory.data_items_from_states(states, existing=False) + except Exception as ex: + Logger.error(u'Unable to get data files from previous commit: {}'.format(ex)) + return 1 - curr_items = self.settings.path_factory.all_existing_data_items() - self.checkout(curr_items) + curr_items = self.settings.path_factory.all_existing_data_items() + self.checkout(curr_items) - self.remove_files(list(set(prev_items) - set(curr_items))) - return 0 + self.remove_files(list(set(prev_items) - set(curr_items))) + return 0 @staticmethod def remove_files(removed_items_set): diff --git a/dvc/command/common/__init__.py b/dvc/command/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dvc/command/base.py b/dvc/command/common/base.py similarity index 63% rename from dvc/command/base.py rename to dvc/command/common/base.py index b28d13206..3b1bbd5fe 100644 --- a/dvc/command/base.py +++ b/dvc/command/common/base.py @@ -1,39 +1,6 @@ -import argparse -import fasteners - -from multiprocessing import cpu_count - -from dvc.config import ConfigError -from dvc.exceptions import DvcException from dvc.logger import Logger - - -class CmdBaseError(DvcException): - def __init__(self, msg): - super(CmdBaseError, self).__init__('{}'.format(msg)) - - -class DvcLockerError(CmdBaseError): - def __init__(self, msg): - super(DvcLockerError, self).__init__('DVC locker error: {}'.format(msg)) - - -class DvcLock(object): - def __init__(self, is_locker, git): - self.is_locker = is_locker - self.git = git - self.lock = None - - def __enter__(self): - if self.is_locker: - self.lock = fasteners.InterProcessLock(self.git.lock_file) - if not self.lock.acquire(timeout=5): - raise DvcLockerError('Cannot perform the cmd since DVC is busy and locked. Please retry the cmd later.') - return self.lock - - def __exit__(self, type, value, traceback): - if self.is_locker: - self.lock.release() +from dvc.command.common.branch_changer import BranchChanger +from dvc.command.common.dvc_lock import DvcLock class CmdBase(object): @@ -78,12 +45,6 @@ class CmdBase(object): def set_git_action(self, value): self.parsed_args.no_git_actions = not value - @property - def is_locker(self): - if 'no_lock' in self.parsed_args.__dict__: - return not self.parsed_args.no_lock - return True - def set_locker(self, value): self.parsed_args.no_lock = value @@ -99,5 +60,17 @@ class CmdBase(object): def not_committed_changes_warning(): Logger.warn('changes were not committed to git') + def run_cmd(self): + with DvcLock(self.is_locker, self.git): + with BranchChanger(self.parsed_args.branch, self.parsed_args.new_branch, self.git): + return self.run() + + # Abstract methods that have to be implemented by any inheritance class def run(self): pass + + @property + def is_locker(self): + if 'no_lock' in self.parsed_args.__dict__: + return not self.parsed_args.no_lock + return True diff --git a/dvc/command/common/branch_changer.py b/dvc/command/common/branch_changer.py new file mode 100644 index 000000000..3980aeaf2 --- /dev/null +++ b/dvc/command/common/branch_changer.py @@ -0,0 +1,31 @@ +from dvc.command.common.common_error import CmdCommonError + + +class BranchChangerError(CmdCommonError): + def __init__(self, branch, msg): + super(CmdCommonError, self).__init__('Error in changing branch \'{}\': {}'.format(branch, msg)) + + +class BranchChanger(object): + def __init__(self, branch, new_branch, git): + if branch and new_branch: + raise BranchChangerError("Commands conflict: --branch and --new-branch cannot be used at the same command") + + self.perform_action = branch or new_branch + + self.branch = branch if branch else new_branch + self.create_new = new_branch is not None + self.git = git + + def __enter__(self): + if self.perform_action: + code, _, err = self.git.checkout(self.branch, self.create_new) + if code != 0: + raise BranchChangerError(self.branch, err) + return self + + def __exit__(self, type, value, traceback): + if self.perform_action: + code, _, err = self.git.checkout_previous() + if code != 0: + raise BranchChangerError(self.branch, err) diff --git a/dvc/command/common/common_error.py b/dvc/command/common/common_error.py new file mode 100644 index 000000000..3013a73bb --- /dev/null +++ b/dvc/command/common/common_error.py @@ -0,0 +1,6 @@ +from dvc.exceptions import DvcException + + +class CmdCommonError(DvcException): + def __init__(self, msg): + super(CmdCommonError, self).__init__('{}'.format(msg)) diff --git a/dvc/command/common/dvc_lock.py b/dvc/command/common/dvc_lock.py new file mode 100644 index 000000000..973d4afce --- /dev/null +++ b/dvc/command/common/dvc_lock.py @@ -0,0 +1,28 @@ +import fasteners + +from dvc.command.common.common_error import CmdCommonError + + +class DvcLockerError(CmdCommonError): + def __init__(self, msg): + super(DvcLockerError, self).__init__('DVC locker error: {}'.format(msg)) + + +class DvcLock(object): + TIMEOUT = 5 + + def __init__(self, is_locker, git): + self.is_locker = is_locker + self.git = git + self.lock = None + + def __enter__(self): + if self.is_locker: + self.lock = fasteners.InterProcessLock(self.git.lock_file) + if not self.lock.acquire(timeout=self.TIMEOUT): + raise DvcLockerError('Cannot perform the cmd since DVC is busy and locked. Please retry the cmd later.') + return self.lock + + def __exit__(self, type, value, traceback): + if self.is_locker: + self.lock.release() diff --git a/dvc/command/traverse.py b/dvc/command/common/traverse.py similarity index 94% rename from dvc/command/traverse.py rename to dvc/command/common/traverse.py index b4be15839..2d7df39ad 100644 --- a/dvc/command/traverse.py +++ b/dvc/command/common/traverse.py @@ -1,10 +1,11 @@ import os -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.exceptions import DvcException from dvc.logger import Logger from dvc.path.data_item import DataItemError + class TraverseError(DvcException): def __init__(self, msg): DvcException.__init__(self, 'Traverse file tree error: {}'.format(msg)) @@ -17,9 +18,8 @@ class Traverse(CmdBase): self._do_not_start_from_root = do_not_start_from_root def run(self): - with DvcLock(self.is_locker, self.git): - if not self._traverse_all(): - return 1 + if not self._traverse_all(): + return 1 return 0 diff --git a/dvc/command/config.py b/dvc/command/config.py index 8db88b509..b948529d1 100644 --- a/dvc/command/config.py +++ b/dvc/command/config.py @@ -1,7 +1,7 @@ import os import configobj -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.logger import Logger from dvc.config import Config @@ -75,7 +75,7 @@ class CmdConfig(CmdBase): return 0 - def _run(self): + def run(self): if self.check_opt() != 0: return 1 @@ -86,7 +86,3 @@ class CmdConfig(CmdBase): return self.show() return self.set() - - def run(self): - with DvcLock(self.is_locker, self.git): - return self._run() diff --git a/dvc/command/data_sync.py b/dvc/command/data_sync.py index 71f060877..0098b3826 100644 --- a/dvc/command/data_sync.py +++ b/dvc/command/data_sync.py @@ -1,25 +1,27 @@ -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.logger import Logger import dvc.data_cloud as cloud -class CmdDataPull(CmdBase): - def __init__(self, settings): - super(CmdDataPull, self).__init__(settings) +class CmdDataBase(CmdBase): + def __init__(self, settings, cmd): + super(CmdDataBase, self).__init__(settings) + self.cmd = cmd def run(self): - with DvcLock(self.is_locker, self.git): - self.cloud.pull(self.parsed_args.targets, self.parsed_args.jobs) + self.cmd(self.parsed_args.targets, self.parsed_args.jobs) -class CmdDataPush(CmdBase): +class CmdDataPull(CmdDataBase): def __init__(self, settings): - super(CmdDataPush, self).__init__(settings) + super(CmdDataPull, self).__init__(settings, self.cloud.pull) + + +class CmdDataPush(CmdDataBase): + def __init__(self, settings): + super(CmdDataPush, self).__init__(settings, self.cloud.push) - def run(self): - with DvcLock(self.is_locker, self.git): - self.cloud.push(self.parsed_args.targets, self.parsed_args.jobs) class CmdDataStatus(CmdBase): def __init__(self, settings): @@ -41,9 +43,6 @@ class CmdDataStatus(CmdBase): Logger.info('\t{}\t{}'.format(prefix_map[ret], target.data.dvc)) def run(self): - with DvcLock(self.is_locker, self.git): - status = self.cloud.status(self.parsed_args.targets, self.parsed_args.jobs) - - self._show(status) - - return 0 + status = self.cloud.status(self.parsed_args.targets, self.parsed_args.jobs) + self._show(status) + return 0 diff --git a/dvc/command/find.py b/dvc/command/find.py index bf837d94d..3bb708c02 100644 --- a/dvc/command/find.py +++ b/dvc/command/find.py @@ -1,5 +1,5 @@ from dvc import utils -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.executor import ExecutorError from dvc.logger import Logger @@ -9,23 +9,22 @@ class CmdFind(CmdBase): super(CmdFind, self).__init__(settings) def run(self): - with DvcLock(self.is_locker, self.git): - fname = self.parsed_args.target - branch_name = self.parsed_args.branch_name + fname = self.parsed_args.target + branch_name = self.parsed_args.branch_name - branches = self.git.branches(branch_name) - metrics = [self.read_metrics(fname, b) for b in branches] + branches = self.git.branches(branch_name) + metrics = [self.read_metrics(fname, b) for b in branches] - zipped = zip(metrics, branches) - res = self.filter(zipped, self.parsed_args.criteria) - res = filter(lambda x: x[0] is not None, res) + zipped = zip(metrics, branches) + res = self.filter(zipped, self.parsed_args.criteria) + res = filter(lambda x: x[0] is not None, res) - for (val, branch) in res: - if self.parsed_args.show_value: - print('{}: {}'.format(branch, val)) - else: - print(branch) - return 0 + for (val, branch) in res: + if self.parsed_args.show_value: + print('{}: {}'.format(branch, val)) + else: + print(branch) + return 0 @staticmethod def filter(zipped, criteria): diff --git a/dvc/command/gc.py b/dvc/command/gc.py index ed39afc6a..ada0bac8e 100644 --- a/dvc/command/gc.py +++ b/dvc/command/gc.py @@ -1,7 +1,6 @@ import os -from dvc.command.base import DvcLock -from dvc.command.traverse import Traverse +from dvc.command.common.traverse import Traverse from dvc.logger import Logger from dvc.path.data_item import DataItemError from dvc.config import ConfigI @@ -13,20 +12,19 @@ class CmdGC(Traverse): self.clist = [] def run(self): - with DvcLock(self.is_locker, self.git): - if not self._traverse(self.git.git_dir_abs): - Logger.error('Failed to collect used cache') - return 1 + if not self._traverse(self.git.git_dir_abs): + Logger.error('Failed to collect used cache') + return 1 - for cache in os.listdir(ConfigI.CACHE_DIR): - fname = os.path.join(ConfigI.CACHE_DIR, cache) - if fname in self.clist: - continue - os.remove(fname) - self._remove_cloud_cache(self.settings.path_factory.data_item('.empty', fname)) - Logger.info('Cache \'{}\' was removed'.format(fname)) + for cache in os.listdir(ConfigI.CACHE_DIR): + fname = os.path.join(ConfigI.CACHE_DIR, cache) + if fname in self.clist: + continue + os.remove(fname) + self._remove_cloud_cache(self.settings.path_factory.data_item('.empty', fname)) + Logger.info('Cache \'{}\' was removed'.format(fname)) - return 0 + return 0 def process_file(self, target): Logger.debug(u'[Cmd-GC] GC file {}.'.format(target)) diff --git a/dvc/command/import_file.py b/dvc/command/import_file.py index ff9e53126..f26e2cfc7 100644 --- a/dvc/command/import_file.py +++ b/dvc/command/import_file.py @@ -1,5 +1,4 @@ import os -import re try: from urlparse import urlparse @@ -8,12 +7,11 @@ except ImportError: # pylint: disable=no-name-in-module, import-error from urllib.parse import urlparse -from dvc.command.base import CmdBase, DvcLock -from dvc.data_cloud import sizeof_fmt, file_md5 +from dvc.command.common.base import CmdBase from dvc.logger import Logger from dvc.exceptions import DvcException from dvc.state_file import StateFile -from dvc.system import System + class ImportFileError(DvcException): def __init__(self, msg): @@ -26,32 +24,30 @@ class CmdImportFile(CmdBase): def run(self): targets = [] - with DvcLock(self.is_locker, self.git): - output = self.parsed_args.output - self.verify_output(output, self.parsed_args.input) + output = self.parsed_args.output + self.verify_output(output, self.parsed_args.input) - for input in self.parsed_args.input: - if not os.path.isdir(input): - targets.append((input, output)) - else: - input_dir = os.path.basename(input) - for root, dirs, files in os.walk(input): - for file in files: - filename = os.path.join(root, file) + for input in self.parsed_args.input: + if not os.path.isdir(input): + targets.append((input, output)) + else: + input_dir = os.path.basename(input) + for root, dirs, files in os.walk(input): + for file in files: + filename = os.path.join(root, file) - rel = os.path.relpath(filename, input) - out = os.path.join(output, input_dir, rel) + rel = os.path.relpath(filename, input) + out = os.path.join(output, input_dir, rel) - out_dir = os.path.dirname(out) - if not os.path.exists(out_dir): - os.mkdir(out_dir) + out_dir = os.path.dirname(out) + if not os.path.exists(out_dir): + os.mkdir(out_dir) - targets.append((filename, out)) - pass - self.import_files(targets, self.parsed_args.lock, self.parsed_args.jobs) - message = 'DVC import files: {} -> {}'.format(str(self.parsed_args.input), output) - self.commit_if_needed(message) - pass + targets.append((filename, out)) + pass + self.import_files(targets, self.parsed_args.lock, self.parsed_args.jobs) + message = 'DVC import files: {} -> {}'.format(str(self.parsed_args.input), output) + self.commit_if_needed(message) @staticmethod def verify_output(output, input): diff --git a/dvc/command/init.py b/dvc/command/init.py index b5e3b5e40..40bda25e5 100644 --- a/dvc/command/init.py +++ b/dvc/command/init.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from dvc.command.base import CmdBase +from dvc.command.common.base import CmdBase from dvc.logger import Logger from dvc.config import Config from dvc.exceptions import DvcException @@ -62,6 +62,10 @@ ProjectName = def __init__(self, settings): super(CmdInit, self).__init__(settings) + @property + def is_locker(self): + return False + def get_not_existing_path(self, *args): path = Path(os.path.join(self.git.git_dir, *args)) if path.exists(): diff --git a/dvc/command/instance_create.py b/dvc/command/instance_create.py index 35237b152..6697fa769 100644 --- a/dvc/command/instance_create.py +++ b/dvc/command/instance_create.py @@ -1,5 +1,5 @@ from dvc.cloud.instance_manager import InstanceManager -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.exceptions import DvcException from dvc.logger import Logger @@ -9,21 +9,20 @@ class CmdInstanceCreate(CmdBase): super(CmdInstanceCreate, self).__init__(settings) def run(self): - with DvcLock(self.is_locker, self.git): - name = self.parsed_args.name - cloud = self.parsed_args.cloud or self.settings.config.cloud + name = self.parsed_args.name + cloud = self.parsed_args.cloud or self.settings.config.cloud - # print('NAME: {}'.format(name)) - # print('CLOUD: {}'.format(cloud)) + # print('NAME: {}'.format(name)) + # print('CLOUD: {}'.format(cloud)) - if not name: - Logger.error('Instance name is not defined') - return 1 + if not name: + Logger.error('Instance name is not defined') + return 1 - try: - InstanceManager().create(name, cloud, self.parsed_args, self.settings.config) - except DvcException as ex: - Logger.error('Instance creation error: {}'.format(ex)) - return 1 + try: + InstanceManager().create(name, cloud, self.parsed_args, self.settings.config) + except DvcException as ex: + Logger.error('Instance creation error: {}'.format(ex)) + return 1 - return 0 + return 0 diff --git a/dvc/command/lock.py b/dvc/command/lock.py index 9b8f8a674..7a2b4ce2d 100644 --- a/dvc/command/lock.py +++ b/dvc/command/lock.py @@ -1,4 +1,4 @@ -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.logger import Logger from dvc.state_file import StateFile @@ -8,8 +8,7 @@ class CmdLock(CmdBase): super(CmdLock, self).__init__(settings) def run(self): - with DvcLock(self.is_locker, self.git): - return self.lock_files(self.parsed_args.files, not self.parsed_args.unlock) + return self.lock_files(self.parsed_args.files, not self.parsed_args.unlock) def lock_files(self, files, target): cmd = 'lock' if target else 'unlock' diff --git a/dvc/command/merge.py b/dvc/command/merge.py index ed30906e9..9fa2c2cca 100644 --- a/dvc/command/merge.py +++ b/dvc/command/merge.py @@ -1,13 +1,13 @@ import os -import json -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.config import ConfigI from dvc.logger import Logger from dvc.state_file import StateFile from dvc.path.data_item import DataItem from dvc.system import System + class CmdMerge(CmdBase): def __init__(self, settings): super(CmdMerge, self).__init__(settings) @@ -63,12 +63,11 @@ class CmdMerge(CmdBase): self.commit_if_needed(msg) def run(self): - with DvcLock(self.is_locker, self.git): - targets = self.collect_targets() - if not targets: - return 1 + targets = self.collect_targets() + if not targets: + return 1 - self.checkout_targets(targets) - self.print_info(targets) + self.checkout_targets(targets) + self.print_info(targets) - return 0 + return 0 diff --git a/dvc/command/remove.py b/dvc/command/remove.py index 24c365467..a9f96027c 100644 --- a/dvc/command/remove.py +++ b/dvc/command/remove.py @@ -1,6 +1,6 @@ import os -from dvc.command.traverse import Traverse +from dvc.command.common.traverse import Traverse from dvc.logger import Logger @@ -8,6 +8,13 @@ class CmdRemove(Traverse): def __init__(self, settings): super(CmdRemove, self).__init__(settings, "remove") + def run(self): + if not self.parsed_args.target: + Logger.error('Nothing specified, nothing removed.') + return 1 + + return super(CmdRemove, self).run() + def process_file(self, target): Logger.debug(u'[Cmd-Remove] Remove file {}.'.format(target)) diff --git a/dvc/command/repro.py b/dvc/command/repro.py index ff64210b3..7c85b32a4 100644 --- a/dvc/command/repro.py +++ b/dvc/command/repro.py @@ -1,7 +1,6 @@ import os import copy -from dvc.command.base import DvcLock from dvc.command.import_file import CmdImportFile from dvc.command.run import CmdRun from dvc.logger import Logger @@ -40,23 +39,21 @@ class CmdRepro(CmdRun): return [] def run(self): - with DvcLock(self.is_locker, self.git): - recursive = not self.parsed_args.single_item - targets = [] + recursive = not self.parsed_args.single_item + targets = [] - if self.parsed_args.target: - targets += self.parsed_args.target - else: - target = self.settings.config._config['Global'].get('Target', None) - if not target or len(target) == 0: - Logger.error('Reproduction target is not defined. ' + - 'Specify data file or set target by ' + - '`dvc config global.target target` command.') - return 1 - targets += [target] + if self.parsed_args.target: + targets += self.parsed_args.target + else: + target = self.settings.config._config['Global'].get('Target', None) + if not target or len(target) == 0: + Logger.error('Reproduction target is not defined. ' + + 'Specify data file or set target by ' + + '`dvc config global.target target` command.') + return 1 + targets += [target] - return self.repro_target(targets, recursive, self.parsed_args.force) - pass + return self.repro_target(targets, recursive, self.parsed_args.force) def repro_target(self, target, recursive, force): if not self.no_git_actions and not self.git.is_ready_to_go(): diff --git a/dvc/command/run.py b/dvc/command/run.py index 30c081277..679e2f5ac 100644 --- a/dvc/command/run.py +++ b/dvc/command/run.py @@ -1,11 +1,9 @@ import os -import sys -from dvc.command.base import CmdBase, DvcLock +from dvc.command.common.base import CmdBase from dvc.exceptions import DvcException from dvc.git_wrapper import GitWrapper from dvc.logger import Logger -from dvc.path.data_item import DataDirError, NotInGitDirError from dvc.repository_change import RepositoryChange from dvc.state_file import StateFile from dvc.utils import cached_property @@ -37,16 +35,14 @@ class CmdRun(CmdBase): return self._data_items_from_params(self.parsed_args.output, 'Output') def run(self): - with DvcLock(self.is_locker, self.git): - cmd = [self.parsed_args.command] + self.parsed_args.args - data_items_from_args, not_data_items_from_args = self.argv_files_by_type(cmd) - return self.run_and_commit_if_needed(cmd, - data_items_from_args, - not_data_items_from_args, - self.parsed_args.stdout, - self.parsed_args.stderr, - self.parsed_args.shell) - pass + cmd = [self.parsed_args.command] + self.parsed_args.args + data_items_from_args, not_data_items_from_args = self.argv_files_by_type(cmd) + return self.run_and_commit_if_needed(cmd, + data_items_from_args, + not_data_items_from_args, + self.parsed_args.stdout, + self.parsed_args.stderr, + self.parsed_args.shell) def run_and_commit_if_needed(self, command_args, data_items_from_args, not_data_items_from_args, stdout, stderr, shell, diff --git a/dvc/command/show_pipeline.py b/dvc/command/show_pipeline.py index e54b31f55..be29d2c7c 100644 --- a/dvc/command/show_pipeline.py +++ b/dvc/command/show_pipeline.py @@ -1,8 +1,7 @@ import os import networkx as nx -from dvc.command.traverse import Traverse -from dvc.command.init import CmdInit +from dvc.command.common.traverse import Traverse from dvc.logger import Logger from dvc.state_file import StateFile diff --git a/dvc/command/show_workflow.py b/dvc/command/show_workflow.py index 80322d99c..4d9d794b1 100644 --- a/dvc/command/show_workflow.py +++ b/dvc/command/show_workflow.py @@ -1,6 +1,6 @@ import os -from dvc.command.base import CmdBase +from dvc.command.common.base import CmdBase from dvc.logger import Logger diff --git a/dvc/git_wrapper.py b/dvc/git_wrapper.py index aca0162b6..18ef1c7c0 100644 --- a/dvc/git_wrapper.py +++ b/dvc/git_wrapper.py @@ -317,16 +317,17 @@ class GitWrapper(GitWrapperI): Executor.exec_cmd_only_success(cmd.split()) @staticmethod - def checkout(commit): - Logger.debug('[dvc-git] Checkout {}'.format(commit)) - cmd = 'git checkout {}'.format(commit) - Executor.exec_cmd(cmd.split()) + def checkout(commit_or_branch, create_new=False): + prefix = '-b ' if create_new else '' + Logger.debug('[dvc-git] Checkout {}{}'.format(prefix, commit_or_branch)) + cmd = 'git checkout {}{}'.format(prefix, commit_or_branch) + return Executor.exec_cmd(cmd.split()) @staticmethod def checkout_previous(): Logger.debug('[dvc-git] Checkout previous') cmd = 'git checkout -' - Executor.exec_cmd(cmd.split()) + return Executor.exec_cmd(cmd.split()) def separate_dependency_files_and_dirs(self, code_dependencies): code_files = [] diff --git a/dvc/main.py b/dvc/main.py index 22f9af212..276d60cd8 100644 --- a/dvc/main.py +++ b/dvc/main.py @@ -18,7 +18,7 @@ def main(): return 255 try: - ret = instance.run() + ret = instance.run_cmd() except Exception as e: exc_info = settings.parsed_args.verbose Logger.error("{} error: {}".format(instance.__class__.__name__, e), exc_info=exc_info)