Repository: iterative/dvc

Issue:
More error information is needed wrt. remote files - [Errno 2] No such file
**Setup**
`dvc --version` = 0.32.1+db5a0 and local and remote server uses ubuntu 18.04.2. For method of installation see https://github.com/PeterFogh/dvc_dask_use_case.

**Problem**
When remote files (via SSH in my case) are missing when running `dvc repro`, because `dvc checkout` have not been performed, yet. DVC puts out the following error message: `Error: failed to reproduce '[DVC_STAGE_FILE]': [Errno 2] No such file`. I find this error message easy to misunderstand. I wish the error message pointed to the specific remote file that is missing. In my case I know it is `Posts.tsv`, because I have removed it my self.

There is the verbose log from executing `dvc repro &> no_error.log` of https://github.com/PeterFogh/dvc_dask_use_case reprository, with no error:
[no_error.log](https://github.com/iterative/dvc/files/2974913/no_error.log)

And there is the verbose log with the error from executing `dvc repro &> remove_post_tsv.log` after I have removed the remote file with `rm Posts.tsv`:
[remove_posts_tsv.log](https://github.com/iterative/dvc/files/2974918/remove_posts_tsv.log)

I will trying to come up with a PR which contains the logging fo the file path.


Discussion:

Resolving patch:
diff --git a/dvc/command/repro.py b/dvc/command/repro.py
--- a/dvc/command/repro.py
+++ b/dvc/command/repro.py
@@ -80,7 +80,7 @@ def add_parser(subparsers, parent_parser):
         "-c",
         "--cwd",
         default=os.path.curdir,
-        help="Directory within your repo to reroduce from.",
+        help="Directory within your repo to reproduce from.",
     )
     repro_parser.add_argument(
         "-m",
diff --git a/dvc/remote/ssh/connection.py b/dvc/remote/ssh/connection.py
--- a/dvc/remote/ssh/connection.py
+++ b/dvc/remote/ssh/connection.py
@@ -1,3 +1,4 @@
+import errno
 import os
 import posixpath
 
@@ -143,7 +144,11 @@ def walk_files(self, directory):
 
     def remove(self, path):
         self._sftp_connect()
-        self._sftp.remove(path)
+        try:
+            self._sftp.remove(path)
+        except IOError as exc:
+            if exc.errno != errno.ENOENT:
+                raise
 
     def download(self, src, dest, no_progress_bar=False, progress_title=None):
         self._sftp_connect()
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -208,10 +208,12 @@ def _changed_deps(self):
             return True
 
         for dep in self.deps:
-            if dep.changed():
+            status = dep.status()
+            if status:
                 logger.warning(
-                    "Dependency '{dep}' of '{stage}' changed.".format(
-                        dep=dep, stage=self.relpath
+                    "Dependency '{dep}' of '{stage}' changed because it is "
+                    "'{status}'.".format(
+                        dep=dep, stage=self.relpath, status=status[str(dep)]
                     )
                 )
                 return True
@@ -220,10 +222,12 @@ def _changed_deps(self):
 
     def _changed_outs(self):
         for out in self.outs:
-            if out.changed():
+            status = out.status()
+            if status:
                 logger.warning(
-                    "Output '{out}' of '{stage}' changed.".format(
-                        out=out, stage=self.relpath
+                    "Output '{out}' of '{stage}' changed because it is "
+                    "'{status}'".format(
+                        out=out, stage=self.relpath, status=status[str(out)]
                     )
                 )
                 return True
@@ -253,10 +257,13 @@ def changed(self):
         return ret
 
     def remove_outs(self, ignore_remove=False):
-        """
-        Used mainly for `dvc remove --outs`
-        """
+        """Used mainly for `dvc remove --outs` and :func:`Stage.reproduce`."""
         for out in self.outs:
+            logger.debug(
+                "Removing output '{out}' of '{stage}'.".format(
+                    out=out, stage=self.relpath
+                )
+            )
             out.remove(ignore_remove=ignore_remove)
 
     def unprotect_outs(self):


Test patch: