Repository: iterative/dvc

Issue:
dvc run does not include folder in .gitignore
I have a project that has been working properly. It's fundamentally based on pipeline.
Before when I created the pipeline the models folder was added to .gitignore, however now I can't make it ignore the models folder. Here is the pipeline:

```
dvc add data

dvc run --no-exec \
    -f train.dvc \
    -d code/train.py \
    -d data/train \
    -o models \
    -M metrics/train.json \
    python code/train.py

dvc run --no-exec \
    -f eval.dvc \
    -d code/eval.py \
    -d data/test \
    -d models \
    -M metrics/eval.json \
    python code/eval.py
```

now .gitignore contains:
```
/data
```
however I expect /models to be there as it was (as specified by -o)

**Please provide information about your setup**
dvc 0.87.0
Python 3.6.9
ubuntu 10.04



Discussion:

Patch:
diff --git a/dvc/output/base.py b/dvc/output/base.py
index 8b57517f8..442c33c3d 100644
--- a/dvc/output/base.py
+++ b/dvc/output/base.py
@@ -209,6 +209,15 @@ class BaseOutput(object):
     def isfile(self):
         return self.remote.isfile(self.path_info)
 
+    def ignore(self):
+        if not self.use_scm_ignore:
+            return
+
+        if self.repo.scm.is_tracked(self.fspath):
+            raise OutputAlreadyTrackedError(self)
+
+        self.repo.scm.ignore(self.fspath)
+
     def save(self):
         if not self.exists:
             raise self.DoesNotExistError(self)
@@ -219,11 +228,7 @@ class BaseOutput(object):
         if self.is_empty:
             logger.warning("'{}' is empty.".format(self))
 
-        if self.use_scm_ignore:
-            if self.repo.scm.is_tracked(self.fspath):
-                raise OutputAlreadyTrackedError(self)
-
-            self.repo.scm.ignore(self.fspath)
+        self.ignore()
 
         if not self.use_cache:
             self.info = self.save_info()
diff --git a/dvc/repo/run.py b/dvc/repo/run.py
index 14042b00e..65bef89d7 100644
--- a/dvc/repo/run.py
+++ b/dvc/repo/run.py
@@ -67,10 +67,14 @@ def run(self, fname=None, no_exec=False, **kwargs):
     except OutputDuplicationError as exc:
         raise OutputDuplicationError(exc.output, set(exc.stages) - {stage})
 
-    if not no_exec:
+    if no_exec:
+        for out in stage.outs:
+            out.ignore()
+    else:
         stage.run(
             no_commit=kwargs.get("no_commit", False),
             ignore_build_cache=kwargs.get("ignore_build_cache", False),
         )
+
     dvcfile.dump(stage, update_pipeline=True)
     return stage


Test patch:
diff --git a/tests/func/test_run_single_stage.py b/tests/func/test_run_single_stage.py
index 049aa5ed0..1270439db 100644
--- a/tests/func/test_run_single_stage.py
+++ b/tests/func/test_run_single_stage.py
@@ -30,7 +30,7 @@ from dvc.stage.exceptions import (
 from dvc.system import System
 from dvc.utils import file_md5
 from dvc.utils.stage import load_stage_file
-from tests.basic_env import TestDvc
+from tests.basic_env import TestDvc, TestDvcGit
 
 
 class TestRun(TestDvc):
@@ -90,13 +90,17 @@ class TestRunMissingDep(TestDvc):
             )
 
 
-class TestRunNoExec(TestDvc):
+class TestRunNoExec(TestDvcGit):
     def test(self):
         self.dvc.run(
             cmd="python {} {} {}".format(self.CODE, self.FOO, "out"),
+            deps=[self.CODE, self.FOO],
+            outs=["out"],
             no_exec=True,
         )
         self.assertFalse(os.path.exists("out"))
+        with open(".gitignore", "r") as fobj:
+            self.assertEqual(fobj.read(), "/out\n")
 
 
 class TestRunCircularDependency(TestDvc):