Repository: iterative/dvc

Issue:
dvc repro reproduces directories all the time
The directory was just created by `dvc run` and next `dvc repro` reproduces the directory one more time.

```
$ dvc repro -v dir1.dvc
Data /home/ubuntu/src/myrepo_test/data/file.txt with cache /home/ubuntu/src/myrepo_test/.dvc/cache/2db7e560e41eb16f6a78cb13d2645836 didn't change
/home/ubuntu/src/myrepo_test/data/file.txt.dvc didn't change
Data /home/ubuntu/src/myrepo_test/dir1 with cache /home/ubuntu/src/myrepo_test/.dvc/cache/3908923f1bc06b1f1279aad916f5cdbf changed
/home/ubuntu/src/myrepo_test/dir1.dvc changed
Data /home/ubuntu/src/myrepo_test/dir1 with cache /home/ubuntu/src/myrepo_test/.dvc/cache/3908923f1bc06b1f1279aad916f5cdbf changed
/home/ubuntu/src/myrepo_test/dir1.dvc changed
Removing '/home/ubuntu/src/myrepo_test/dir1/.head'
Removing '/home/ubuntu/src/myrepo_test/dir1/.tail'
Reproducing 'dir1.dvc':
	./headdir.sh data/file.txt dir1
...
```

Details:
```
# headdir.sh
f=`basename $2`
head -n 3 $1 > $2/${f}.head
tail -n 3 $1 > $2/${f}.tail
```

`dvc run -d data/file.txt -o dir1 ./headdir.sh data/file.txt dir1`


Discussion:
Yeah, I was able to reproduce this issue. Already working on it.

Resolving patch:
diff --git a/dvc/output.py b/dvc/output.py
--- a/dvc/output.py
+++ b/dvc/output.py
@@ -171,16 +171,48 @@ def loads(cls, project, s, use_cache=True, cwd=os.curdir):
     def loads_from(cls, project, s_list, use_cache=False, cwd=os.curdir):
         return [cls.loads(project, x, use_cache=use_cache, cwd=cwd) for x in s_list]
 
+    def _changed_file(self, path, cache):
+        if os.path.isfile(path) and \
+           os.path.isfile(cache) and \
+           System.samefile(path, cache) and \
+           os.stat(cache).st_mode & stat.S_IREAD:
+            return False
+
+        return True
+
+    def _changed_dir(self):
+        if not os.path.isdir(self.path) or not os.path.isdir(self.cache):
+            return True
+
+        for root, dirs, files in os.walk(self.path):
+            for fname in files:
+                path = os.path.join(root, fname)
+                mtime = os.path.getmtime(path)
+                inode = os.stat(path).st_ino
+
+                state = self.project.state.get(path)
+                if state and state.mtime == mtime and state.inode == inode:
+                    md5 = state.md5
+                else:
+                    md5 = file_md5(path)[0]
+
+                cache = self.project.cache.get(md5)
+                if self._changed_file(path, cache):
+                    return True
+
+        return False
+
     def changed(self):
         ret = True
 
         if not self.use_cache:
             ret = super(Output, self).changed()
-        elif os.path.exists(self.path) and \
-           os.path.exists(self.cache) and \
-           System.samefile(self.path, self.cache) and \
-           os.stat(self.cache).st_mode & stat.S_IREAD:
-            ret = False
+        elif os.path.isfile(self.path) and \
+             os.path.isfile(self.cache):
+            ret = self._changed_file(self.path, self.cache)
+        elif os.path.isdir(self.path) and \
+             os.path.isdir(self.cache):
+            ret = self._changed_dir()
 
         msg = "Data {} with cache {} "
         if ret:


Test patch:
diff --git a/tests/test_repro.py b/tests/test_repro.py
--- a/tests/test_repro.py
+++ b/tests/test_repro.py
@@ -104,6 +104,33 @@ def test(self):
         self.assertEqual(stages[0].outs[0].md5, file_md5(self.BAR)[0])
 
 
+class TestReproChangedDir(TestDvc):
+    def test(self):
+        file_name = 'file'
+        shutil.copyfile(self.FOO, file_name)
+
+        stage_name = 'dir.dvc'
+        dir_name = 'dir'
+        dir_code = 'dir.py'
+
+        with open(dir_code, 'w+') as fd:
+            fd.write("import os; import shutil; os.mkdir(\"{}\"); shutil.copyfile(\"{}\", os.path.join(\"{}\", \"{}\"))".format(dir_name, file_name, dir_name, file_name))
+
+        self.dvc.run(fname=stage_name,
+                     outs=[dir_name],
+                     deps=[file_name, dir_code],
+                     cmd="python {}".format(dir_code))
+
+        stages = self.dvc.reproduce(stage_name)
+        self.assertEqual(len(stages), 0)
+
+        os.unlink(file_name)
+        shutil.copyfile(self.BAR, file_name)
+
+        stages = self.dvc.reproduce(stage_name)
+        self.assertEqual(len(stages), 1)
+
+
 class TestCmdRepro(TestRepro):
     def test(self):
         ret = main(['repro',