diff --git a/dvc/config.py b/dvc/config.py index 5f2e2d8cb..5916a5201 100644 --- a/dvc/config.py +++ b/dvc/config.py @@ -171,7 +171,7 @@ class Config(object): # need to convert our config to dict before passing it to self._config = self._lower(self._config) local = self._lower(local) - self._config.update(local) + self._config = self._merge(self._config, local) self._config = Schema(self.SCHEMA).validate(self._config) @@ -182,6 +182,17 @@ class Config(object): except Exception as ex: raise ConfigError(ex) + @staticmethod + def _merge(first, second): + res = {} + sections = list(first.keys()) + list(second.keys()) + for section in sections: + f = first.get(section, {}).copy() + s = second.get(section, {}).copy() + f.update(s) + res[section] = f + return res + @staticmethod def _lower(config): new_config = {} diff --git a/dvc/state.py b/dvc/state.py index 1e668d031..efcf142d8 100644 --- a/dvc/state.py +++ b/dvc/state.py @@ -96,7 +96,20 @@ class State(object): @staticmethod def mtime(path): - return str(int(nanotime.timestamp(os.path.getmtime(path)))) + if os.path.isdir(path): + mtime = -1 + for root, dirs, files in os.walk(path): + for fname in files: + f = os.path.join(root, fname) + m = os.path.getmtime(f) + if m > mtime: + mtime = m + if mtime == -1: + mtime = os.path.getmtime(path) + else: + mtime = os.path.getmtime(path) + + return str(int(nanotime.timestamp(mtime))) @staticmethod def inode(path): diff --git a/tests/test_repro.py b/tests/test_repro.py index 1e1441ad1..94ff02dd7 100644 --- a/tests/test_repro.py +++ b/tests/test_repro.py @@ -255,6 +255,31 @@ class TestReproChangedDir(TestDvc): self.assertEqual(len(stages), 1) +class TestReproChangedDirData(TestDvc): + def test(self): + dir_name = 'dir' + dir_code = 'dir_code.py' + with open(dir_code, 'w+') as fd: + fd.write("import os; import sys; import shutil; shutil.copytree(sys.argv[1], sys.argv[2])") + + stage = self.dvc.run(outs=[dir_name], + deps=[self.DATA_DIR, dir_code], + cmd="python {} {} {}".format(dir_code, + self.DATA_DIR, + dir_name)) + self.assertTrue(stage is not None) + + stages = self.dvc.reproduce(stage.path) + self.assertEqual(len(stages), 0) + + with open(self.DATA_SUB, 'a') as fd: + fd.write('add') + + stages = self.dvc.reproduce(stage.path) + self.assertEqual(len(stages), 1) + self.assertTrue(stages[0] is not None) + + class TestReproMissingMd5InStageFile(TestRepro): def test(self): with open(self.file1_stage, 'r') as fd: