Repository: iterative/dvc

Issue:
Introduce `--purge` option for `dvc remove`
`dvc delete stage.dvc` should remove outputs of the specified stage(same as `dvc remove`) and also delete the dvc file itself and do things like cleaning up .gitignore.

Kudos @analystanand !

Discussion:

Patch:
diff --git a/dvc/cli.py b/dvc/cli.py
index 2ba52bb3f..b40c51ed1 100644
--- a/dvc/cli.py
+++ b/dvc/cli.py
@@ -226,6 +226,17 @@ def parse_args(argv=None):
                         'remove',
                         parents=[parent_parser],
                         help='Remove outputs of DVC file.')
+    remove_parser_group = remove_parser.add_mutually_exclusive_group()
+    remove_parser_group.add_argument('-o',
+                        '--outs',
+                        action='store_true',
+                        default=True,
+                        help='Only remove DVC file outputs.')
+    remove_parser_group.add_argument('-p',
+                        '--purge',
+                        action='store_true',
+                        default=False,
+                        help='Remove DVC file and all its outputs')
     remove_parser.add_argument('targets',
                         nargs='+',
                         help='DVC files.')
diff --git a/dvc/command/remove.py b/dvc/command/remove.py
index 228e3a365..aea78dfdb 100644
--- a/dvc/command/remove.py
+++ b/dvc/command/remove.py
@@ -4,9 +4,10 @@ from dvc.command.common.base import CmdBase
 
 class CmdRemove(CmdBase):
     def run(self):
+        outs_only = not self.args.purge
         for target in self.args.targets:
             try:
-                self.project.remove(target)
+                self.project.remove(target, outs_only=outs_only)
             except DvcException as ex:
                 self.project.logger.error('Failed to remove {}', ex)
                 return 1
diff --git a/dvc/project.py b/dvc/project.py
index c6f3ae832..05a6c3d36 100644
--- a/dvc/project.py
+++ b/dvc/project.py
@@ -123,13 +123,15 @@ class Project(object):
         stage.dump()
         return stage
 
-    def remove(self, target):
+    def remove(self, target, outs_only=False):
         if not Stage.is_stage_file(target):
             raise StageNotFoundError(target)
 
         stage = Stage.load(self, target)
-        for out in stage.outs:
-            out.remove()
+        if outs_only:
+            stage.remove_outs()
+        else:
+            stage.remove()
 
         return stage
 


Test patch:
diff --git a/tests/test_remove.py b/tests/test_remove.py
index b6503feab..7ae68f6ee 100644
--- a/tests/test_remove.py
+++ b/tests/test_remove.py
@@ -11,11 +11,17 @@ from tests.basic_env import TestDvc
 class TestRemove(TestDvc):
     def test(self):
         stage = self.dvc.add(self.FOO)
-        stage_removed = self.dvc.remove(stage.path)
+        stage_removed = self.dvc.remove(stage.path, outs_only=True)
+
+        self.assertIsInstance(stage_removed, Stage)
+        self.assertEqual(stage.path, stage_removed.path)
+        self.assertFalse(os.path.isfile(self.FOO))
 
+        stage_removed = self.dvc.remove(stage.path)
         self.assertIsInstance(stage_removed, Stage)
         self.assertEqual(stage.path, stage_removed.path)
         self.assertFalse(os.path.isfile(self.FOO))
+        self.assertFalse(os.path.exists(stage.path))
 
 
 class TestRemoveNonExistentFile(TestDvc):
@@ -30,7 +36,7 @@ class TestRemoveDirectory(TestDvc):
         stage_removed = self.dvc.remove(stage_add.path)
         self.assertEqual(stage_add.path, stage_removed.path)
         self.assertFalse(os.path.exists(self.DATA_DIR))
-        self.assertTrue(os.path.exists(stage_removed.path))
+        self.assertFalse(os.path.exists(stage_removed.path))
 
 
 class TestCmdRemove(TestDvc):