We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
dvc run: regular output files should not be in .gitignore
Please add a corresponded unit-test. See examples in class `TestGitIgnoreWhenCheckout` file `test_checkout.py`.

--- 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/stage.py`
Content:
```
1 import os
2 import stat
3 import yaml
4 import itertools
5 
6 from dvc.system import System
7 from dvc.utils import file_md5
8 from dvc.exceptions import DvcException
9 from dvc.executor import Executor
10 
11 
12 class OutputError(DvcException):
13     pass
14 
15 
16 class MissingDataSource(OutputError):
17     def __init__(self, missing_files):
18         assert len(missing_files) > 0
19 
20         source = 'source'
21         if len(missing_files) > 1:
22             source += 's'
23 
24         msg = u'missing data {}: {}'.format(source, ', '.join(missing_files))
25         super(MissingDataSource, self).__init__(msg)
26 
27 
28 class CmdOutputError(DvcException):
29     def __init__(self, path, msg):
30         super(CmdOutputError, self).__init__('Output file \'{}\' error: {}'.format(path, msg))
31 
32 
33 class CmdOutputNoCacheError(CmdOutputError):
34     def __init__(self, path):
35         super(CmdOutputNoCacheError, self).__init__(path, 'no cache')
36 
37 
38 class CmdOutputOutsideOfRepoError(CmdOutputError):
39     def __init__(self, path):
40         super(CmdOutputOutsideOfRepoError, self).__init__(path, 'outside of repository')
41 
42 
43 class CmdOutputDoesNotExistError(CmdOutputError):
44     def __init__(self, path):
45         super(CmdOutputDoesNotExistError, self).__init__(path, 'does not exist')
46 
47 
48 class CmdOutputIsNotFileError(CmdOutputError):
49     def __init__(self, path):
50         super(CmdOutputIsNotFileError, self).__init__(path, 'not a file')
51 
52 
53 class CmdOutputAlreadyTrackedError(CmdOutputError):
54     def __init__(self, path):
55         super(CmdOutputAlreadyTrackedError, self).__init__(path, 'already tracked by scm(e.g. git)')
56 
57 
58 class Dependency(object):
59     PARAM_PATH = 'path'
60     PARAM_MD5 = 'md5'
61 
62     def __init__(self, project, path, md5=None):
63         self.project = project
64         self.path = os.path.abspath(os.path.realpath(path))
65 
66         if not self.path.startswith(self.project.root_dir):
67             raise CmdOutputOutsideOfRepoError(self.path)
68 
69         self.md5 = md5
70 
71     @property
72     def dvc_path(self):
73         return os.path.relpath(self.path, self.project.root_dir)
74 
75     @property
76     def rel_path(self):
77         return os.path.relpath(self.path, '.')
78 
79     def _changed_md5(self):
80         if not os.path.exists(self.path):
81             return True
82 
83         state = self.project.state.get(self.path)
84         if state and state.mtime == self.mtime():
85             md5 = state.md5
86         else:
87             md5 = file_md5(self.path)[0]
88 
89         return self.md5 != md5
90 
91     def changed(self):
92         return self._changed_md5()
93 
94     def mtime(self):
95         return os.path.getmtime(self.path)
96 
97     def inode(self):
98         return os.stat(self.path).st_ino
99 
100     def update(self):
101         if not os.path.exists(self.path):
102             raise CmdOutputDoesNotExistError(self.rel_path)
103         if not os.path.isfile(self.path):
104             raise CmdOutputIsNotFileError(self.path)
105 
106         state = self.project.state.get(self.path)
107         if state and state.mtime == self.mtime() and state.inode == self.inode():
108             md5 = state.md5
109             msg = '{} using md5 {} from state file'
110             self.project.logger.debug(msg.format(self.path, md5))
111             self.md5 = md5
112         else:
113             self.md5 = file_md5(self.path)[0]
114             self.project.state.update(self.path, self.md5, self.mtime(), self.inode())
115 
116     def dumpd(self, cwd):
117         return {
118             Output.PARAM_PATH: os.path.relpath(self.path, cwd),
119             Output.PARAM_MD5: self.md5,
120         }
121 
122     @classmethod
123     def loadd(cls, project, d, cwd=os.curdir):
124         path = os.path.join(cwd, d[Output.PARAM_PATH])
125         md5 = d.get(Output.PARAM_MD5, None)
126         return cls(project, path, md5=md5)
127 
128     @classmethod
129     def loadd_from(cls, project, d_list, cwd=os.curdir):
130         return [cls.loadd(project, x, cwd=cwd) for x in d_list]
131 
132     @classmethod
133     def loads(cls, project, s, cwd=os.curdir):
134         return cls(project, os.path.join(cwd, s), md5=None)
135 
136     @classmethod
137     def loads_from(cls, project, s_list, cwd=os.curdir):
138         return [cls.loads(project, x, cwd=cwd) for x in s_list]
139 
140     def stage(self):
141         for stage in self.project.stages():
142             for out in stage.outs:
143                 if self.path == out.path:
144                     return stage
145         return None
146 
147 
148 class Output(Dependency):
149     PARAM_CACHE = 'cache'
150 
151     def __init__(self, project, path, md5=None, use_cache=True):
152         super(Output, self).__init__(project, path, md5=md5)
153         self.use_cache = use_cache
154 
155     @property
156     def cache(self):
157         return self.project.cache.get(self.md5)
158 
159     def dumpd(self, cwd):
160         ret = super(Output, self).dumpd(cwd)
161         ret[Output.PARAM_CACHE] = self.use_cache
162         return ret
163 
164     @classmethod
165     def loadd(cls, project, d, cwd=os.curdir):
166         ret = super(Output, cls).loadd(project, d, cwd=cwd)
167         ret.use_cache = d.get(Output.PARAM_CACHE, True)
168         return ret
169 
170     @classmethod
171     def loads(cls, project, s, use_cache=True, cwd=os.curdir):
172         ret = super(Output, cls).loads(project, s, cwd=cwd)
173         ret.use_cache = use_cache
174         return ret
175 
176     @classmethod
177     def loads_from(cls, project, s_list, use_cache=False, cwd=os.curdir):
178         return [cls.loads(project, x, use_cache=use_cache, cwd=cwd) for x in s_list]
179 
180     def changed(self):
181         if not self.use_cache:
182             return super(Output, self).changed()
183 
184         return not os.path.exists(self.path) or \
185                not os.path.exists(self.cache) or \
186                not System.samefile(self.path, self.cache)
187 
188     def link(self, checkout=False):
189         if not self.use_cache:
190             raise CmdOutputNoCacheError(self.path)
191 
192         if not os.path.exists(self.path) and not os.path.exists(self.cache):
193             raise CmdOutputNoCacheError(self.path)
194 
195         if os.path.exists(self.path) and \
196            os.path.exists(self.cache) and \
197            System.samefile(self.path, self.cache) and \
198            os.stat(self.cache).st_mode & stat.S_IREAD:
199             return
200 
201         if os.path.exists(self.cache):
202             if os.path.exists(self.path):
203                 # This means that we already have cache for this data.
204                 # We remove data and link it to existing cache to save
205                 # some space.
206                 self.remove()
207             src = self.cache
208             link = self.path
209         elif not checkout:
210             src = self.path
211             link = self.cache
212         else:
213             raise CmdOutputNoCacheError(self.path)
214 
215         System.hardlink(src, link)
216 
217         os.chmod(self.path, stat.S_IREAD)
218 
219     def checkout(self):
220         if not self.use_cache:
221             return
222         if not os.path.exists(self.cache):
223             self.project.logger.warn(u'\'{}\': cache file not found'.format(self.dvc_path))
224             self.remove()
225         else:
226             self.link(checkout=True)
227 
228     def save(self):
229         if not self.use_cache:
230             return
231 
232         if self.project.scm.is_tracked(self.path):
233             raise CmdOutputAlreadyTrackedError(self.path)
234 
235         self.link()
236 
237     def remove(self):
238         if not os.path.exists(self.path):
239             return
240 
241         self.project.logger.debug("Removing '{}'".format(self.path))
242         os.chmod(self.path, stat.S_IWUSR)
243         os.unlink(self.path)
244         if os.path.exists(self.cache):
245             os.chmod(self.cache, stat.S_IREAD)
246 
247 
248 class Stage(object):
249     STAGE_FILE = 'Dvcfile'
250     STAGE_FILE_SUFFIX = '.dvc'
251 
252     PARAM_CMD = 'cmd'
253     PARAM_DEPS = 'deps'
254     PARAM_OUTS = 'outs'
255 
256     def __init__(self, project, path=None, cmd=None, cwd=None, deps=[], outs=[]):
257         self.project = project
258         self.path = path
259         self.cmd = cmd
260         self.cwd = cwd
261         self.outs = outs
262         self.deps = deps
263 
264     @property
265     def relpath(self):
266         return os.path.relpath(self.path)
267 
268     @property
269     def dvc_path(self):
270         return os.path.relpath(self.path, self.project.root_dir)
271 
272     @property
273     def is_data_source(self):
274         return self.cmd is None
275 
276     @staticmethod
277     def is_stage_file(path):
278         if not os.path.isfile(path):
279             return False
280 
281         if not path.endswith(Stage.STAGE_FILE_SUFFIX) and os.path.basename(path) != Stage.STAGE_FILE:
282             return False
283 
284         return True
285 
286     def changed(self):
287         for entry in itertools.chain(self.outs, self.deps):
288             if entry.changed():
289                 self.project.logger.debug("{} changed".format(self.path))
290                 return True
291         return False
292 
293     def remove_outs(self):
294         for out in self.outs:
295             out.remove()
296             self.project.scm.ignore_remove(out.path)
297 
298     def remove(self):
299         self.remove_outs()
300         os.unlink(self.path)
301 
302     def reproduce(self, force=False):
303         if not self.changed() and not force:
304             return
305 
306         if self.cmd:
307             # Removing outputs only if we actually have command to reproduce
308             self.remove_outs()
309 
310         self.run()
311 
312     @staticmethod
313     def loadd(project, d, path):
314         path = os.path.abspath(path)
315         cwd = os.path.dirname(path)
316         cmd = d[Stage.PARAM_CMD]
317         deps = Dependency.loadd_from(project, d[Stage.PARAM_DEPS], cwd=cwd)
318         outs = Output.loadd_from(project, d[Stage.PARAM_OUTS], cwd=cwd)
319 
320         return Stage(project=project,
321                      path=path,
322                      cmd=cmd,
323                      cwd=cwd,
324                      deps=deps,
325                      outs=outs)
326 
327     @staticmethod
328     def load(project, fname):
329         with open(fname, 'r') as fd:
330             return Stage.loadd(project, yaml.safe_load(fd), fname)
331 
332     def dumpd(self):
333         deps = [x.dumpd(self.cwd) for x in self.deps]
334         outs = [x.dumpd(self.cwd) for x in self.outs]
335 
336         return {
337             Stage.PARAM_CMD: self.cmd,
338             Stage.PARAM_DEPS: deps,
339             Stage.PARAM_OUTS: outs,
340         }
341 
342     def dump(self, fname=None):
343         if not fname:
344             fname = self.path
345 
346         with open(fname, 'w') as fd:
347             yaml.safe_dump(self.dumpd(), fd, default_flow_style=False)
348 
349     def save(self):
350         for dep in self.deps:
351             dep.update()
352 
353         for out in self.outs:
354             out.update()
355             out.save()
356             self.project.scm.ignore(out.path)
357 
358     def run(self):
359         if not self.is_data_source:
360             self.project.logger.info(u'Reproducing \'{}\':\n\t{}'.format(self.relpath, self.cmd))
361 
362             Executor.exec_cmd_only_success(self.cmd, cwd=str(self.cwd), shell=True)
363             self.save()
364 
365             self.project.logger.debug(u'\'{}\' was reproduced'.format(self.relpath))
366         else:
367             self.project.logger.info(u'Verifying data sources in \'{}\''.format(self.relpath))
368             self.check_missing_outputs()
369             self.save()
370 
371     def check_missing_outputs(self):
372         missing_outs = [out.rel_path for out in self.outs if not os.path.exists(out.rel_path)]
373         if missing_outs:
374             raise MissingDataSource(missing_outs)
375 
376     def checkout(self):
377         for out in self.outs:
378             out.checkout()
379
```
--- 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/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -293,7 +293,8 @@
     def remove_outs(self):
         for out in self.outs:
             out.remove()
-            self.project.scm.ignore_remove(out.path)
+            if out.use_cache:
+                self.project.scm.ignore_remove(out.path)
 
     def remove(self):
         self.remove_outs()
@@ -353,7 +354,8 @@
         for out in self.outs:
             out.update()
             out.save()
-            self.project.scm.ignore(out.path)
+            if out.use_cache:
+                self.project.scm.ignore(out.path)
 
     def run(self):
         if not self.is_data_source:

{"golden_diff": "diff --git a/dvc/stage.py b/dvc/stage.py\n--- a/dvc/stage.py\n+++ b/dvc/stage.py\n@@ -293,7 +293,8 @@\n     def remove_outs(self):\n         for out in self.outs:\n             out.remove()\n-            self.project.scm.ignore_remove(out.path)\n+            if out.use_cache:\n+                self.project.scm.ignore_remove(out.path)\n \n     def remove(self):\n         self.remove_outs()\n@@ -353,7 +354,8 @@\n         for out in self.outs:\n             out.update()\n             out.save()\n-            self.project.scm.ignore(out.path)\n+            if out.use_cache:\n+                self.project.scm.ignore(out.path)\n \n     def run(self):\n         if not self.is_data_source:\n", "issue": "dvc run: regular output files should not be in .gitignore\nPlease add a corresponded unit-test. See examples in class `TestGitIgnoreWhenCheckout` file `test_checkout.py`.\n", "before_files": [{"content": "import os\nimport stat\nimport yaml\nimport itertools\n\nfrom dvc.system import System\nfrom dvc.utils import file_md5\nfrom dvc.exceptions import DvcException\nfrom dvc.executor import Executor\n\n\nclass OutputError(DvcException):\n    pass\n\n\nclass MissingDataSource(OutputError):\n    def __init__(self, missing_files):\n        assert len(missing_files) > 0\n\n        source = 'source'\n        if len(missing_files) > 1:\n            source += 's'\n\n        msg = u'missing data {}: {}'.format(source, ', '.join(missing_files))\n        super(MissingDataSource, self).__init__(msg)\n\n\nclass CmdOutputError(DvcException):\n    def __init__(self, path, msg):\n        super(CmdOutputError, self).__init__('Output file \\'{}\\' error: {}'.format(path, msg))\n\n\nclass CmdOutputNoCacheError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputNoCacheError, self).__init__(path, 'no cache')\n\n\nclass CmdOutputOutsideOfRepoError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputOutsideOfRepoError, self).__init__(path, 'outside of repository')\n\n\nclass CmdOutputDoesNotExistError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputDoesNotExistError, self).__init__(path, 'does not exist')\n\n\nclass CmdOutputIsNotFileError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputIsNotFileError, self).__init__(path, 'not a file')\n\n\nclass CmdOutputAlreadyTrackedError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputAlreadyTrackedError, self).__init__(path, 'already tracked by scm(e.g. git)')\n\n\nclass Dependency(object):\n    PARAM_PATH = 'path'\n    PARAM_MD5 = 'md5'\n\n    def __init__(self, project, path, md5=None):\n        self.project = project\n        self.path = os.path.abspath(os.path.realpath(path))\n\n        if not self.path.startswith(self.project.root_dir):\n            raise CmdOutputOutsideOfRepoError(self.path)\n\n        self.md5 = md5\n\n    @property\n    def dvc_path(self):\n        return os.path.relpath(self.path, self.project.root_dir)\n\n    @property\n    def rel_path(self):\n        return os.path.relpath(self.path, '.')\n\n    def _changed_md5(self):\n        if not os.path.exists(self.path):\n            return True\n\n        state = self.project.state.get(self.path)\n        if state and state.mtime == self.mtime():\n            md5 = state.md5\n        else:\n            md5 = file_md5(self.path)[0]\n\n        return self.md5 != md5\n\n    def changed(self):\n        return self._changed_md5()\n\n    def mtime(self):\n        return os.path.getmtime(self.path)\n\n    def inode(self):\n        return os.stat(self.path).st_ino\n\n    def update(self):\n        if not os.path.exists(self.path):\n            raise CmdOutputDoesNotExistError(self.rel_path)\n        if not os.path.isfile(self.path):\n            raise CmdOutputIsNotFileError(self.path)\n\n        state = self.project.state.get(self.path)\n        if state and state.mtime == self.mtime() and state.inode == self.inode():\n            md5 = state.md5\n            msg = '{} using md5 {} from state file'\n            self.project.logger.debug(msg.format(self.path, md5))\n            self.md5 = md5\n        else:\n            self.md5 = file_md5(self.path)[0]\n            self.project.state.update(self.path, self.md5, self.mtime(), self.inode())\n\n    def dumpd(self, cwd):\n        return {\n            Output.PARAM_PATH: os.path.relpath(self.path, cwd),\n            Output.PARAM_MD5: self.md5,\n        }\n\n    @classmethod\n    def loadd(cls, project, d, cwd=os.curdir):\n        path = os.path.join(cwd, d[Output.PARAM_PATH])\n        md5 = d.get(Output.PARAM_MD5, None)\n        return cls(project, path, md5=md5)\n\n    @classmethod\n    def loadd_from(cls, project, d_list, cwd=os.curdir):\n        return [cls.loadd(project, x, cwd=cwd) for x in d_list]\n\n    @classmethod\n    def loads(cls, project, s, cwd=os.curdir):\n        return cls(project, os.path.join(cwd, s), md5=None)\n\n    @classmethod\n    def loads_from(cls, project, s_list, cwd=os.curdir):\n        return [cls.loads(project, x, cwd=cwd) for x in s_list]\n\n    def stage(self):\n        for stage in self.project.stages():\n            for out in stage.outs:\n                if self.path == out.path:\n                    return stage\n        return None\n\n\nclass Output(Dependency):\n    PARAM_CACHE = 'cache'\n\n    def __init__(self, project, path, md5=None, use_cache=True):\n        super(Output, self).__init__(project, path, md5=md5)\n        self.use_cache = use_cache\n\n    @property\n    def cache(self):\n        return self.project.cache.get(self.md5)\n\n    def dumpd(self, cwd):\n        ret = super(Output, self).dumpd(cwd)\n        ret[Output.PARAM_CACHE] = self.use_cache\n        return ret\n\n    @classmethod\n    def loadd(cls, project, d, cwd=os.curdir):\n        ret = super(Output, cls).loadd(project, d, cwd=cwd)\n        ret.use_cache = d.get(Output.PARAM_CACHE, True)\n        return ret\n\n    @classmethod\n    def loads(cls, project, s, use_cache=True, cwd=os.curdir):\n        ret = super(Output, cls).loads(project, s, cwd=cwd)\n        ret.use_cache = use_cache\n        return ret\n\n    @classmethod\n    def loads_from(cls, project, s_list, use_cache=False, cwd=os.curdir):\n        return [cls.loads(project, x, use_cache=use_cache, cwd=cwd) for x in s_list]\n\n    def changed(self):\n        if not self.use_cache:\n            return super(Output, self).changed()\n\n        return not os.path.exists(self.path) or \\\n               not os.path.exists(self.cache) or \\\n               not System.samefile(self.path, self.cache)\n\n    def link(self, checkout=False):\n        if not self.use_cache:\n            raise CmdOutputNoCacheError(self.path)\n\n        if not os.path.exists(self.path) and not os.path.exists(self.cache):\n            raise CmdOutputNoCacheError(self.path)\n\n        if os.path.exists(self.path) and \\\n           os.path.exists(self.cache) and \\\n           System.samefile(self.path, self.cache) and \\\n           os.stat(self.cache).st_mode & stat.S_IREAD:\n            return\n\n        if os.path.exists(self.cache):\n            if os.path.exists(self.path):\n                # This means that we already have cache for this data.\n                # We remove data and link it to existing cache to save\n                # some space.\n                self.remove()\n            src = self.cache\n            link = self.path\n        elif not checkout:\n            src = self.path\n            link = self.cache\n        else:\n            raise CmdOutputNoCacheError(self.path)\n\n        System.hardlink(src, link)\n\n        os.chmod(self.path, stat.S_IREAD)\n\n    def checkout(self):\n        if not self.use_cache:\n            return\n        if not os.path.exists(self.cache):\n            self.project.logger.warn(u'\\'{}\\': cache file not found'.format(self.dvc_path))\n            self.remove()\n        else:\n            self.link(checkout=True)\n\n    def save(self):\n        if not self.use_cache:\n            return\n\n        if self.project.scm.is_tracked(self.path):\n            raise CmdOutputAlreadyTrackedError(self.path)\n\n        self.link()\n\n    def remove(self):\n        if not os.path.exists(self.path):\n            return\n\n        self.project.logger.debug(\"Removing '{}'\".format(self.path))\n        os.chmod(self.path, stat.S_IWUSR)\n        os.unlink(self.path)\n        if os.path.exists(self.cache):\n            os.chmod(self.cache, stat.S_IREAD)\n\n\nclass Stage(object):\n    STAGE_FILE = 'Dvcfile'\n    STAGE_FILE_SUFFIX = '.dvc'\n\n    PARAM_CMD = 'cmd'\n    PARAM_DEPS = 'deps'\n    PARAM_OUTS = 'outs'\n\n    def __init__(self, project, path=None, cmd=None, cwd=None, deps=[], outs=[]):\n        self.project = project\n        self.path = path\n        self.cmd = cmd\n        self.cwd = cwd\n        self.outs = outs\n        self.deps = deps\n\n    @property\n    def relpath(self):\n        return os.path.relpath(self.path)\n\n    @property\n    def dvc_path(self):\n        return os.path.relpath(self.path, self.project.root_dir)\n\n    @property\n    def is_data_source(self):\n        return self.cmd is None\n\n    @staticmethod\n    def is_stage_file(path):\n        if not os.path.isfile(path):\n            return False\n\n        if not path.endswith(Stage.STAGE_FILE_SUFFIX) and os.path.basename(path) != Stage.STAGE_FILE:\n            return False\n\n        return True\n\n    def changed(self):\n        for entry in itertools.chain(self.outs, self.deps):\n            if entry.changed():\n                self.project.logger.debug(\"{} changed\".format(self.path))\n                return True\n        return False\n\n    def remove_outs(self):\n        for out in self.outs:\n            out.remove()\n            self.project.scm.ignore_remove(out.path)\n\n    def remove(self):\n        self.remove_outs()\n        os.unlink(self.path)\n\n    def reproduce(self, force=False):\n        if not self.changed() and not force:\n            return\n\n        if self.cmd:\n            # Removing outputs only if we actually have command to reproduce\n            self.remove_outs()\n\n        self.run()\n\n    @staticmethod\n    def loadd(project, d, path):\n        path = os.path.abspath(path)\n        cwd = os.path.dirname(path)\n        cmd = d[Stage.PARAM_CMD]\n        deps = Dependency.loadd_from(project, d[Stage.PARAM_DEPS], cwd=cwd)\n        outs = Output.loadd_from(project, d[Stage.PARAM_OUTS], cwd=cwd)\n\n        return Stage(project=project,\n                     path=path,\n                     cmd=cmd,\n                     cwd=cwd,\n                     deps=deps,\n                     outs=outs)\n\n    @staticmethod\n    def load(project, fname):\n        with open(fname, 'r') as fd:\n            return Stage.loadd(project, yaml.safe_load(fd), fname)\n\n    def dumpd(self):\n        deps = [x.dumpd(self.cwd) for x in self.deps]\n        outs = [x.dumpd(self.cwd) for x in self.outs]\n\n        return {\n            Stage.PARAM_CMD: self.cmd,\n            Stage.PARAM_DEPS: deps,\n            Stage.PARAM_OUTS: outs,\n        }\n\n    def dump(self, fname=None):\n        if not fname:\n            fname = self.path\n\n        with open(fname, 'w') as fd:\n            yaml.safe_dump(self.dumpd(), fd, default_flow_style=False)\n\n    def save(self):\n        for dep in self.deps:\n            dep.update()\n\n        for out in self.outs:\n            out.update()\n            out.save()\n            self.project.scm.ignore(out.path)\n\n    def run(self):\n        if not self.is_data_source:\n            self.project.logger.info(u'Reproducing \\'{}\\':\\n\\t{}'.format(self.relpath, self.cmd))\n\n            Executor.exec_cmd_only_success(self.cmd, cwd=str(self.cwd), shell=True)\n            self.save()\n\n            self.project.logger.debug(u'\\'{}\\' was reproduced'.format(self.relpath))\n        else:\n            self.project.logger.info(u'Verifying data sources in \\'{}\\''.format(self.relpath))\n            self.check_missing_outputs()\n            self.save()\n\n    def check_missing_outputs(self):\n        missing_outs = [out.rel_path for out in self.outs if not os.path.exists(out.rel_path)]\n        if missing_outs:\n            raise MissingDataSource(missing_outs)\n\n    def checkout(self):\n        for out in self.outs:\n            out.checkout()\n", "path": "dvc/stage.py"}], "after_files": [{"content": "import os\nimport stat\nimport yaml\nimport itertools\n\nfrom dvc.system import System\nfrom dvc.utils import file_md5\nfrom dvc.exceptions import DvcException\nfrom dvc.executor import Executor\n\n\nclass OutputError(DvcException):\n    pass\n\n\nclass MissingDataSource(OutputError):\n    def __init__(self, missing_files):\n        assert len(missing_files) > 0\n\n        source = 'source'\n        if len(missing_files) > 1:\n            source += 's'\n\n        msg = u'missing data {}: {}'.format(source, ', '.join(missing_files))\n        super(MissingDataSource, self).__init__(msg)\n\n\nclass CmdOutputError(DvcException):\n    def __init__(self, path, msg):\n        super(CmdOutputError, self).__init__('Output file \\'{}\\' error: {}'.format(path, msg))\n\n\nclass CmdOutputNoCacheError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputNoCacheError, self).__init__(path, 'no cache')\n\n\nclass CmdOutputOutsideOfRepoError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputOutsideOfRepoError, self).__init__(path, 'outside of repository')\n\n\nclass CmdOutputDoesNotExistError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputDoesNotExistError, self).__init__(path, 'does not exist')\n\n\nclass CmdOutputIsNotFileError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputIsNotFileError, self).__init__(path, 'not a file')\n\n\nclass CmdOutputAlreadyTrackedError(CmdOutputError):\n    def __init__(self, path):\n        super(CmdOutputAlreadyTrackedError, self).__init__(path, 'already tracked by scm(e.g. git)')\n\n\nclass Dependency(object):\n    PARAM_PATH = 'path'\n    PARAM_MD5 = 'md5'\n\n    def __init__(self, project, path, md5=None):\n        self.project = project\n        self.path = os.path.abspath(os.path.realpath(path))\n\n        if not self.path.startswith(self.project.root_dir):\n            raise CmdOutputOutsideOfRepoError(self.path)\n\n        self.md5 = md5\n\n    @property\n    def dvc_path(self):\n        return os.path.relpath(self.path, self.project.root_dir)\n\n    @property\n    def rel_path(self):\n        return os.path.relpath(self.path, '.')\n\n    def _changed_md5(self):\n        if not os.path.exists(self.path):\n            return True\n\n        state = self.project.state.get(self.path)\n        if state and state.mtime == self.mtime():\n            md5 = state.md5\n        else:\n            md5 = file_md5(self.path)[0]\n\n        return self.md5 != md5\n\n    def changed(self):\n        return self._changed_md5()\n\n    def mtime(self):\n        return os.path.getmtime(self.path)\n\n    def inode(self):\n        return os.stat(self.path).st_ino\n\n    def update(self):\n        if not os.path.exists(self.path):\n            raise CmdOutputDoesNotExistError(self.rel_path)\n        if not os.path.isfile(self.path):\n            raise CmdOutputIsNotFileError(self.path)\n\n        state = self.project.state.get(self.path)\n        if state and state.mtime == self.mtime() and state.inode == self.inode():\n            md5 = state.md5\n            msg = '{} using md5 {} from state file'\n            self.project.logger.debug(msg.format(self.path, md5))\n            self.md5 = md5\n        else:\n            self.md5 = file_md5(self.path)[0]\n            self.project.state.update(self.path, self.md5, self.mtime(), self.inode())\n\n    def dumpd(self, cwd):\n        return {\n            Output.PARAM_PATH: os.path.relpath(self.path, cwd),\n            Output.PARAM_MD5: self.md5,\n        }\n\n    @classmethod\n    def loadd(cls, project, d, cwd=os.curdir):\n        path = os.path.join(cwd, d[Output.PARAM_PATH])\n        md5 = d.get(Output.PARAM_MD5, None)\n        return cls(project, path, md5=md5)\n\n    @classmethod\n    def loadd_from(cls, project, d_list, cwd=os.curdir):\n        return [cls.loadd(project, x, cwd=cwd) for x in d_list]\n\n    @classmethod\n    def loads(cls, project, s, cwd=os.curdir):\n        return cls(project, os.path.join(cwd, s), md5=None)\n\n    @classmethod\n    def loads_from(cls, project, s_list, cwd=os.curdir):\n        return [cls.loads(project, x, cwd=cwd) for x in s_list]\n\n    def stage(self):\n        for stage in self.project.stages():\n            for out in stage.outs:\n                if self.path == out.path:\n                    return stage\n        return None\n\n\nclass Output(Dependency):\n    PARAM_CACHE = 'cache'\n\n    def __init__(self, project, path, md5=None, use_cache=True):\n        super(Output, self).__init__(project, path, md5=md5)\n        self.use_cache = use_cache\n\n    @property\n    def cache(self):\n        return self.project.cache.get(self.md5)\n\n    def dumpd(self, cwd):\n        ret = super(Output, self).dumpd(cwd)\n        ret[Output.PARAM_CACHE] = self.use_cache\n        return ret\n\n    @classmethod\n    def loadd(cls, project, d, cwd=os.curdir):\n        ret = super(Output, cls).loadd(project, d, cwd=cwd)\n        ret.use_cache = d.get(Output.PARAM_CACHE, True)\n        return ret\n\n    @classmethod\n    def loads(cls, project, s, use_cache=True, cwd=os.curdir):\n        ret = super(Output, cls).loads(project, s, cwd=cwd)\n        ret.use_cache = use_cache\n        return ret\n\n    @classmethod\n    def loads_from(cls, project, s_list, use_cache=False, cwd=os.curdir):\n        return [cls.loads(project, x, use_cache=use_cache, cwd=cwd) for x in s_list]\n\n    def changed(self):\n        if not self.use_cache:\n            return super(Output, self).changed()\n\n        return not os.path.exists(self.path) or \\\n               not os.path.exists(self.cache) or \\\n               not System.samefile(self.path, self.cache)\n\n    def link(self, checkout=False):\n        if not self.use_cache:\n            raise CmdOutputNoCacheError(self.path)\n\n        if not os.path.exists(self.path) and not os.path.exists(self.cache):\n            raise CmdOutputNoCacheError(self.path)\n\n        if os.path.exists(self.path) and \\\n           os.path.exists(self.cache) and \\\n           System.samefile(self.path, self.cache) and \\\n           os.stat(self.cache).st_mode & stat.S_IREAD:\n            return\n\n        if os.path.exists(self.cache):\n            if os.path.exists(self.path):\n                # This means that we already have cache for this data.\n                # We remove data and link it to existing cache to save\n                # some space.\n                self.remove()\n            src = self.cache\n            link = self.path\n        elif not checkout:\n            src = self.path\n            link = self.cache\n        else:\n            raise CmdOutputNoCacheError(self.path)\n\n        System.hardlink(src, link)\n\n        os.chmod(self.path, stat.S_IREAD)\n\n    def checkout(self):\n        if not self.use_cache:\n            return\n        if not os.path.exists(self.cache):\n            self.project.logger.warn(u'\\'{}\\': cache file not found'.format(self.dvc_path))\n            self.remove()\n        else:\n            self.link(checkout=True)\n\n    def save(self):\n        if not self.use_cache:\n            return\n\n        if self.project.scm.is_tracked(self.path):\n            raise CmdOutputAlreadyTrackedError(self.path)\n\n        self.link()\n\n    def remove(self):\n        if not os.path.exists(self.path):\n            return\n\n        self.project.logger.debug(\"Removing '{}'\".format(self.path))\n        os.chmod(self.path, stat.S_IWUSR)\n        os.unlink(self.path)\n        if os.path.exists(self.cache):\n            os.chmod(self.cache, stat.S_IREAD)\n\n\nclass Stage(object):\n    STAGE_FILE = 'Dvcfile'\n    STAGE_FILE_SUFFIX = '.dvc'\n\n    PARAM_CMD = 'cmd'\n    PARAM_DEPS = 'deps'\n    PARAM_OUTS = 'outs'\n\n    def __init__(self, project, path=None, cmd=None, cwd=None, deps=[], outs=[]):\n        self.project = project\n        self.path = path\n        self.cmd = cmd\n        self.cwd = cwd\n        self.outs = outs\n        self.deps = deps\n\n    @property\n    def relpath(self):\n        return os.path.relpath(self.path)\n\n    @property\n    def dvc_path(self):\n        return os.path.relpath(self.path, self.project.root_dir)\n\n    @property\n    def is_data_source(self):\n        return self.cmd is None\n\n    @staticmethod\n    def is_stage_file(path):\n        if not os.path.isfile(path):\n            return False\n\n        if not path.endswith(Stage.STAGE_FILE_SUFFIX) and os.path.basename(path) != Stage.STAGE_FILE:\n            return False\n\n        return True\n\n    def changed(self):\n        for entry in itertools.chain(self.outs, self.deps):\n            if entry.changed():\n                self.project.logger.debug(\"{} changed\".format(self.path))\n                return True\n        return False\n\n    def remove_outs(self):\n        for out in self.outs:\n            out.remove()\n            if out.use_cache:\n                self.project.scm.ignore_remove(out.path)\n\n    def remove(self):\n        self.remove_outs()\n        os.unlink(self.path)\n\n    def reproduce(self, force=False):\n        if not self.changed() and not force:\n            return\n\n        if self.cmd:\n            # Removing outputs only if we actually have command to reproduce\n            self.remove_outs()\n\n        self.run()\n\n    @staticmethod\n    def loadd(project, d, path):\n        path = os.path.abspath(path)\n        cwd = os.path.dirname(path)\n        cmd = d[Stage.PARAM_CMD]\n        deps = Dependency.loadd_from(project, d[Stage.PARAM_DEPS], cwd=cwd)\n        outs = Output.loadd_from(project, d[Stage.PARAM_OUTS], cwd=cwd)\n\n        return Stage(project=project,\n                     path=path,\n                     cmd=cmd,\n                     cwd=cwd,\n                     deps=deps,\n                     outs=outs)\n\n    @staticmethod\n    def load(project, fname):\n        with open(fname, 'r') as fd:\n            return Stage.loadd(project, yaml.safe_load(fd), fname)\n\n    def dumpd(self):\n        deps = [x.dumpd(self.cwd) for x in self.deps]\n        outs = [x.dumpd(self.cwd) for x in self.outs]\n\n        return {\n            Stage.PARAM_CMD: self.cmd,\n            Stage.PARAM_DEPS: deps,\n            Stage.PARAM_OUTS: outs,\n        }\n\n    def dump(self, fname=None):\n        if not fname:\n            fname = self.path\n\n        with open(fname, 'w') as fd:\n            yaml.safe_dump(self.dumpd(), fd, default_flow_style=False)\n\n    def save(self):\n        for dep in self.deps:\n            dep.update()\n\n        for out in self.outs:\n            out.update()\n            out.save()\n            if out.use_cache:\n                self.project.scm.ignore(out.path)\n\n    def run(self):\n        if not self.is_data_source:\n            self.project.logger.info(u'Reproducing \\'{}\\':\\n\\t{}'.format(self.relpath, self.cmd))\n\n            Executor.exec_cmd_only_success(self.cmd, cwd=str(self.cwd), shell=True)\n            self.save()\n\n            self.project.logger.debug(u'\\'{}\\' was reproduced'.format(self.relpath))\n        else:\n            self.project.logger.info(u'Verifying data sources in \\'{}\\''.format(self.relpath))\n            self.check_missing_outputs()\n            self.save()\n\n    def check_missing_outputs(self):\n        missing_outs = [out.rel_path for out in self.outs if not os.path.exists(out.rel_path)]\n        if missing_outs:\n            raise MissingDataSource(missing_outs)\n\n    def checkout(self):\n        for out in self.outs:\n            out.checkout()\n", "path": "dvc/stage.py"}]}