From a3e023c34024ddedebcc4905c6104622ae472c81 Mon Sep 17 00:00:00 2001 From: Ruslan Kuprieiev Date: Sun, 14 Jan 2018 01:34:20 +0200 Subject: [PATCH] state: don't forget to track inode Signed-off-by: Ruslan Kuprieiev --- dvc/stage.py | 7 +++++-- dvc/state.py | 23 ++++++++++++++--------- tests/test_state.py | 8 ++++++-- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/dvc/stage.py b/dvc/stage.py index e68016208..29ddefbf5 100644 --- a/dvc/stage.py +++ b/dvc/stage.py @@ -94,6 +94,9 @@ class Dependency(object): def mtime(self): return os.path.getmtime(self.path) + def inode(self): + return os.stat(self.path).st_ino + def update(self): if not os.path.exists(self.path): raise CmdOutputDoesNotExistError(self.rel_path) @@ -101,14 +104,14 @@ class Dependency(object): raise CmdOutputIsNotFileError(self.path) state = self.project.state.get(self.path) - if state and state.mtime == self.mtime(): + if state and state.mtime == self.mtime() and state.inode == self.inode(): md5 = state.md5 msg = '{} using md5 {} from state file' self.project.logger.debug(msg.format(self.path, md5)) self.md5 = md5 else: self.md5 = file_md5(self.path)[0] - self.project.state.update(self.path, self.md5, self.mtime()) + self.project.state.update(self.path, self.md5, self.mtime(), self.inode()) def dumpd(self, cwd): return { diff --git a/dvc/state.py b/dvc/state.py index ddd579e92..f4ae082df 100644 --- a/dvc/state.py +++ b/dvc/state.py @@ -8,30 +8,35 @@ class StateEntry(object): PARAM_PATH = 'path' PARAM_MTIME = 'mtime' PARAM_MD5 = 'md5' + PARAM_INODE = 'inode' - def __init__(self, root_dir, path, md5, mtime): + def __init__(self, root_dir, path, md5, mtime, inode): self.root_dir = root_dir self.path = path self.mtime = mtime self.md5 = md5 + self.inode = inode self.dvc_path = os.path.relpath(self.path, self.root_dir) - def update(self, md5, mtime): + def update(self, md5, mtime, inode): self.mtime = mtime self.md5 = md5 + self.inode = inode @staticmethod def loadd(root_dir, d): path = os.path.join(root_dir, d[StateEntry.PARAM_PATH]) mtime = d[StateEntry.PARAM_MTIME] md5 = d[StateEntry.PARAM_MD5] - return StateEntry(root_dir, path, md5, mtime) + inode = d[StateEntry.PARAM_INODE] + return StateEntry(root_dir, path, md5, mtime, inode) def dumpd(self): return { self.PARAM_PATH: self.dvc_path, self.PARAM_MD5: self.md5, - self.PARAM_MTIME: self.mtime + self.PARAM_MTIME: self.mtime, + self.PARAM_INODE: self.inode } @@ -53,18 +58,18 @@ class State(object): def init(root_dir, dvc_dir): return State(root_dir, dvc_dir) - def update(self, path, md5, mtime): + def update(self, path, md5, mtime, inode): existing = self.get(path) if not existing: - return self.add(path, md5, mtime) + return self.add(path, md5, mtime, inode) - state = StateEntry(self.root_dir, path, md5, mtime) + state = StateEntry(self.root_dir, path, md5, mtime, inode) self._db.update(state.dumpd(), self._q.path == state.dvc_path) return state - def add(self, path, md5, mtime): - entry = StateEntry(self.root_dir, path, md5, mtime) + def add(self, path, md5, mtime, inode): + entry = StateEntry(self.root_dir, path, md5, mtime, inode) self._db.insert(entry.dumpd()) return entry diff --git a/tests/test_state.py b/tests/test_state.py index 642bed0a8..af80eaa3a 100644 --- a/tests/test_state.py +++ b/tests/test_state.py @@ -12,20 +12,24 @@ class TestState(TestDvc): md5_2 = '2' mtime_1 = 1 mtime_2 = 2 + inode_1 = 1 + inode_2 = 2 state = State(self.dvc.root_dir, self.dvc.dvc_dir) self.assertIsNone(state.get(path)) - state.add(path, md5_1, mtime_1) + state.add(path, md5_1, mtime_1, inode_1) entry = state.get(path) self.assertIsInstance(entry, StateEntry) self.assertEqual(entry.path, path) self.assertEqual(entry.md5, md5_1) self.assertEqual(entry.mtime, mtime_1) + self.assertEqual(entry.inode, inode_1) - state.update(path, md5_2, mtime_2) + state.update(path, md5_2, mtime_2, inode_2) entry = state.get(path) self.assertIsInstance(entry, StateEntry) self.assertEqual(entry.path, path) self.assertEqual(entry.md5, md5_2) self.assertEqual(entry.mtime, mtime_2) + self.assertEqual(entry.inode, inode_2)