~ubuntu-branches/ubuntu/vivid/python-pip/vivid

« back to all changes in this revision

Viewing changes to pip/vcs/subversion.py

  • Committer: Bazaar Package Importer
  • Author(s): Carl Chenet
  • Date: 2010-06-04 02:02:44 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20100604020244-su7wqtthao1rx4jj
Tags: 0.7.2-1
* New upstream version
* Added debian/install
* debian/source/format
  - bump to 3.0 (quilt)
* renamed python-pip.manpages to manpages
* debian/control
  - bump Standards-Version to 3.8.4 (no changes needed)
  - switched XS-Python-Version to all
  - removed python-setuptools in Build-Depends 
* debian/rules
  - overriding dh_auto_install to install with install file
* debian/pip.1
  - added uninstall and search sections

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import re
 
3
from pip import call_subprocess
 
4
from pip.index import Link
 
5
from pip.util import rmtree, display_path
 
6
from pip.log import logger
 
7
from pip.vcs import vcs, VersionControl
 
8
 
 
9
_svn_xml_url_re = re.compile('url="([^"]+)"')
 
10
_svn_rev_re = re.compile('committed-rev="(\d+)"')
 
11
_svn_url_re = re.compile(r'URL: (.+)')
 
12
_svn_revision_re = re.compile(r'Revision: (.+)')
 
13
 
 
14
class Subversion(VersionControl):
 
15
    name = 'svn'
 
16
    dirname = '.svn'
 
17
    repo_name = 'checkout'
 
18
    schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+https')
 
19
    bundle_file = 'svn-checkout.txt'
 
20
    guide = ('# This was an svn checkout; to make it a checkout again run:\n'
 
21
            'svn checkout --force -r %(rev)s %(url)s .\n')
 
22
 
 
23
    def get_info(self, location):
 
24
        """Returns (url, revision), where both are strings"""
 
25
        assert not location.rstrip('/').endswith(self.dirname), 'Bad directory: %s' % location
 
26
        output = call_subprocess(
 
27
            [self.cmd, 'info', location], show_stdout=False, extra_environ={'LANG': 'C'})
 
28
        match = _svn_url_re.search(output)
 
29
        if not match:
 
30
            logger.warn('Cannot determine URL of svn checkout %s' % display_path(location))
 
31
            logger.info('Output that cannot be parsed: \n%s' % output)
 
32
            return None, None
 
33
        url = match.group(1).strip()
 
34
        match = _svn_revision_re.search(output)
 
35
        if not match:
 
36
            logger.warn('Cannot determine revision of svn checkout %s' % display_path(location))
 
37
            logger.info('Output that cannot be parsed: \n%s' % output)
 
38
            return url, None
 
39
        return url, match.group(1)
 
40
 
 
41
    def parse_vcs_bundle_file(self, content):
 
42
        for line in content.splitlines():
 
43
            if not line.strip() or line.strip().startswith('#'):
 
44
                continue
 
45
            match = re.search(r'^-r\s*([^ ])?', line)
 
46
            if not match:
 
47
                return None, None
 
48
            rev = match.group(1)
 
49
            rest = line[match.end():].strip().split(None, 1)[0]
 
50
            return rest, rev
 
51
        return None, None
 
52
 
 
53
    def unpack(self, location):
 
54
        """Check out the svn repository at the url to the destination location"""
 
55
        url, rev = self.get_url_rev()
 
56
        logger.notify('Checking out svn repository %s to %s' % (url, location))
 
57
        logger.indent += 2
 
58
        try:
 
59
            if os.path.exists(location):
 
60
                # Subversion doesn't like to check out over an existing directory
 
61
                # --force fixes this, but was only added in svn 1.5
 
62
                rmtree(location)
 
63
            call_subprocess(
 
64
                [self.cmd, 'checkout', url, location],
 
65
                filter_stdout=self._filter, show_stdout=False)
 
66
        finally:
 
67
            logger.indent -= 2
 
68
 
 
69
    def export(self, location):
 
70
        """Export the svn repository at the url to the destination location"""
 
71
        url, rev = self.get_url_rev()
 
72
        logger.notify('Exporting svn repository %s to %s' % (url, location))
 
73
        logger.indent += 2
 
74
        try:
 
75
            if os.path.exists(location):
 
76
                # Subversion doesn't like to check out over an existing directory
 
