Repository: iterative/dvc

Issue:
dvc add config option to auto-stage .dvc files to git
I track a lot of separate .dvc directories in a project I am working on. I would love it if there was a flag (say `-g`) that would add, after adding to dvc, the .dvc file to the current staged git commit. It's not an important feature exactly, as I could just write a script, but I think it would add value for a lot of users.

I am completely ready to dive in and attempt to draft a PR to accomplish this on my own (and write tests) but I don't have enough free time or emotional fortitude to pursue this without first opening an issue for feedback on the idea. :)


Comments:
Hi @bobertlo !

Good idea! We've had similar discussions in https://github.com/iterative/dvc/issues/1576 . Would you say that you would expect to see this option enabled by default? Or just as an explicit flag? 


Re #1576 definitely not committing. I think an explicit flag would be good. Maybe a config option would be more appropriate for enabling this mode by default. Not something you would want to flip on in a minor release at least.
@bobertlo Config option sounds very good. Could totally start with that and then consider switching to that by-default in 2.0. :+1: 

The implementation itself should be pretty simple, as we have a reminder message already with the list of edited files, so we could totally just use it to git-add something. https://github.com/iterative/dvc/blob/master/dvc/repo/scm_context.py#L6 . And the config option is easy to add in https://github.com/iterative/dvc/blob/master/dvc/config.py . Let us know if you'll have any questions :slightly_smiling_face: Check out our contrib guide https://dvc.org/doc/user-guide/contributing/core and feel free to ping us in #dev-talk channel on discord :slightly_smiling_face: Thank you for looking into this! :pray: 
Thanks for the info! I'll start working on a draft of this feature as soon as I have time.

Patch:
diff --git a/dvc/config.py b/dvc/config.py
--- a/dvc/config.py
+++ b/dvc/config.py
@@ -137,6 +137,7 @@ class RelPath(str):
         Optional("analytics", default=True): Bool,
         Optional("hardlink_lock", default=False): Bool,
         Optional("no_scm", default=False): Bool,
+        Optional("autostage", default=False): Bool,
         Optional("experiments", default=False): Bool,
         Optional("check_update", default=True): Bool,
     },
diff --git a/dvc/repo/scm_context.py b/dvc/repo/scm_context.py
--- a/dvc/repo/scm_context.py
+++ b/dvc/repo/scm_context.py
@@ -3,7 +3,11 @@ def run(repo, *args, **kw):
         try:
             result = method(repo, *args, **kw)
             repo.scm.reset_ignores()
-            repo.scm.remind_to_track()
+            autostage = repo.config.get("core", {}).get("autostage", False)
+            if autostage:
+                repo.scm.track_changed_files()
+            else:
+                repo.scm.remind_to_track()
             repo.scm.reset_tracked_files()
             return result
         except Exception:
diff --git a/dvc/scm/base.py b/dvc/scm/base.py
--- a/dvc/scm/base.py
+++ b/dvc/scm/base.py
@@ -154,6 +154,11 @@ def remind_to_track(self):
         Method to remind user to track newly created files handled by scm
         """
 
+    def track_changed_files(self):
+        """
+        Method to stage files that have changed
+        """
+
     def track_file(self, path):
         """
         Method to add file to mechanism that will remind user
diff --git a/dvc/scm/git.py b/dvc/scm/git.py
--- a/dvc/scm/git.py
+++ b/dvc/scm/git.py
@@ -381,6 +381,12 @@ def remind_to_track(self):
             "\tgit add {files}".format(files=files)
         )
 
+    def track_changed_files(self):
+        if not self.files_to_track:
+            return
+
+        self.add(self.files_to_track)
+
     def track_file(self, path):
         self.files_to_track.add(path)
 


Test patch:
diff --git a/tests/unit/scm/test_scm.py b/tests/unit/scm/test_scm.py
--- a/tests/unit/scm/test_scm.py
+++ b/tests/unit/scm/test_scm.py
@@ -17,6 +17,7 @@ def test_should_successfully_perform_method(self):
         method = mock.Mock()
         wrapped = scm_context(method)
 
+        self.repo_mock.configure_mock(config={})
         wrapped(self.repo_mock)
 
         self.assertEqual(1, method.call_count)
@@ -25,6 +26,20 @@ def test_should_successfully_perform_method(self):
 
         self.assertEqual(0, self.scm_mock.cleanup_ignores.call_count)
 
+    def test_should_check_autostage(self):
+        method = mock.Mock()
+        wrapped = scm_context(method)
+
+        config_autostage_attrs = {"config": {"core": {"autostage": True}}}
+        self.repo_mock.configure_mock(**config_autostage_attrs)
+        wrapped(self.repo_mock)
+
+        self.assertEqual(1, method.call_count)
+        self.assertEqual(1, self.scm_mock.reset_ignores.call_count)
+        self.assertEqual(1, self.scm_mock.track_changed_files.call_count)
+
+        self.assertEqual(0, self.scm_mock.cleanup_ignores.call_count)
+
     def test_should_throw_and_cleanup(self):
         method = mock.Mock(side_effect=Exception("some problem"))
         wrapped = scm_context(method)