We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Add a way to suppress dvc changes to .gitignore
At the moment, DVC make changes to the .gitignore after commands like `dvc add`. There are cases, like mine, where it's not needed as .gitignore already exclude whole directory where data is kept.
Add a configuration that allow to suppress dvc changes to .gitignore.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dvc/scm/git/__init__.py`
Content:
```
1 """Manages Git."""
2 
3 import logging
4 import os
5 
6 from funcy import cached_property
7 from pathspec.patterns import GitWildMatchPattern
8 
9 from dvc.exceptions import GitHookAlreadyExistsError
10 from dvc.scm.base import Base
11 from dvc.scm.base import CloneError
12 from dvc.scm.base import FileNotInRepoError
13 from dvc.scm.base import RevError
14 from dvc.scm.base import SCMError
15 from dvc.scm.git.tree import GitTree
16 from dvc.utils import fix_env
17 from dvc.utils import is_binary
18 from dvc.utils import relpath
19 from dvc.utils.fs import path_isin
20 
21 
22 logger = logging.getLogger(__name__)
23 
24 
25 class Git(Base):
26     """Class for managing Git."""
27 
28     GITIGNORE = ".gitignore"
29     GIT_DIR = ".git"
30 
31     def __init__(self, root_dir=os.curdir):
32         """Git class constructor.
33         Requires `Repo` class from `git` module (from gitpython package).
34         """
35         super().__init__(root_dir)
36 
37         import git
38         from git.exc import InvalidGitRepositoryError
39 
40         try:
41             self.repo = git.Repo(self.root_dir)
42         except InvalidGitRepositoryError:
43             msg = "{} is not a git repository"
44             raise SCMError(msg.format(self.root_dir))
45 
46         # NOTE: fixing LD_LIBRARY_PATH for binary built by PyInstaller.
47         # http://pyinstaller.readthedocs.io/en/stable/runtime-information.html
48         env = fix_env(None)
49         libpath = env.get("LD_LIBRARY_PATH", None)
50         self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)
51 
52         self.ignored_paths = []
53         self.files_to_track = set()
54 
55     @staticmethod
56     def clone(url, to_path, rev=None):
57         import git
58 
59         ld_key = "LD_LIBRARY_PATH"
60 
61         env = fix_env(None)
62         if is_binary() and ld_key not in env.keys():
63             # In fix_env, we delete LD_LIBRARY_PATH key if it was empty before
64             # PyInstaller modified it. GitPython, in git.Repo.clone_from, uses
65             # env to update its own internal state. When there is no key in
66             # env, this value is not updated and GitPython re-uses
67             # LD_LIBRARY_PATH that has been set by PyInstaller.
68             # See [1] for more info.
69             # [1] https://github.com/gitpython-developers/GitPython/issues/924
70             env[ld_key] = ""
71 
72         try:
73             tmp_repo = git.Repo.clone_from(
74                 url,
75                 to_path,
76                 env=env,  # needed before we can fix it in __init__
77                 no_single_branch=True,
78             )
79             tmp_repo.close()
80         except git.exc.GitCommandError as exc:
81             raise CloneError(url, to_path) from exc
82 
83         # NOTE: using our wrapper to make sure that env is fixed in __init__
84         repo = Git(to_path)
85 
86         if rev:
87             try:
88                 repo.checkout(rev)
89             except git.exc.GitCommandError as exc:
90                 raise RevError(
91                     "failed to access revision '{}' for repo '{}'".format(
92                         rev, url
93                     )
94                 ) from exc
95 
96         return repo
97 
98     @staticmethod
99     def is_repo(root_dir):
100         return os.path.isdir(Git._get_git_dir(root_dir))
101 
102     @staticmethod
103     def is_submodule(root_dir):
104         return os.path.isfile(Git._get_git_dir(root_dir))
105 
106     @staticmethod
107     def _get_git_dir(root_dir):
108         return os.path.join(root_dir, Git.GIT_DIR)
109 
110     @property
111     def dir(self):
112         return self.repo.git_dir
113 
114     @property
115     def ignore_file(self):
116         return self.GITIGNORE
117 
118     def _get_gitignore(self, path):
119         ignore_file_dir = os.path.dirname(path)
120 
121         assert os.path.isabs(path)
122         assert os.path.isabs(ignore_file_dir)
123 
124         entry = relpath(path, ignore_file_dir).replace(os.sep, "/")
125         # NOTE: using '/' prefix to make path unambiguous
126         if len(entry) > 0 and entry[0] != "/":
127             entry = "/" + entry
128 
129         gitignore = os.path.join(ignore_file_dir, self.GITIGNORE)
130 
131         if not path_isin(gitignore, os.path.realpath(self.root_dir)):
132             raise FileNotInRepoError(path)
133 
134         return entry, gitignore
135 
136     @staticmethod
137     def _ignored(entry, gitignore_path):
138         if os.path.exists(gitignore_path):
139             with open(gitignore_path, "r") as fobj:
140                 ignore_list = fobj.readlines()
141             return any(
142                 filter(lambda x: x.strip() == entry.strip(), ignore_list)
143             )
144         return False
145 
146     def ignore(self, path):
147         entry, gitignore = self._get_gitignore(path)
148 
149         if self._ignored(entry, gitignore):
150             return
151 
152         msg = "Adding '{}' to '{}'.".format(relpath(path), relpath(gitignore))
153         logger.debug(msg)
154 
155         self._add_entry_to_gitignore(entry, gitignore)
156 
157         self.track_file(relpath(gitignore))
158 
159         self.ignored_paths.append(path)
160 
161     def _add_entry_to_gitignore(self, entry, gitignore):
162         entry = GitWildMatchPattern.escape(entry)
163 
164         with open(gitignore, "a+", encoding="utf-8") as fobj:
165             fobj.seek(0, os.SEEK_END)
166             if fobj.tell() == 0:
167                 # Empty file
168                 prefix = ""
169             else:
170                 fobj.seek(fobj.tell() - 1, os.SEEK_SET)
171                 last = fobj.read(1)
172                 prefix = "" if last == "\n" else "\n"
173             fobj.write("{}{}\n".format(prefix, entry))
174 
175     def ignore_remove(self, path):
176         entry, gitignore = self._get_gitignore(path)
177 
178         if not os.path.exists(gitignore):
179             return
180 
181         with open(gitignore, "r") as fobj:
182             lines = fobj.readlines()
183 
184         filtered = list(filter(lambda x: x.strip() != entry.strip(), lines))
185 
186         with open(gitignore, "w") as fobj:
187             fobj.writelines(filtered)
188 
189         self.track_file(relpath(gitignore))
190 
191     def add(self, paths):
192         # NOTE: GitPython is not currently able to handle index version >= 3.
193         # See https://github.com/iterative/dvc/issues/610 for more details.
194         try:
195             self.repo.index.add(paths)
196         except AssertionError:
197             msg = (
198                 "failed to add '{}' to git. You can add those files "
199                 "manually using `git add`. See "
200                 "https://github.com/iterative/dvc/issues/610 for more "
201                 "details.".format(str(paths))
202             )
203 
204             logger.exception(msg)
205 
206     def commit(self, msg):
207         self.repo.index.commit(msg)
208 
209     def checkout(self, branch, create_new=False):
210         if create_new:
211             self.repo.git.checkout("HEAD", b=branch)
212         else:
213             self.repo.git.checkout(branch)
214 
215     def pull(self):
216         info, = self.repo.remote().pull()
217         if info.flags & info.ERROR:
218             raise SCMError("pull failed: {}".format(info.note))
219 
220     def push(self):
221         info, = self.repo.remote().push()
222         if info.flags & info.ERROR:
223             raise SCMError("push failed: {}".format(info.summary))
224 
225     def branch(self, branch):
226         self.repo.git.branch(branch)
227 
228     def tag(self, tag):
229         self.repo.git.tag(tag)
230 
231     def untracked_files(self):
232         files = self.repo.untracked_files
233         return [os.path.join(self.repo.working_dir, fname) for fname in files]
234 
235     def is_tracked(self, path):
236         # it is equivalent to `bool(self.repo.git.ls_files(path))` by
237         # functionality, but ls_files fails on unicode filenames
238         path = relpath(path, self.root_dir)
239         # There are 4 stages, see BaseIndexEntry.stage
240         return any((path, i) in self.repo.index.entries for i in (0, 1, 2, 3))
241 
242     def is_dirty(self):
243         return self.repo.is_dirty()
244 
245     def active_branch(self):
246         return self.repo.active_branch.name
247 
248     def list_branches(self):
249         return [h.name for h in self.repo.heads]
250 
251     def list_tags(self):
252         return [t.name for t in self.repo.tags]
253 
254     def list_all_commits(self):
255         return [c.hexsha for c in self.repo.iter_commits("--all")]
256 
257     def _install_hook(self, name, preconditions, cmd):
258         # only run in dvc repo
259         in_dvc_repo = '[ -n "$(git ls-files --full-name .dvc)" ]'
260 
261         command = "if {}; then exec dvc {}; fi".format(
262             " && ".join([in_dvc_repo] + preconditions), cmd
263         )
264 
265         hook = self._hook_path(name)
266 
267         if os.path.isfile(hook):
268             with open(hook, "r+") as fobj:
269                 if command not in fobj.read():
270                     fobj.write("{command}\n".format(command=command))
271         else:
272             with open(hook, "w+") as fobj:
273                 fobj.write("#!/bin/sh\n" "{command}\n".format(command=command))
274 
275         os.chmod(hook, 0o777)
276 
277     def install(self):
278         self._verify_dvc_hooks()
279 
280         self._install_hook(
281             "post-checkout",
282             [
283                 # checking out some reference and not specific file.
284                 '[ "$3" = "1" ]',
285                 # make sure we are not in the middle of a rebase/merge, so we
286                 # don't accidentally break it with an unsuccessful checkout.
287                 # Note that git hooks are always running in repo root.
288                 "[ ! -d .git/rebase-merge ]",
289             ],
290             "checkout",
291         )
292         self._install_hook("pre-commit", [], "status")
293         self._install_hook("pre-push", [], "push")
294 
295     def cleanup_ignores(self):
296         for path in self.ignored_paths:
297             self.ignore_remove(path)
298         self.reset_ignores()
299 
300     def reset_ignores(self):
301         self.ignored_paths = []
302 
303     def reset_tracked_files(self):
304         self.files_to_track = set()
305 
306     def remind_to_track(self):
307         if not self.files_to_track:
308             return
309 
310         logger.info(
311             "\n"
312             "To track the changes with git, run:\n"
313             "\n"
314             "\tgit add {files}".format(files=" ".join(self.files_to_track))
315         )
316 
317     def track_file(self, path):
318         self.files_to_track.add(path)
319 
320     def belongs_to_scm(self, path):
321         basename = os.path.basename(path)
322         path_parts = os.path.normpath(path).split(os.path.sep)
323         return basename == self.ignore_file or Git.GIT_DIR in path_parts
324 
325     def get_tree(self, rev):
326         return GitTree(self.repo, self.resolve_rev(rev))
327 
328     def get_rev(self):
329         return self.repo.git.rev_parse("HEAD")
330 
331     def resolve_rev(self, rev):
332         from git.exc import GitCommandError
333 
334         try:
335             return self.repo.git.rev_parse(rev)
336         except GitCommandError:
337             raise RevError("unknown Git revision '{}'".format(rev))
338 
339     def close(self):
340         self.repo.close()
341 
342     @cached_property
343     def _hooks_home(self):
344         return os.path.join(self.root_dir, self.GIT_DIR, "hooks")
345 
346     def _hook_path(self, name):
347         return os.path.join(self._hooks_home, name)
348 
349     def _verify_hook(self, name):
350         if os.path.exists(self._hook_path(name)):
351             raise GitHookAlreadyExistsError(name)
352 
353     def _verify_dvc_hooks(self):
354         self._verify_hook("post-checkout")
355         self._verify_hook("pre-commit")
356         self._verify_hook("pre-push")
357
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py
--- a/dvc/scm/git/__init__.py
+++ b/dvc/scm/git/__init__.py
@@ -18,7 +18,6 @@
 from dvc.utils import relpath
 from dvc.utils.fs import path_isin
 