77
                # --force fixes this, but was only added in svn 1.5
 
78
                rmtree(location)
 
79
            call_subprocess(
 
80
                [self.cmd, 'export', url, location],
 
81
                filter_stdout=self._filter, show_stdout=False)
 
82
        finally:
 
83
            logger.indent -= 2
 
84
 
 
85
    def switch(self, dest, url, rev_options):
 
86
        call_subprocess(
 
87
            [self.cmd, 'switch'] + rev_options + [url, dest])
 
88
 
 
89
    def update(self, dest, rev_options):
 
90
        call_subprocess(
 
91
            [self.cmd, 'update'] + rev_options + [dest])
 
92
 
 
93
    def obtain(self, dest):
 
94
        url, rev = self.get_url_rev()
 
95
        if rev:
 
96
            rev_options = ['-r', rev]
 
97
            rev_display = ' (to revision %s)' % rev
 
98
        else:
 
99
            rev_options = []
 
100
            rev_display = ''
 
101
        if self.check_destination(dest, url, rev_options, rev_display):
 
102
            logger.notify('Checking out %s%s to %s'
 
103
                          % (url, rev_display, display_path(dest)))
 
104
            call_subprocess(
 
105
                [self.cmd, 'checkout', '-q'] + rev_options + [url, dest])
 
106
 
 
107
    def get_location(self, dist, dependency_links):
 
108
        egg_fragment_re = re.compile(r'#egg=(.*)$')
 
109
        for url in dependency_links:
 
110
            egg_fragment = Link(url).egg_fragment
 
111
            if not egg_fragment:
 
112
                continue
 
113
            if '-' in egg_fragment:
 
114
                ## FIXME: will this work when a package has - in the name?
 
115
                key = '-'.join(egg_fragment.split('-')[:-1]).lower()
 
116
            else:
 
117
                key = egg_fragment
 
118
            if key == dist.key:
 
119
                return url.split('#', 1)[0]
 
120
        return None
 
121
 
 
122
    def get_revision(self, location):
 
123
        """
 
124
        Return the maximum revision for all files under a given location
 
125
        """
 
126
        # Note: taken from setuptools.command.egg_info
 
127
        revision = 0
 
128
 
 
129
        for base, dirs, files in os.walk(location):
 
130
            if self.dirname not in dirs:
 
131
                dirs[:] = []
 
132
                continue    # no sense walking uncontrolled subdirs
 
133
            dirs.remove(self.dirname)
 
134
            entries_fn = os.path.join(base, self.dirname, 'entries')
 
135
            if not os.path.exists(entries_fn):
 
136
                ## FIXME: should we warn?
 
137
                continue
 
138
            f = open(entries_fn)
 
139
            data = f.read()
 
140
            f.close()
 
141
 
 
142
            if data.startswith('8') or data.startswith('9') or data.startswith('10'):
 
143
                data = map(str.splitlines,data.split('\n\x0c\n'))
 
144
                del data[0][0]  # get rid of the '8'
 
145
                dirurl = data[0][3]
 
146
                revs = [int(d[9]) for d in data if len(d)>9 and d[9]]+[0]
 
147
                if revs:
 
148
                    localrev = max(revs)
 
149
                else:
 
150
                    localrev = 0
 
151
            elif data.startswith('<?xml'):
 
152
                dirurl = _svn_xml_url_re.search(data).group(1)    # get repository URL
 
153
                revs = [int(m.group(1)) for m in _svn_rev_re.finditer(data)]+[0]
 
154
                if revs:
 
155
                    localrev = max(revs)
 
156
                else:
 
157
                    localrev = 0
 
158
            else:
 
159
                logger.warn("Unrecognized .svn/entries format; skipping %s", base)
 
160
                dirs[:] = []
 
161
                continue
 
162
            if base == location:
 
163
                base_url = dirurl+'/'   # save the root url
 
164
            elif not dirurl.startswith(base_url):
 
165
                dirs[:] = []
 
166
                continue    # not part of the same svn tree, skip it
 
167
            revision = max(revision, localrev)
 
168
        return revision
 
169
 
 
170
    def get_url_rev(self):
 
171
        # hotfix the URL scheme after removing svn+ from svn+ssh:// readd it
 
172
        url, rev = super(Subversion, self).get_url_rev()
 
173
        if url.startswith('ssh://'):
 
174
            url = 'svn+' + url
 
