summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/pip-0.8.1-py2.7.egg/pip/vcs/subversion.py
blob: 85715d97a6a518cb51f1d31d77947bbb53839586 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import os
import re
from pip import call_subprocess
from pip.index import Link
from pip.util import rmtree, display_path
from pip.log import logger
from pip.vcs import vcs, VersionControl

_svn_xml_url_re = re.compile('url="([^"]+)"')
_svn_rev_re = re.compile('committed-rev="(\d+)"')
_svn_url_re = re.compile(r'URL: (.+)')
_svn_revision_re = re.compile(r'Revision: (.+)')


class Subversion(VersionControl):
    name = 'svn'
    dirname = '.svn'
    repo_name = 'checkout'
    schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https')
    bundle_file = 'svn-checkout.txt'
    guide = ('# This was an svn checkout; to make it a checkout again run:\n'
            'svn checkout --force -r %(rev)s %(url)s .\n')

    def get_info(self, location):
        """Returns (url, revision), where both are strings"""
        assert not location.rstrip('/').endswith(self.dirname), 'Bad directory: %s' % location
        output = call_subprocess(
            [self.cmd, 'info', location], show_stdout=False, extra_environ={'LANG': 'C'})
        match = _svn_url_re.search(output)
        if not match:
            logger.warn('Cannot determine URL of svn checkout %s' % display_path(location))
            logger.info('Output that cannot be parsed: \n%s' % output)
            return None, None
        url = match.group(1).strip()
        match = _svn_revision_re.search(output)
        if not match:
            logger.warn('Cannot determine revision of svn checkout %s' % display_path(location))
            logger.info('Output that cannot be parsed: \n%s' % output)
            return url, None
        return url, match.group(1)

    def parse_vcs_bundle_file(self, content):
        for line in content.splitlines():
            if not line.strip() or line.strip().startswith('#'):
                continue
            match = re.search(r'^-r\s*([^ ])?', line)
            if not match:
                return None, None
            rev = match.group(1)
            rest = line[match.end():].strip().split(None, 1)[0]
            return rest, rev
        return None, None

    def unpack(self, location):
        """Check out the svn repository at the url to the destination location"""
        url, rev = self.get_url_rev()
        logger.notify('Checking out svn repository %s to %s' % (url, location))
        logger.indent += 2
        try:
            if os.path.exists(location):
                # Subversion doesn't like to check out over an existing directory
                # --force fixes this, but was only added in svn 1.5
                rmtree(location)
            call_subprocess(
                [self.cmd, 'checkout', url, location],
                filter_stdout=self._filter, show_stdout=False)
        finally:
            logger.indent -= 2

    def export(self, location):
        """Export the svn repository at the url to the destination location"""
        url, rev = self.get_url_rev()
        logger.notify('Exporting svn repository %s to %s' % (url, location))
        logger.indent += 2
        try:
            if os.path.exists(location):
                # Subversion doesn't like to check out over an existing directory
                # --force fixes this, but was only added in svn 1.5
                rmtree(location)
            call_subprocess(
                [self.cmd, 'export', url, location],
                filter_stdout=self._filter, show_stdout=False)
        finally:
            logger.indent -= 2

    def switch(self, dest, url, rev_options):
        call_subprocess(
            [self.cmd, 'switch'] + rev_options + [url, dest])

    def update(self, dest, rev_options):
        call_subprocess(
            [self.cmd, 'update'] + rev_options + [dest])

    def obtain(self, dest):
        url, rev = self.get_url_rev()
        if rev:
            rev_options = ['-r', rev]
            rev_display = ' (to revision %s)' % rev
        else:
            rev_options = []
            rev_display = ''
        if self.check_destination(dest, url, rev_options, rev_display):
            logger.notify('Checking out %s%s to %s'
                          % (url, rev_display, display_path(dest)))
            call_subprocess(
                [self.cmd, 'checkout', '-q'] + rev_options + [url, dest])

    def get_location(self, dist, dependency_links):
        for url in dependency_links:
            egg_fragment = Link(url).egg_fragment
            if not egg_fragment:
                continue
            if '-' in egg_fragment:
                ## FIXME: will this work when a package has - in the name?
                key = '-'.join(egg_fragment.split('-')[:-1]).lower()
            else:
                key = egg_fragment
            if key == dist.key:
                return url.split('#', 1)[0]
        return None

    def get_revision(self, location):
        """
        Return the maximum revision for all files under a given location
        """
        # Note: taken from setuptools.command.egg_info
        revision = 0

        for base, dirs, files in os.walk(location):
            if self.dirname not in dirs:
                dirs[:] = []
                continue    # no sense walking uncontrolled subdirs
            dirs.remove(self.dirname)
            entries_fn = os.path.join(base, self.dirname, 'entries')
            if not os.path.exists(entries_fn):
                ## FIXME: should we warn?
                continue
            f = open(entries_fn)
            data = f.read()
            f.close()

            if data.startswith('8') or data.startswith('9') or data.startswith('10'):
                data = map(str.splitlines, data.split('\n\x0c\n'))
                del data[0][0]  # get rid of the '8'
                dirurl = data[0][3]
                revs = [int(d[9]) for d in data if len(d)>9 and d[9]]+[0]
                if revs:
                    localrev = max(revs)
                else:
                    localrev = 0
            elif data.startswith('<?xml'):
                dirurl = _svn_xml_url_re.search(data).group(1)    # get repository URL
                revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)]+[0]
                if revs:
                    localrev = max(revs)
                else:
                    localrev = 0
            else:
                logger.warn("Unrecognized .svn/entries format; skipping %s", base)
                dirs[:] = []
                continue
            if base == location:
                base_url = dirurl+'/'   # save the root url
            elif not dirurl.startswith(base_url):
                dirs[:] = []
                continue    # not part of the same svn tree, skip it
            revision = max(revision, localrev)
        return revision

    def get_url_rev(self):
        # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
        url, rev = super(Subversion, self).get_url_rev()
        if url.startswith('ssh://'):
            url = 'svn+' + url
        return url, rev

    def get_url(self, location):
        # In cases where the source is in a subdirectory, not alongside setup.py
        # we have to look up in the location until we find a real setup.py
        orig_location = location
        while not os.path.exists(os.path.join(location, 'setup.py')):
            last_location = location
            location = os.path.dirname(location)
            if location == last_location:
                # We've traversed up to the root of the filesystem without finding setup.py
                logger.warn("Could not find setup.py for directory %s (tried all parent directories)"
                            % orig_location)
                return None
        f = open(os.path.join(location, self.dirname, 'entries'))
        data = f.read()
        f.close()
        if data.startswith('8') or data.startswith('9') or data.startswith('10'):
            data = map(str.splitlines, data.split('\n\x0c\n'))
            del data[0][0]  # get rid of the '8'
            return data[0][3]
        elif data.startswith('<?xml'):
            match = _svn_xml_url_re.search(data)
            if not match:
                raise ValueError('Badly formatted data: %r' % data)
            return match.group(1)    # get repository URL
        else:
            logger.warn("Unrecognized .svn/entries format in %s" % location)
            # Or raise exception?
            return None

    def get_tag_revs(self, svn_tag_url):
        stdout = call_subprocess(
            [self.cmd, 'ls', '-v', svn_tag_url], show_stdout=False)
        results = []
        for line in stdout.splitlines():
            parts = line.split()
            rev = int(parts[0])
            tag = parts[-1].strip('/')
            results.append((tag, rev))
        return results

    def find_tag_match(self, rev, tag_revs):
        best_match_rev = None
        best_tag = None
        for tag, tag_rev in tag_revs:
            if (tag_rev > rev and
                (best_match_rev is None or best_match_rev > tag_rev)):
                # FIXME: Is best_match > tag_rev really possible?
                # or is it a sign something is wacky?
                best_match_rev = tag_rev
                best_tag = tag
        return best_tag

    def get_src_requirement(self, dist, location, find_tags=False):
        repo = self.get_url(location)
        if repo is None:
            return None
        parts = repo.split('/')
        ## FIXME: why not project name?
        egg_project_name = dist.egg_name().split('-', 1)[0]
        rev = self.get_revision(location)
        if parts[-2] in ('tags', 'tag'):
            # It's a tag, perfect!
            full_egg_name = '%s-%s' % (egg_project_name, parts[-1])
        elif parts[-2] in ('branches', 'branch'):
            # It's a branch :(
            full_egg_name = '%s-%s-r%s' % (dist.egg_name(), parts[-1], rev)
        elif parts[-1] == 'trunk':
            # Trunk :-/
            full_egg_name = '%s-dev_r%s' % (dist.egg_name(), rev)
            if find_tags:
                tag_url = '/'.join(parts[:-1]) + '/tags'
                tag_revs = self.get_tag_revs(tag_url)
                match = self.find_tag_match(rev, tag_revs)
                if match:
                    logger.notify('trunk checkout %s seems to be equivalent to tag %s' % match)
                    repo = '%s/%s' % (tag_url, match)
                    full_egg_name = '%s-%s' % (egg_project_name, match)
        else:
            # Don't know what it is
            logger.warn('svn URL does not fit normal structure (tags/branches/trunk): %s' % repo)
            full_egg_name = '%s-dev_r%s' % (egg_project_name, rev)
        return 'svn+%s@%s#egg=%s' % (repo, rev, full_egg_name)

vcs.register(Subversion)