source: tailor/vcpx/darcs.py @ 284

Revision 284, 13.0 KB checked in by lele@…, 8 years ago (diff)

Do not relay on --last=1, but rather get changes of exactly the same patch
Maybe I'm just paranoid, but it seems safer to not assume --last=1 gives
back the right patch, and explicitly ask for the same patch just pulled.

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Darcs details
4# :Creato:   ven 18 giu 2004 14:45:28 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
6#
7
8"""
9This module contains supporting classes for the `darcs` versioning system.
10"""
11
12__docformat__ = 'reStructuredText'
13
14from shwrap import SystemCommand, shrepr
15from source import UpdatableSourceWorkingDir, ChangesetApplicationFailure, \
16     GetUpstreamChangesetsFailure
17from target import SyncronizableTargetWorkingDir, TargetInitializationFailure
18
19MOTD = """\
20This is the Darcs equivalent of
21%s/%s
22"""
23
24class DarcsRecord(SystemCommand):
25    COMMAND = "darcs record --all --pipe %(entries)s"
26
27    def __call__(self, output=None, dry_run=False, **kwargs):
28        date = kwargs.get('date').strftime('%Y/%m/%d %H:%M:%S')
29        author = kwargs.get('author')
30        patchname = kwargs.get('patchname')
31        logmessage = kwargs.get('logmessage')
32        if not logmessage:
33            logmessage = ''
34
35        input = "%s UTC\n%s\n%s\n%s\n" % (date, author, patchname, logmessage)
36       
37        return SystemCommand.__call__(self, output=output, input=input,
38                                      dry_run=dry_run, 
39                                      **kwargs)
40
41
42def changesets_from_darcschanges(changes):
43    """
44    Parse XML output of ``darcs changes``.
45
46    Return a list of `Changeset`s.
47    """
48   
49    from xml.sax import parseString
50    from xml.sax.handler import ContentHandler
51    from changes import ChangesetEntry, Changeset
52    from datetime import datetime
53
54    class DarcsXMLChangesHandler(ContentHandler):
55        def __init__(self):
56            self.changesets = []
57            self.current = None
58            self.current_field = []
59
60        def startElement(self, name, attributes):
61            if name == 'patch':
62                self.current = {}
63                self.current['author'] = attributes['author']
64                date = attributes['date']
65                # 20040619130027
66                y = int(date[:4])
67                m = int(date[4:6])
68                d = int(date[6:8])
69                hh = int(date[8:10])
70                mm = int(date[10:12])
71                ss = int(date[12:14])
72                timestamp = datetime(y, m, d, hh, mm, ss)
73                self.current['date'] = timestamp
74                self.current['comment'] = ''
75                self.current['entries'] = []
76            elif name in ['name', 'comment',
77                          'add_file', 'add_directory',
78                          'modify_file', 'remove_file']:
79                self.current_field = []
80            elif name == 'move':
81                self.old_name = attributes['from']
82                self.new_name = attributes['to']
83
84        def endElement(self, name):
85            if name == 'patch':
86                # Sort the paths to make tests easier
87                self.current['entries'].sort(lambda x,y: cmp(x.name, y.name))
88                self.changesets.append(Changeset(self.current['name'],
89                                                 self.current['date'],
90                                                 self.current['author'],
91                                                 self.current['comment'],
92                                                 self.current['entries']))
93                self.current = None
94            elif name in ['name', 'comment']:
95                self.current[name] = ''.join(self.current_field)
96            elif name == 'move':
97                entry = ChangesetEntry(self.new_name)
98                entry.action_kind = entry.RENAMED
99                entry.old_name = self.old_name
100                self.current['entries'].append(entry)
101            elif name in ['add_file', 'add_directory',
102                          'modify_file', 'remove_file']:
103                entry = ChangesetEntry(''.join(self.current_field).strip())
104                entry.action_kind = { 'add_file': entry.ADDED,
105                                      'add_directory': entry.ADDED,
106                                      'modify_file': entry.UPDATED,
107                                      'remove_file': entry.DELETED,
108                                      'rename_file': entry.RENAMED
109                                    }[name]
110
111                self.current['entries'].append(entry)
112
113        def characters(self, data):
114            self.current_field.append(data)
115
116    handler = DarcsXMLChangesHandler()
117    parseString(changes.getvalue(), handler)
118
119    changesets = handler.changesets
120   
121    # sort changeset by date
122    changesets.sort(lambda x, y: cmp(x.date, y.date))
123
124    return changesets
125
126   
127class DarcsWorkingDir(UpdatableSourceWorkingDir,SyncronizableTargetWorkingDir):
128    """
129    A working directory under ``darcs``.
130    """
131
132    ## UpdatableSourceWorkingDir
133   
134    def getUpstreamChangesets(self, root, repository, module, sincerev=None):
135        """
136        Do the actual work of fetching the upstream changeset.
137        """
138
139        from datetime import datetime
140        from time import strptime
141        from changes import Changeset
142       
143        c = SystemCommand(working_dir=root,
144                          command="TZ=UTC darcs pull --dry-run")
145        output = c(output=True)
146        if c.exit_status:
147            raise GetUpstreamChangesetsFailure("'darcs pull' returned status %d saying \"%s\"" % (c.exit_status, output.getvalue().strip()))
148       
149        l = output.readline()
150        while l and not (l.startswith('Would pull the following changes:') or
151                         l == 'No remote changes to pull in!\n'):
152            l = output.readline()
153
154        changesets = []
155
156        if l <> 'No remote changes to pull in!\n':
157            ## Sat Jul 17 01:22:08 CEST 2004  lele@nautilus
158            ##   * Refix _getUpstreamChangesets for darcs
159
160            l = output.readline()
161            while not l.startswith('Making no changes:  this is a dry run.'):
162                # Assume it's a line like
163                #    Sun Jan  2 00:24:04 UTC 2005  lele@nautilus.homeip.net
164                # we used to split on the double space before the email,
165                # but in this case this is wrong. Waiting for xml output,
166                # is it really sane asserting date's length to 28 chars?
167                date = l[:28]
168                author = l[30:-1]
169                y,m,d,hh,mm,ss,d1,d2,d3 = strptime(date, "%a %b %d %H:%M:%S %Z %Y")
170                date = datetime(y,m,d,hh,mm,ss)
171                l = output.readline()
172                assert (l.startswith('  * ') or
173                        l.startswith('  UNDO:') or
174                        l.startswith('  tagged'))
175
176                if l.startswith('  *'):
177                    name = l[4:-1]
178                else:
179                    name = l[2:-1]
180
181                changelog = []
182                l = output.readline()
183                while l.startswith(' '):
184                    changelog.append(l.strip())
185                    l = output.readline()
186
187                changesets.append(Changeset(name, date, author, '\n'.join(changelog)))
188
189                while not l.strip():
190                    l = output.readline()
191
192        return changesets
193   
194    def _applyChangeset(self, root, changeset, logger=None):
195        """
196        Do the actual work of applying the changeset to the working copy.
197        """
198
199        patchname=shrepr(changeset.revision)
200       
201        c = SystemCommand(working_dir=root,
202                          command="darcs pull --all --patches=%(patch)s")
203        output = c(output=True, patch=patchname)
204        if c.exit_status:
205            raise ChangesetApplicationFailure("'darcs pull' returned status %d saying \"%s\"" % (c.exit_status, output.getvalue().strip()))
206
207        c = SystemCommand(working_dir=root,
208                          command="darcs changes --patches=%(patch)s"
209                                  " --xml-output --summ")
210        last = changesets_from_darcschanges(c(output=True, patch=patchname))
211        if last:
212            changeset.entries.extend(last[0].entries)
213
214    def _checkoutUpstreamRevision(self, basedir, repository, module, revision,
215                                  subdir=None, logger=None):
216        """
217        Concretely do the checkout of the upstream revision.
218        """
219
220        from os.path import join, exists
221        from os import mkdir
222       
223        wdir = join(basedir, subdir)
224        if not exists(join(wdir, '_darcs')):
225            if not exists(wdir):
226                mkdir(wdir)
227
228            c = SystemCommand(working_dir=wdir, command="darcs initialize")
229            c(output=True)
230
231            if c.exit_status:
232                raise TargetInitializationFailure(
233                    "'darcs initialize' returned status %s" % c.exit_status)
234           
235            dpull = SystemCommand(working_dir=wdir,
236                                 command="darcs pull --all --verbose"
237                                         " %(tag)s %(repository)s"
238                                         " 2>&1")
239           
240            output = dpull(output=True, repository=shrepr(repository),
241                           tag=(revision<>'HEAD' and
242                                '--tag=%s' % shrepr(revision)
243                                or ''))
244            if dpull.exit_status:
245                raise TargetInitializationFailure(
246                    "'darcs pull' returned status %d saying \"%s\"" %
247                    (dpull.exit_status, output.getvalue().strip()))
248
249        c = SystemCommand(working_dir=wdir,
250                          command="darcs changes --last=1 --xml-output 2>&1")
251        output = c(output=True)
252        if c.exit_status:
253            raise ChangesetApplicationFailure("'darcs changes' returned status %d saying \"%s\"" % (c.exit_status, output.getvalue().strip()))
254       
255        last = changesets_from_darcschanges(output)
256       
257        return last[0].revision
258
259   
260    ## SyncronizableTargetWorkingDir
261   
262    def _addEntries(self, root, entries):
263        """
264        Add a sequence of entries.
265        """
266
267        c = SystemCommand(working_dir=root,
268                          command="darcs add --case-ok --not-recursive"
269                                  " --quiet %(entry)s")
270        c(entry=' '.join([shrepr(e.name) for e in entries]))
271       
272    def _commit(self,root, date, author, remark, changelog=None, entries=None):
273        """
274        Commit the changeset.
275        """
276
277        c = DarcsRecord(working_dir=root)
278
279        if entries:
280            entries = ' '.join([shrepr(e) for e in entries])
281        else:
282            entries = '.'
283           
284        c(output=True, date=date, patchname=remark,
285          logmessage=changelog, author=author, entries=entries)
286        if c.exit_status:
287            raise ChangesetApplicationFailure("'darcs record' returned status %d" % c.exit_status)
288       
289    def _removeEntries(self, root, entries):
290        """
291        Remove a sequence of entries.
292        """
293
294        # Since the source VCS already deleted the entry, and given that
295        # darcs will do the right thing with it, do nothing here, instead
296        # of
297        #         c = SystemCommand(working_dir=root,
298        #                           command="darcs remove %(entry)s")
299        #         c(entry=' '.join([e.name for e in entries]))
300        # that raises status 512 on darcs not finding the entry.
301
302        pass
303   
304    def _renameEntry(self, root, oldentry, newentry):
305        """
306        Rename an entry.
307        """
308
309        from os.path import join, exists
310        from os import rename
311       
312        # Check to see if the oldentry is still there. If it does,
313        # that probably means one thing: it's been moved and then
314        # replaced, see svn 'R' event. In this case, rename the
315        # existing old entry to something else to trick "darcs mv"
316        # (that will assume the move was already done manually) and
317        # finally restore its name.
318
319        renamed = exists(join(root, oldentry))
320        if renamed:
321            rename(oldentry, oldentry + '-TAILOR-HACKED-TEMP-NAME')
322
323        try:
324            c = SystemCommand(working_dir=root,
325                              command="darcs mv %(old)s %(new)s")
326            c(old=shrepr(oldentry), new=shrepr(newentry))
327        finally:
328            if renamed:
329                rename(oldentry + '-TAILOR-HACKED-TEMP-NAME', oldentry)
330
331    def _initializeWorkingDir(self, root, repository, module, subdir, addentry=None):
332        """
333        Execute `darcs initialize`.
334        """
335
336        from os.path import join
337       
338        c = SystemCommand(working_dir=root, command="darcs initialize")
339        c(output=True)
340
341        if c.exit_status:
342            raise TargetInitializationFailure(
343                "'darcs initialize' returned status %s" % c.exit_status)
344
345        motd = open(join(root, '_darcs/prefs/motd'), 'w')
346        motd.write(MOTD % (repository, module))
347        motd.close()
348
349        boring = open(join(root, '_darcs/prefs/boring'), 'a')
350        boring.write('^tailor.log$\n^tailor.info$\n')
351        boring.close()
352       
353        c = SystemCommand(working_dir=root,
354                          command="darcs add --case-ok --recursive"
355                          " --quiet %(entry)s")
356        c(entry=shrepr(subdir))
Note: See TracBrowser for help on using the repository browser.