175
        return url, rev
 
176
 
 
177
    def get_url(self, location):
 
178
        # In cases where the source is in a subdirectory, not alongside setup.py
 
179
        # we have to look up in the location until we find a real setup.py
 
180
        orig_location = location
 
181
        while not os.path.exists(os.path.join(location, 'setup.py')):
 
182
            last_location = location
 
183
            location = os.path.dirname(location)
 
184
            if location == last_location:
 
185
                # We've traversed up to the root of the filesystem without finding setup.py
 
186
                logger.warn("Could not find setup.py for directory %s (tried all parent directories)"
 
187
                            % orig_location)
 
188
                return None
 
189
        f = open(os.path.join(location, self.dirname, 'entries'))
 
190
        data = f.read()
 
191
        f.close()
 
192
        if data.startswith('8') or data.startswith('9') or data.startswith('10'):
 
193
            data = map(str.splitlines,data.split('\n\x0c\n'))
 
194
            del data[0][0]  # get rid of the '8'
 
195
            return data[0][3]
 
196
        elif data.startswith('<?xml'):
 
197
            match = _svn_xml_url_re.search(data)
 
198
            if not match:
 
199
                raise ValueError('Badly formatted data: %r' % data)
 
200
            return match.group(1)    # get repository URL
 
201
        else:
 
202
            logger.warn("Unrecognized .svn/entries format in %s" % location)
 
203
            # Or raise exception?
 
204
            return None
 
205
 
 
206
    def get_tag_revs(self, svn_tag_url):
 
207
        stdout = call_subprocess(
 
208
            [self.cmd, 'ls', '-v', svn_tag_url], show_stdout=False)
 
209
        results = []
 
210
        for line in stdout.splitlines():
 
211
            parts = line.split()
 
212
            rev = int(parts[0])
 
213
            tag = parts[-1].strip('/')
 
214
            results.append((tag, rev))
 
215
        return results
 
216
 
 
217
    def find_tag_match(self, rev, tag_revs):
 
218
        best_match_rev = None
 
219
        best_tag = None
 
220
        for tag, tag_rev in tag_revs:
 
221
            if (tag_rev > rev and
 
222
                (best_match_rev is None or best_match_rev > tag_rev)):
 
223
                # FIXME: Is best_match > tag_rev really possible?
 
224
                # or is it a sign something is wacky?
 
225
                best_match_rev = tag_rev
 
226
                best_tag = tag
 
227
        return best_tag
 
228
 
 
229
    def get_src_requirement(self, dist, location, find_tags=False):
 
230
        repo = self.get_url(location)
 
231
        if repo is None:
 
232
            return None
 
233
        parts = repo.split('/')
 
234
        ## FIXME: why not project name?
 
235
        egg_project_name = dist.egg_name().split('-', 1)[0]
 
236
        rev = self.get_revision(location)
 
237
        if parts[-2] in ('tags', 'tag'):
 
238
            # It's a tag, perfect!
 
239
            full_egg_name = '%s-%s' % (egg_project_name, parts[-1])
 
240
        elif parts[-2] in ('branches', 'branch'):
 
241
            # It's a branch :(
 
242
            full_egg_name = '%s-%s-r%s' % (dist.egg_name(), parts[-1], rev)
 
243
        elif parts[-1] == 'trunk':
 
244
            # Trunk :-/
 
245
            full_egg_name = '%s-dev_r%s' % (dist.egg_name(), rev)
 
246
            if find_tags:
 
247
                tag_url = '/'.join(parts[:-1]) + '/tags'
 
248
                tag_revs = self.get_tag_revs(tag_url)
 
249
                match = self.find_tag_match(rev, tag_revs)
 
250
                if match:
 
251
                    logger.notify('trunk checkout %s seems to be equivalent to tag %s' % match)
 
252
                    repo = '%s/%s' % (tag_url, match)
 
253
                    full_egg_name = '%s-%s' % (egg_project_name, match)
 
254
        else:
 
255
            # Don't know what it is
 
256
            logger.warn('svn URL does not fit normal structure (tags/branches/trunk): %s' % repo)
 
257
            full_egg_name = '%s-dev_r%s' % (egg_project_name, rev)
 
258
        return 'svn+%s@%s#egg=%s' % (repo, rev, full_egg_name)
 
259
 
 
260
vcs.register(Subversion)