1# -*- coding: utf-8 -*-
2
3# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:
4#Copyright (c) 2005 Ali Afshar <aafshar@gmail.com>
5
6#Permission is hereby granted, free of charge, to any person obtaining a copy
7#of this software and associated documentation files (the "Software"), to deal
8#in the Software without restriction, including without limitation the rights
9#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10#copies of the Software, and to permit persons to whom the Software is
11#furnished to do so, subject to the following conditions:
12
13#The above copyright notice and this permission notice shall be included in
14#all copies or substantial portions of the Software.
15
16#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22#SOFTWARE.
23
24import errno
25import os
26import shutil
27import subprocess
28import tempfile
29
30from . import _vc
31
32
33class Vc(_vc.CachedVc):
34
35    CMD = "darcs"
36    NAME = "Darcs"
37    VC_DIR = "_darcs"
38    state_map = {
39        "a": _vc.STATE_NONE,
40        "A": _vc.STATE_NEW,
41        "M": _vc.STATE_MODIFIED,
42        "C": _vc.STATE_CONFLICT,
43        "R": _vc.STATE_REMOVED,
44    }
45
46    def commit_command(self, message):
47        return [self.CMD, "record",
48                "--skip-long-comment",
49                "--repodir=%s" % self.root,
50                "-a",
51                "-m", message]
52
53    def update_command(self):
54        # This will not work while passing the files parameter after it
55        # This hack allows you to update in the root directory
56        return [self.CMD, "pull", "-a", "-p"]
57
58    def add_command(self):
59        return [self.CMD, "add"]
60
61    def remove_command(self, force=0):
62        return [self.CMD, "remove"]
63
64    def revert_command(self):
65        # will not work, since darcs needs interaction it seems
66        return [self.CMD, "revert", "-a"]
67
68    def resolved_command(self):
69        # untested
70        return [self.CMD, "resolve"]
71
72    def valid_repo(self):
73        if _vc.call([self.CMD, "query", "tags"], cwd=self.root):
74            return False
75        else:
76            return True
77
78    def get_working_directory(self, workdir):
79        return self.root
80
81    def get_path_for_repo_file(self, path, commit=None):
82        if commit is not None:
83            raise NotImplementedError()
84
85        if not path.startswith(self.root + os.path.sep):
86            raise _vc.InvalidVCPath(self, path, "Path not in repository")
87        path = path[len(self.root) + 1:]
88
89        process = subprocess.Popen([self.CMD, "show", "contents",
90                                    "--repodir=" + self.root, path],
91                                   cwd=self.root, stdout=subprocess.PIPE,
92                                   stderr=subprocess.PIPE)
93        vc_file = process.stdout
94
95        # Error handling here involves doing nothing; in most cases, the only
96        # sane response is to return an empty temp file.
97
98        with tempfile.NamedTemporaryFile(prefix='meld-tmp', delete=False) as f:
99            shutil.copyfileobj(vc_file, f)
100        return f.name
101
102    def _get_dirsandfiles(self, directory, dirs, files):
103        whatsnew = self._get_tree_cache(directory)
104        retfiles, retdirs = (self._get_statuses(whatsnew, files, _vc.File),
105                             self._get_statuses(whatsnew, dirs, _vc.Dir))
106        return retfiles, retdirs
107
108    def _lookup_tree_cache(self, rootdir):
109        non_boring = self._get_whatsnew()
110        boring = self._get_whatsnew(boring=True)
111        for path in boring:
112            if not path in non_boring:
113                non_boring[path] = _vc.STATE_IGNORED
114        return non_boring
115
116    def _get_whatsnew(self, boring=False):
117        whatsnew = {}
118        commandline = [self.CMD, "whatsnew", "--summary", "-l", "--repodir=" + self.root]
119        if boring:
120            commandline.append("--boring")
121        while 1:
122            try:
123                p = _vc.popen(commandline)
124                break
125            except OSError as e:
126                if e.errno != errno.EAGAIN:
127                    raise
128        for line in p:
129            if line.startswith('No changes!'):
130                continue
131            elements = line.split()
132            if len(elements) > 1:
133                if elements[1] == '->':
134                    status = _vc.STATE_NEW
135                    filename = elements.pop()
136                else:
137                    status = self.state_map[elements.pop(0)]
138                    filename = elements.pop(0)
139                filepath = os.path.join(self.root,
140                                        os.path.normpath(filename))
141                whatsnew[filepath] = status
142        return whatsnew
143
144    def _get_statuses(self, whatsnew, files, fstype):
145        rets = []
146        for filename, path in files:
147            state = _vc.STATE_NORMAL
148            if path in whatsnew:
149                state = whatsnew[path]
150            vcfile = fstype(path, filename, state)
151            if filename != self.VC_DIR:
152                rets.append(vcfile)
153        return rets
154