-
 logger = logging.getLogger(__name__)
 
 
@@ -133,20 +132,19 @@
 
         return entry, gitignore
 
-    @staticmethod
-    def _ignored(entry, gitignore_path):
-        if os.path.exists(gitignore_path):
-            with open(gitignore_path, "r") as fobj:
-                ignore_list = fobj.readlines()
-            return any(
-                filter(lambda x: x.strip() == entry.strip(), ignore_list)
-            )
-        return False
+    def _ignored(self, path):
+        from git.exc import GitCommandError
+
+        try:
+            self.repo.git.check_ignore(path)
+            return True
+        except GitCommandError:
+            return False
 
     def ignore(self, path):
         entry, gitignore = self._get_gitignore(path)
 
-        if self._ignored(entry, gitignore):
+        if self._ignored(path):
             return
 
         msg = "Adding '{}' to '{}'.".format(relpath(path), relpath(gitignore))

{"golden_diff": "diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py\n--- a/dvc/scm/git/__init__.py\n+++ b/dvc/scm/git/__init__.py\n@@ -18,7 +18,6 @@\n from dvc.utils import relpath\n from dvc.utils.fs import path_isin\n \n-\n logger = logging.getLogger(__name__)\n \n \n@@ -133,20 +132,19 @@\n \n         return entry, gitignore\n \n-    @staticmethod\n-    def _ignored(entry, gitignore_path):\n-        if os.path.exists(gitignore_path):\n-            with open(gitignore_path, \"r\") as fobj:\n-                ignore_list = fobj.readlines()\n-            return any(\n-                filter(lambda x: x.strip() == entry.strip(), ignore_list)\n-            )\n-        return False\n+    def _ignored(self, path):\n+        from git.exc import GitCommandError\n+\n+        try:\n+            self.repo.git.check_ignore(path)\n+            return True\n+        except GitCommandError:\n+            return False\n \n     def ignore(self, path):\n         entry, gitignore = self._get_gitignore(path)\n \n-        if self._ignored(entry, gitignore):\n+        if self._ignored(path):\n             return\n \n         msg = \"Adding '{}' to '{}'.\".format(relpath(path), relpath(gitignore))\n", "issue": "Add a way to suppress dvc changes to .gitignore\nAt the moment, DVC make changes to the .gitignore after commands like `dvc add`. There are cases, like mine, where it's not needed as .gitignore already exclude whole directory where data is kept.\r\nAdd a configuration that allow to suppress dvc changes to .gitignore.\n", "before_files": [{"content": "\"\"\"Manages Git.\"\"\"\n\nimport logging\nimport os\n\nfrom funcy import cached_property\nfrom pathspec.patterns import GitWildMatchPattern\n\nfrom dvc.exceptions import GitHookAlreadyExistsError\nfrom dvc.scm.base import Base\nfrom dvc.scm.base import CloneError\nfrom dvc.scm.base import FileNotInRepoError\nfrom dvc.scm.base import RevError\nfrom dvc.scm.base import SCMError\nfrom dvc.scm.git.tree import GitTree\nfrom dvc.utils import fix_env\nfrom dvc.utils import is_binary\nfrom dvc.utils import relpath\nfrom dvc.utils.fs import path_isin\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass Git(Base):\n    \"\"\"Class for managing Git.\"\"\"\n\n    GITIGNORE = \".gitignore\"\n    GIT_DIR = \".git\"\n\n    def __init__(self, root_dir=os.curdir):\n        \"\"\"Git class constructor.\n        Requires `Repo` class from `git` module (from gitpython package).\n        \"\"\"\n        super().__init__(root_dir)\n\n        import git\n        from git.exc import InvalidGitRepositoryError\n\n        try:\n            self.repo = git.Repo(self.root_dir)\n        except InvalidGitRepositoryError:\n            msg = \"{} is not a git repository\"\n            raise SCMError(msg.format(self.root_dir))\n\n        # NOTE: fixing LD_LIBRARY_PATH for binary built by PyInstaller.\n        # http://pyinstaller.readthedocs.io/en/stable/runtime-information.html\n        env = fix_env(None)\n        libpath = env.get(\"LD_LIBRARY_PATH\", None)\n        self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)\n\n        self.ignored_paths = []\n        self.files_to_track = set()\n\n    @staticmethod\n    def clone(url, to_path, rev=None):\n        import git\n\n        ld_key = \"LD_LIBRARY_PATH\"\n\n        env = fix_env(None)\n        if is_binary() and ld_key not in env.keys():\n            # In fix_env, we delete LD_LIBRARY_PATH key if it was empty before\n            # PyInstaller modified it. GitPython, in git.Repo.clone_from, uses\n            # env to update its own internal state. When there is no key in\n            # env, this value is not updated and GitPython re-uses\n            # LD_LIBRARY_PATH that has been set by PyInstaller.\n            # See [1] for more info.\n            # [1] https://github.com/gitpython-developers/GitPython/issues/924\n            env[ld_key] = \"\"\n\n        try:\n            tmp_repo = git.Repo.clone_from(\n                url,\n                to_path,\n                env=env,  # needed before we can fix it in __init__\n                no_single_branch=True,\n            )\n            tmp_repo.close()\n        except git.exc.GitCommandError as exc:\n            raise CloneError(url, to_path) from exc\n\n        # NOTE: using our wrapper to make sure that env is fixed in __init__\n        repo = Git(to_path)\n\n        if rev:\n            try:\n                repo.checkout(rev)\n            except git.exc.GitCommandError as exc:\n                raise RevError(\n                    \"failed to access revision '{}' for repo '{}'\".format(\n                        rev, url\n                    )\n                ) from exc\n\n        return repo\n\n    @staticmethod\n    def is_repo(root_dir):\n        return os.path.isdir(Git._get_git_dir(root_dir))\n\n    @staticmethod\n    def is_submodule(root_dir):\n        return os.path.isfile(Git._get_git_dir(root_dir))\n\n    @staticmethod\n    def _get_git_dir(root_dir):\n        return os.path.join(root_dir, Git.GIT_DIR)\n\n    @property\n    def dir(self):\n        return self.repo.git_dir\n\n    @property\n    def ignore_file(self):\n        return self.GITIGNORE\n\n    def _get_gitignore(self, path):\n        ignore_file_dir = os.path.dirname(path)\n\n        assert os.path.isabs(path)\n        assert os.path.isabs(ignore_file_dir)\n\n        entry = relpath(path, ignore_file_dir).replace(os.sep, \"/\")\n        # NOTE: using '/' prefix to make path unambiguous\n        if len(entry) > 0 and entry[0] != \"/\":\n            entry = \"/\" + entry\n\n        gitignore = os.path.join(ignore_file_dir, self.GITIGNORE)\n\n        if not path_isin(gitignore, os.path.realpath(self.root_dir)):\n            raise FileNotInRepoError(path)\n\n        return entry, gitignore\n\n    @staticmethod\n    def _ignored(entry, gitignore_path):\n        if os.path.exists(gitignore_path):\n            with open(gitignore_path, \"r\") as fobj:\n                ignore_list = fobj.readlines()\n            return any(\n                filter(lambda x: x.strip() == entry.strip(), ignore_list)\n            )\n        return False\n\n    def ignore(self, path):\n        entry, gitignore = self._get_gitignore(path)\n\n        if self._ignored(entry, gitignore):\n            return\n\n        msg = \"Adding '{}' to '{}'.\".format(relpath(path), relpath(gitignore))\n        logger.debug(msg)\n\n        self._add_entry_to_gitignore(entry, gitignore)\n\n        self.track_file(relpath(gitignore))\n\n        self.ignored_paths.append(path)\n\n    def _add_entry_to_gitignore(self, entry, gitignore):\n        entry = GitWildMatchPattern.escape(entry)\n\n        with open(gitignore, \"a+\", encoding=\"utf-8\") as fobj:\n            fobj.seek(0, os.SEEK_END)\n            if fobj.tell() == 0:\n                # Empty file\n                prefix = \"\"\n            else:\n                fobj.seek(fobj.tell() - 1, os.SEEK_SET)\n                last = fobj.read(1)\n                prefix = \"\" if last == \"\\n\" else \"\\n\"\n            fobj.write(\"{}{}\\n\".format(prefix, entry))\n\n    def ignore_remove(self, path):\n        entry, gitignore = self._get_gitignore(path)\n\n        if not os.path.exists(gitignore):\n            return\n\n        with open(gitignore, \"r\") as fobj:\n            lines = fobj.readlines()\n\n        filtered = list(filter(lambda x: x.strip() != entry.strip(), lines))\n\n        with open(gitignore, \"w\") as fobj:\n            fobj.writelines(filtered)\n\n        self.track_file(relpath(gitignore))\n\n    def add(self, paths):\n        # NOTE: GitPython is not currently able to handle index version >= 3.\n        # See https://github.com/iterative/dvc/issues/610 for more details.\n        try:\n            self.repo.index.add(paths)\n        except AssertionError:\n            msg = (\n                \"failed to add '{}' to git. You can add those files \"\n                \"manually using `git add`. See \"\n                \"https://github.com/iterative/dvc/issues/610 for more \"\n                \"details.\".format(str(paths))\n            )\n\n            logger.exception(msg)\n\n    def commit(self, msg):\n        self.repo.index.commit(msg)\n\n    def checkout(self, branch, create_new=False):\n        if create_new:\n            self.repo.git.checkout(\"HEAD\", b=branch)\n        else:\n            self.repo.git.checkout(branch)\n\n    def pull(self):\n        info, = self.repo.remote().pull()\n        if info.flags & info.ERROR:\n            raise SCMError(\"pull failed: {}\".format(info.note))\n\n    def push(self):\n        info, = self.repo.remote().push()\n        if info.flags & info.ERROR:\n            raise SCMError(\"push failed: {}\".format(info.summary))\n\n    def branch(self, branch):\n        self.repo.git.branch(branch)\n\n    def tag(self, tag):\n        self.repo.git.tag(tag)\n\n    def untracked_files(self):\n        files = self.repo.untracked_files\n        return [os.path.join(self.repo.working_dir, fname) for fname in files]\n\n    def is_tracked(self, path):\n        # it is equivalent to `bool(self.repo.git.ls_files(path))` by\n        # functionality, but ls_files fails on unicode filenames\n        path = relpath(path, self.root_dir)\n        # There are 4 stages, see BaseIndexEntry.stage\n        return any((path, i) in self.repo.index.entries for i in (0, 1, 2, 3))\n\n    def is_dirty(self):\n        return self.repo.is_dirty()\n\n    def active_branch(self):\n        return self.repo.active_branch.name\n\n    def list_branches(self):\n        return [h.name for h in self.repo.heads]\n\n    def list_tags(self):\n        return [t.name for t in self.repo.tags]\n\n    def list_all_commits(self):\n        return [c.hexsha for c in self.repo.iter_commits(\"--all\")]\n\n    def _install_hook(self, name, preconditions, cmd):\n        # only run in dvc repo\n        in_dvc_repo = '[ -n \"$(git ls-files --full-name .dvc)\" ]'\n\n        command = \"if {}; then exec dvc {}; fi\".format(\n            \" && \".join([in_dvc_repo] + preconditions), cmd\n        )\n\n        hook = self._hook_path(name)\n\n        if os.path.isfile(hook):\n            with open(hook, \"r+\") as fobj:\n                if command not in fobj.read():\n                    fobj.write(\"{command}\\n\".format(command=command))\n        else:\n            with open(hook, \"w+\") as fobj:\n                fobj.write(\"#!/bin/sh\\n\" \"{command}\\n\".format(command=command))\n\n        os.chmod(hook, 0o777)\n\n    def install(self):\n        self._verify_dvc_hooks()\n\n        self._install_hook(\n            \"post-checkout\",\n            [\n                # checking out some reference and not specific file.\n                '[ \"$3\" = \"1\" ]',\n                # make sure we are not in the middle of a rebase/merge, so we\n                # don't accidentally break it with an unsuccessful checkout.\n                # Note that git hooks are always running in repo root.\n                \"[ ! -d .git/rebase-merge ]\",\n            ],\n            \"checkout\",\n        )\n        self._install_hook(\"pre-commit\", [], \"status\")\n        self._install_hook(\"pre-push\", [], \"push\")\n\n    def cleanup_ignores(self):\n        for path in self.ignored_paths:\n            self.ignore_remove(path)\n        self.reset_ignores()\n\n    def reset_ignores(self):\n        self.ignored_paths = []\n\n    def reset_tracked_files(self):\n        self.files_to_track = set()\n\n    def remind_to_track(self):\n        if not self.files_to_track:\n            return\n\n        logger.info(\n            \"\\n\"\n            \"To track the changes with git, run:\\n\"\n            \"\\n\"\n            \"\\tgit add {files}\".format(files=\" \".join(self.files_to_track))\n        )\n\n    def track_file(self, path):\n        self.files_to_track.add(path)\n\n    def belongs_to_scm(self, path):\n        basename = os.path.basename(path)\n        path_parts = os.path.normpath(path).split(os.path.sep)\n        return basename == self.ignore_file or Git.GIT_DIR in path_parts\n\n    def get_tree(self, rev):\n        return GitTree(self.repo, self.resolve_rev(rev))\n\n    def get_rev(self):\n        return self.repo.git.rev_parse(\"HEAD\")\n\n    def resolve_rev(self, rev):\n        from git.exc import GitCommandError\n\n        try:\n            return self.repo.git.rev_parse(rev)\n        except GitCommandError:\n            raise RevError(\"unknown Git revision '{}'\".format(rev))\n\n    def close(self):\n        self.repo.close()\n\n    @cached_property\n    def _hooks_home(self):\n        return os.path.join(self.root_dir, self.GIT_DIR, \"hooks\")\n\n    def _hook_path(self, name):\n        return os.path.join(self._hooks_home, name)\n\n    def _verify_hook(self, name):\n        if os.path.exists(self._hook_path(name)):\n            raise GitHookAlreadyExistsError(name)\n\n    def _verify_dvc_hooks(self):\n        self._verify_hook(\"post-checkout\")\n        self._verify_hook(\"pre-commit\")\n        self._verify_hook(\"pre-push\")\n", "path": "dvc/scm/git/__init__.py"}], "after_files": [{"content": "\"\"\"Manages Git.\"\"\"\n\nimport logging\nimport os\n\nfrom funcy import cached_property\nfrom pathspec.patterns import GitWildMatchPattern\n\nfrom dvc.exceptions import GitHookAlreadyExistsError\nfrom dvc.scm.base import Base\nfrom dvc.scm.base import CloneError\nfrom dvc.scm.base import FileNotInRepoError\nfrom dvc.scm.base import RevError\nfrom dvc.scm.base import SCMError\nfrom dvc.scm.git.tree import GitTree\nfrom dvc.utils import fix_env\nfrom dvc.utils import is_binary\nfrom dvc.utils import relpath\nfrom dvc.utils.fs import path_isin\n\nlogger = logging.getLogger(__name__)\n\n\nclass Git(Base):\n    \"\"\"Class for managing Git.\"\"\"\n\n    GITIGNORE = \".gitignore\"\n    GIT_DIR = \".git\"\n\n    def __init__(self, root_dir=os.curdir):\n        \"\"\"Git class constructor.\n        Requires `Repo` class from `git` module (from gitpython package).\n        \"\"\"\n        super().__init__(root_dir)\n\n        import git\n        from git.exc import InvalidGitRepositoryError\n\n        try:\n            self.repo = git.Repo(self.root_dir)\n        except InvalidGitRepositoryError:\n            msg = \"{} is not a git repository\"\n            raise SCMError(msg.format(self.root_dir))\n\n        # NOTE: fixing LD_LIBRARY_PATH for binary built by PyInstaller.\n        # http://pyinstaller.readthedocs.io/en/stable/runtime-information.html\n        env = fix_env(None)\n        libpath = env.get(\"LD_LIBRARY_PATH\", None)\n        self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)\n\n        self.ignored_paths = []\n        self.files_to_track = set()\n\n    @staticmethod\n    def clone(url, to_path, rev=None):\n        import git\n\n        ld_key = \"LD_LIBRARY_PATH\"\n\n        env = fix_env(None)\n        if is_binary() and ld_key not in env.keys():\n            # In fix_env, we delete LD_LIBRARY_PATH key if it was empty before\n            # PyInstaller modified it. GitPython, in git.Repo.clone_from, uses\n            # env to update its own internal state. When there is no key in\n            # env, this value is not updated and GitPython re-uses\n            # LD_LIBRARY_PATH that has been set by PyInstaller.\n            # See [1] for more info.\n            # [1] https://github.com/gitpython-developers/GitPython/issues/924\n            env[ld_key] = \"\"\n\n        try:\n            tmp_repo = git.Repo.clone_from(\n                url,\n                to_path,\n                env=env,  # needed before we can fix it in __init__\n                no_single_branch=True,\n            )\n            tmp_repo.close()\n        except git.exc.GitCommandError as exc:\n            raise CloneError(url, to_path) from exc\n\n        # NOTE: using our wrapper to make sure that env is fixed in __init__\n        repo = Git(to_path)\n\n        if rev:\n            try:\n                repo.checkout(rev)\n            except git.exc.GitCommandError as exc:\n                raise RevError(\n                    \"failed to access revision '{}' for repo '{}'\".format(\n                        rev, url\n                    )\n                ) from exc\n\n        return repo\n\n    @staticmethod\n    def is_repo(root_dir):\n        return os.path.isdir(Git._get_git_dir(root_dir))\n\n    @staticmethod\n    def is_submodule(root_dir):\n        return os.path.isfile(Git._get_git_dir(root_dir))\n\n    @staticmethod\n    def _get_git_dir(root_dir):\n        return os.path.join(root_dir, Git.GIT_DIR)\n\n    @property\n    def dir(self):\n        return self.repo.git_dir\n\n    @property\n    def ignore_file(self):\n        return self.GITIGNORE\n\n    def _get_gitignore(self, path):\n        ignore_file_dir = os.path.dirname(path)\n\n        assert os.path.isabs(path)\n        assert os.path.isabs(ignore_file_dir)\n\n        entry = relpath(path, ignore_file_dir).replace(os.sep, \"/\")\n        # NOTE: using '/' prefix to make path unambiguous\n        if len(entry) > 0 and entry[0] != \"/\":\n            entry = \"/\" + entry\n\n        gitignore = os.path.join(ignore_file_dir, self.GITIGNORE)\n\n        if not path_isin(gitignore, os.path.realpath(self.root_dir)):\n            raise FileNotInRepoError(path)\n\n        return entry, gitignore\n\n    def _ignored(self, path):\n        from git.exc import GitCommandError\n\n        try:\n            self.repo.git.check_ignore(path)\n            return True\n        except GitCommandError:\n            return False\n\n    def ignore(self, path):\n        entry, gitignore = self._get_gitignore(path)\n\n        if self._ignored(path):\n            return\n\n        msg = \"Adding '{}' to '{}'.\".format(relpath(path), relpath(gitignore))\n        logger.debug(msg)\n\n        self._add_entry_to_gitignore(entry, gitignore)\n\n        self.track_file(relpath(gitignore))\n\n        self.ignored_paths.append(path)\n\n    def _add_entry_to_gitignore(self, entry, gitignore):\n        entry = GitWildMatchPattern.escape(entry)\n\n        with open(gitignore, \"a+\", encoding=\"utf-8\") as fobj:\n            fobj.seek(0, os.SEEK_END)\n            if fobj.tell() == 0:\n                # Empty file\n                prefix = \"\"\n            else:\n                fobj.seek(fobj.tell() - 1, os.SEEK_SET)\n                last = fobj.read(1)\n                prefix = \"\" if last == \"\\n\" else \"\\n\"\n            fobj.write(\"{}{}\\n\".format(prefix, entry))\n\n    def ignore_remove(self, path):\n        entry, gitignore = self._get_gitignore(path)\n\n        if not os.path.exists(gitignore):\n            return\n\n        with open(gitignore, \"r\") as fobj:\n            lines = fobj.readlines()\n\n        filtered = list(filter(lambda x: x.strip() != entry.strip(), lines))\n\n        with open(gitignore, \"w\") as fobj:\n            fobj.writelines(filtered)\n\n        self.track_file(relpath(gitignore))\n\n    def add(self, paths):\n        # NOTE: GitPython is not currently able to handle index version >= 3.\n        # See https://github.com/iterative/dvc/issues/610 for more details.\n        try:\n            self.repo.index.add(paths)\n        except AssertionError:\n            msg = (\n                \"failed to add '{}' to git. You can add those files \"\n                \"manually using `git add`. See \"\n                \"https://github.com/iterative/dvc/issues/610 for more \"\n                \"details.\".format(str(paths))\n            )\n\n            logger.exception(msg)\n\n    def commit(self, msg):\n        self.repo.index.commit(msg)\n\n    def checkout(self, branch, create_new=False):\n        if create_new:\n            self.repo.git.checkout(\"HEAD\", b=branch)\n        else:\n            self.repo.git.checkout(branch)\n\n    def pull(self):\n        info, = self.repo.remote().pull()\n        if info.flags & info.ERROR:\n            raise SCMError(\"pull failed: {}\".format(info.note))\n\n    def push(self):\n        info, = self.repo.remote().push()\n        if info.flags & info.ERROR:\n            raise SCMError(\"push failed: {}\".format(info.summary))\n\n    def branch(self, branch):\n        self.repo.git.branch(branch)\n\n    def tag(self, tag):\n        self.repo.git.tag(tag)\n\n    def untracked_files(self):\n        files = self.repo.untracked_files\n        return [os.path.join(self.repo.working_dir, fname) for fname in files]\n\n    def is_tracked(self, path):\n        # it is equivalent to `bool(self.repo.git.ls_files(path))` by\n        # functionality, but ls_files fails on unicode filenames\n        path = relpath(path, self.root_dir)\n        # There are 4 stages, see BaseIndexEntry.stage\n        return any((path, i) in self.repo.index.entries for i in (0, 1, 2, 3))\n\n    def is_dirty(self):\n        return self.repo.is_dirty()\n\n    def active_branch(self):\n        return self.repo.active_branch.name\n\n    def list_branches(self):\n        return [h.name for h in self.repo.heads]\n\n    def list_tags(self):\n        return [t.name for t in self.repo.tags]\n\n    def list_all_commits(self):\n        return [c.hexsha for c in self.repo.iter_commits(\"--all\")]\n\n    def _install_hook(self, name, preconditions, cmd):\n        # only run in dvc repo\n        in_dvc_repo = '[ -n \"$(git ls-files --full-name .dvc)\" ]'\n\n        command = \"if {}; then exec dvc {}; fi\".format(\n            \" && \".join([in_dvc_repo] + preconditions), cmd\n        )\n\n        hook = self._hook_path(name)\n\n        if os.path.isfile(hook):\n            with open(hook, \"r+\") as fobj:\n                if command not in fobj.read():\n                    fobj.write(\"{command}\\n\".format(command=command))\n        else:\n            with open(hook, \"w+\") as fobj:\n                fobj.write(\"#!/bin/sh\\n\" \"{command}\\n\".format(command=command))\n\n        os.chmod(hook, 0o777)\n\n    def install(self):\n        self._verify_dvc_hooks()\n\n        self._install_hook(\n            \"post-checkout\",\n            [\n                # checking out some reference and not specific file.\n                '[ \"$3\" = \"1\" ]',\n                # make sure we are not in the middle of a rebase/merge, so we\n                # don't accidentally break it with an unsuccessful checkout.\n                # Note that git hooks are always running in repo root.\n                \"[ ! -d .git/rebase-merge ]\",\n            ],\n            \"checkout\",\n        )\n        self._install_hook(\"pre-commit\", [], \"status\")\n        self._install_hook(\"pre-push\", [], \"push\")\n\n    def cleanup_ignores(self):\n        for path in self.ignored_paths:\n            self.ignore_remove(path)\n        self.reset_ignores()\n\n    def reset_ignores(self):\n        self.ignored_paths = []\n\n    def reset_tracked_files(self):\n        self.files_to_track = set()\n\n    def remind_to_track(self):\n        if not self.files_to_track:\n            return\n\n        logger.info(\n            \"\\n\"\n            \"To track the changes with git, run:\\n\"\n            \"\\n\"\n            \"\\tgit add {files}\".format(files=\" \".join(self.files_to_track))\n        )\n\n    def track_file(self, path):\n        self.files_to_track.add(path)\n\n    def belongs_to_scm(self, path):\n        basename = os.path.basename(path)\n        path_parts = os.path.normpath(path).split(os.path.sep)\n        return basename == self.ignore_file or Git.GIT_DIR in path_parts\n\n    def get_tree(self, rev):\n        return GitTree(self.repo, self.resolve_rev(rev))\n\n    def get_rev(self):\n        return self.repo.git.rev_parse(\"HEAD\")\n\n    def resolve_rev(self, rev):\n        from git.exc import GitCommandError\n\n        try:\n            return self.repo.git.rev_parse(rev)\n        except GitCommandError:\n            raise RevError(\"unknown Git revision '{}'\".format(rev))\n\n    def close(self):\n        self.repo.close()\n\n    @cached_property\n    def _hooks_home(self):\n        return os.path.join(self.root_dir, self.GIT_DIR, \"hooks\")\n\n    def _hook_path(self, name):\n        return os.path.join(self._hooks_home, name)\n\n    def _verify_hook(self, name):\n        if os.path.exists(self._hook_path(name)):\n            raise GitHookAlreadyExistsError(name)\n\n    def _verify_dvc_hooks(self):\n        self._verify_hook(\"post-checkout\")\n        self._verify_hook(\"pre-commit\")\n        self._verify_hook(\"pre-push\")\n", "path": "dvc/scm/git/__init__.py"}]}