source: tailor/vcpx/svn.py @ 122

Revision 122, 11.0 KB checked in by lele@…, 9 years ago (diff)

Use UTC timestamps everywhere

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