source: tailor/vcpx/svn.py @ 201

Revision 201, 11.9 KB checked in by lele@…, 8 years ago (diff)

Module is not mandatory for svn or darcs
Option --module is not anymore mandatory for svn or darcs repositories,
but it still does for CVS.

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Subversion details
4# :Creato:   ven 18 giu 2004 15:00:52 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
6#
7
8"""
9This module contains supporting classes for Subversion.
10"""
11
12__docformat__ = 'reStructuredText'
13
14from shwrap import SystemCommand
15from source import UpdatableSourceWorkingDir, \
16     ChangesetApplicationFailure, GetUpstreamChangesetsFailure
17from target import SyncronizableTargetWorkingDir, TargetInitializationFailure
18
19
20class SvnUpdate(SystemCommand):
21    COMMAND = "svn update --revision %(revision)s %(entry)s"
22
23
24class SvnInfo(SystemCommand):
25    COMMAND = "LANG= svn info %(entry)s"
26
27    def __call__(self, output=None, dry_run=False, **kwargs):
28        output = SystemCommand.__call__(self, output=True,
29                                        dry_run=dry_run,
30                                        **kwargs)
31        res = {}
32        for l in output:
33            l = l[:-1]
34            if l:
35                key, value = l.split(':', 1)
36                res[key] = value[1:]
37        return res
38
39                 
40class SvnPropGet(SystemCommand):
41    COMMAND = "svn propget %(property)s %(entry)s"
42
43   
44class SvnPropSet(SystemCommand):
45    COMMAND = "svn propset --quiet %(property)s %(value)s %(entry)s"
46
47
48class SvnLog(SystemCommand):
49    COMMAND = "TZ=UTC svn log %(quiet)s %(xml)s --revision %(startrev)s:%(endrev)s %(entry)s 2>&1"
50   
51    def __call__(self, output=None, dry_run=False, **kwargs):
52        quiet = kwargs.get('quiet', True)
53        if quiet == True:
54            kwargs['quiet'] = '--quiet'
55        elif quiet == False:
56            kwargs['quiet'] = ''
57           
58        xml = kwargs.get('xml', False)
59        if xml:
60            kwargs['xml'] = '--xml'
61            output = True
62        else:
63            kwargs['xml'] = ''
64
65        startrev = kwargs.get('startrev')
66        if not startrev:
67            kwargs['startrev'] = 'BASE'
68
69        endrev = kwargs.get('endrev')
70        if not endrev:
71            kwargs['endrev'] = 'HEAD'
72
73        output = SystemCommand.__call__(self, output=output,
74                                        dry_run=dry_run, **kwargs)
75
76        if xml:
77            # parse the output and return the result
78            pass
79
80        return output
81
82
83class SvnCommit(SystemCommand):
84    COMMAND = "svn commit --quiet --file %(logfile)s %(entries)s"
85
86    def __call__(self, output=None, dry_run=False, **kwargs):
87        logfile = kwargs.get('logfile')
88        if not logfile:
89            from tempfile import NamedTemporaryFile
90
91            log = NamedTemporaryFile(bufsize=0)
92            logmessage = kwargs.get('logmessage')
93            if logmessage:
94                log.write(logmessage)
95           
96            kwargs['logfile'] = log.name
97       
98        return SystemCommand.__call__(self, output=output,
99                                      dry_run=dry_run, **kwargs)
100
101
102class SvnAdd(SystemCommand):
103    COMMAND = "svn add --quiet --no-auto-props --non-recursive %(entry)s"
104
105       
106class SvnRemove(SystemCommand):
107    COMMAND = "svn remove --quiet --force %(entry)s"
108
109
110class SvnMv(SystemCommand):
111    COMMAND = "svn mv --quiet %(old)s %(new)s"
112
113   
114class SvnCheckout(SystemCommand):
115    COMMAND = "svn co --revision %(revision)s %(repository)s/%(module)s %(wc)s"
116
117def changesets_from_svnlog(log, url):
118    from xml.sax import parseString
119    from xml.sax.handler import ContentHandler
120    from changes import ChangesetEntry, Changeset
121    from datetime import datetime
122
123    def get_entry_from_path(path, url=url):
124        # Given the repository url of this wc, say
125        #   "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
126        # extract the "entry" portion (a relative path) from what
127        # svn log --xml says, ie
128        #   "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
129        # that is to say "tests/PloneTestCase.py"
130
131        from os.path import split
132
133        prefix = split(path)[0]
134        while prefix:
135            if url.endswith(prefix):
136                return path[len(prefix)+1:]
137
138            prefix = split(prefix)[0]
139
140        # The path is outside our tracked tree...
141        return None
142
143    class SvnXMLLogHandler(ContentHandler):
144        def __init__(self):
145            self.changesets = []
146            self.current = None
147            self.current_field = []
148            self.renamed = {}
149           
150        def startElement(self, name, attributes):
151            if name == 'logentry':
152                self.current = {}
153                self.current['revision'] = attributes['revision']
154                self.current['entries'] = []
155            elif name in ['author', 'date', 'msg']:
156                self.current_field = []
157            elif name == 'path':
158                self.current_field = []
159                if attributes.has_key('copyfrom-path'):
160                    self.current_path_action = (
161                        attributes['action'],
162                        attributes['copyfrom-path'][1:], # make it relative
163                        attributes['copyfrom-rev'])
164                else:
165                    self.current_path_action = attributes['action']
166
167        def endElement(self, name):
168            if name == 'logentry':
169                # Sort the paths to make tests easier
170                self.current['entries'].sort(lambda a,b: cmp(a.name, b.name))
171
172                # Eliminate renamed entries
173                entries = [e for e in self.current['entries']
174                           if e.name not in self.renamed]
175               
176                svndate = self.current['date']
177                # 2004-04-16T17:12:48.000000Z
178                y,m,d = map(int, svndate[:10].split('-'))
179                hh,mm,ss = map(int, svndate[11:19].split(':'))
180                ms = int(svndate[20:-1])
181                timestamp = datetime(y, m, d, hh, mm, ss, ms)
182               
183                changeset = Changeset(self.current['revision'],
184                                      timestamp,
185                                      self.current['author'],
186                                      self.current['msg'],
187                                      entries)
188                self.changesets.append(changeset)
189                self.current = None
190            elif name in ['author', 'date', 'msg']:
191                self.current[name] = ''.join(self.current_field)
192            elif name == 'path':
193                path = ''.join(self.current_field)[1:]
194                entrypath = get_entry_from_path(path)
195                if entrypath:
196                    entry = ChangesetEntry(entrypath)
197
198                    if type(self.current_path_action) == type( () ):
199                        old = get_entry_from_path(self.current_path_action[1])
200                        if old:
201                            entry.action_kind = entry.RENAMED
202                            entry.old_name = old
203                            self.renamed[entry.old_name] = True
204                        else:
205                            entry.action_kind = entry.ADDED
206                    else:
207                        entry.action_kind = self.current_path_action
208
209                    self.current['entries'].append(entry)
210
211                   
212        def characters(self, data):
213            self.current_field.append(data)
214
215
216    handler = SvnXMLLogHandler()
217    parseString(log.getvalue(), handler)
218    return handler.changesets
219
220
221class SvnWorkingDir(UpdatableSourceWorkingDir, SyncronizableTargetWorkingDir):
222
223    ## UpdatableSourceWorkingDir
224
225    def getUpstreamChangesets(self, root, sincerev=None):
226        if sincerev:
227            sincerev = int(sincerev)
228        else:
229            sincerev = 0
230           
231        svnlog = SvnLog(working_dir=root)
232        log = svnlog(quiet='--verbose', output=True, xml=True,
233                     startrev=sincerev+1, entry='.')
234       
235        if svnlog.exit_status:
236            return []
237
238        svninfo = SvnInfo(working_dir=root)
239        info = svninfo(entry='.')
240
241        if svninfo.exit_status:
242            raise GetUpstreamChangesetsFailure('svn info on %r exited with status %d' % (root, svninfo.exit_status))
243       
244        return self.__parseSvnLog(log, info['URL'])
245
246    def __parseSvnLog(self, log, url):
247        """Return an object representation of the ``svn log`` thru HEAD."""
248
249        return changesets_from_svnlog(log, url)
250   
251    def _applyChangeset(self, root, changeset, logger=None):
252        svnup = SvnUpdate(working_dir=root)
253        out = svnup(output=True, entry='.', revision=changeset.revision)
254
255        if svnup.exit_status:
256            raise ChangesetApplicationFailure(
257                "'svn update' returned status %s" % svnup.exit_status)
258           
259        if logger: logger.info("%s updated to %s" % (
260            ','.join([e.name for e in changeset.entries]),
261            changeset.revision))
262       
263        result = []
264        for line in out:
265            if len(line)>2 and line[0] == 'C' and line[1] == ' ':
266                logger.warn("Conflict after 'svn update': '%s'" % line)
267                result.append(line[2:-1])
268           
269        return result
270       
271    def _checkoutUpstreamRevision(self, basedir, repository, module, revision,
272                                  subdir=None, logger=None):
273        """
274        Concretely do the checkout of the upstream revision.
275        """
276       
277        from os.path import join, exists
278       
279        wdir = join(basedir, subdir)
280
281        if not exists(join(wdir, '.svn')):
282            if logger: logger.info("checking out a working copy")
283            svnco = SvnCheckout(working_dir=basedir)
284            svnco(output=True, repository=repository, module=module,
285                  wc=subdir, revision=revision)
286            if svnco.exit_status:
287                raise TargetInitializationFailure(
288                    "'svn checkout' returned status %s" % svnco.exit_status)
289        else:
290            if logger: logger.info("%s already exists, assuming it's a svn working dir" % wdir)
291
292        svninfo = SvnInfo(working_dir=wdir)
293        info = svninfo(entry='.')
294        if svninfo.exit_status:
295            raise GetUpstreamChangesetsFailure(
296                'svn info on %r exited with status %d' %
297                (wdir, svninfo.exit_status))       
298
299        actual = info['Revision']
300       
301        if logger: logger.info("working copy up to svn revision %s",
302                               actual)
303       
304        return actual
305   
306    ## SyncronizableTargetWorkingDir
307
308    def _addEntry(self, root, entry):
309        """
310        Add a new entry.
311        """
312
313        c = SvnAdd(working_dir=root)
314        c(entry=entry)
315
316    def _commit(self,root, date, author, remark, changelog=None, entries=None):
317        """
318        Commit the changeset.
319        """
320
321        c = SvnCommit(working_dir=root)
322       
323        logmessage = "%s\nOriginal author: %s\nDate: %s" % (remark, author,
324                                                            date)
325        if changelog:
326            logmessage = logmessage + '\n\n' + changelog
327           
328        if entries:
329            entries = ' '.join(entries)
330        else:
331            entries = '.'
332           
333        c(logmessage=logmessage, entries=entries)
334       
335    def _removeEntry(self, root, entry):
336        """
337        Remove an entry.
338        """
339
340        c = SvnRemove(working_dir=root)
341        c(entry=entry)
342
343    def _renameEntry(self, root, oldentry, newentry):
344        """
345        Rename an entry.
346        """
347
348        c = SvnMv(working_dir=root)
349        c(old=oldentry, new=newentry)
350
351    def _initializeWorkingDir(self, root, repository, module, subdir, addentry=None):
352        """
353        Add the given directory to an already existing svn working tree.
354        """
355
356        from os.path import exists, join
357
358        if not exists(join(root, '.svn')):
359            raise TargetInitializationFailure("'%s' needs to be an SVN working copy already be under SVN" % root)
360
361        SyncronizableTargetWorkingDir._initializeWorkingDir(self, root,
362                                                            repository, module,
363                                                            subdir, SvnAdd)
Note: See TracBrowser for help on using the repository browser.