source: tailor/vcpx/repository/darcs/source.py @ 1674

Revision 1674, 29.8 KB checked in by lele@…, 3 years ago (diff)

Assemble the complete changelog under darcs when PATCH_NAME_FORMAT is empty

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: Tailor -- Darcs peculiarities when used as a source
3# :Creato:   lun 10 lug 2006 00:04:59 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9This module contains the source specific bits of the darcs backend.
10"""
11
12__docformat__ = 'reStructuredText'
13
14import re
15
16from vcpx.changes import ChangesetEntry, Changeset
17from vcpx.shwrap import ExternalCommand, PIPE, STDOUT
18from vcpx.source import UpdatableSourceWorkingDir, ChangesetApplicationFailure, \
19                        GetUpstreamChangesetsFailure
20from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure
21from vcpx.tzinfo import UTC
22
23
24class DarcsChangeset(Changeset):
25    """
26    Fixup darcs idiosyncrasies:
27
28    - collapse "add A; rename A B" into "add B"
29    - collapse "rename A B; remove B" into "remove A"
30    """
31
32    def __init__(self, revision, date, author, log, entries=None, **other):
33        """
34        Initialize a new DarcsChangeset.
35        """
36
37        super(DarcsChangeset, self).__init__(revision, date, author, log, entries=None, **other)
38        self.darcs_hash = other.get('darcs_hash')
39        if entries is not None:
40            for e in entries:
41                self.addEntry(e, revision)
42
43    def __eq__(self, other):
44        equal = (self.revision == other.revision and
45                 self.date == other.date and
46                 self.author == other.author)
47        if equal:
48            other_hash = getattr(other, 'darcs_hash', None)
49            if (self.darcs_hash is not None and
50                other_hash is not None and
51                self.darcs_hash != other_hash):
52                equal = False
53        return equal
54
55    def __ne__(self, other):
56        different = (self.revision <> other.revision or
57                     self.date <> other.date or
58                     self.author <> other.author)
59        if not different:
60            other_hash = getattr(other, 'darcs_hash', None)
61            if (self.darcs_hash is not None and
62                other_hash is not None and
63                self.darcs_hash != other_hash):
64                different = True
65        return different
66
67    # Match darcs 2.1+ junk: hopefully this regex is strict enough to
68    # not obliterate useful info...
69    ignore_this = re.compile('^Ignore-this: [a-f\\d]+\\n?')
70
71    def setLog(self, log):
72        """Strip away the "Ignore-this:" noise from the changelog."""
73
74        log = self.ignore_this.sub('', log)
75        if not SynchronizableTargetWorkingDir.PATCH_NAME_FORMAT:
76            if log:
77                log = self.revision + '\n' + log
78            else:
79                log = self.revision
80        super(DarcsChangeset, self).setLog(log)
81
82    def addEntry(self, entry, revision):
83        """
84        Fixup darcs idiosyncrasies:
85
86        - collapse "add A; rename A B" into "add B"
87        - collapse "rename A B; add B" into "add B"
88        - annihilate "add A; remove A"
89        - collapse "rename A B; remove B" into "remove A"
90        - collapse "rename A B; rename B C" into "rename A C"
91        - collapse "add A; edit A" into "add A"
92        """
93
94        # This should not happen, since the parser feeds us an already built
95        # list of ChangesetEntries, anyway...
96        if not isinstance(entry, ChangesetEntry):
97            return super(DarcsChangeset, self).addEntry(entry, revision)
98
99        # Ok, before adding this entry, check it against already
100        # known: if this is an add, and there's a rename (such as "add
101        # A; rename A B; ") then...
102
103        if entry.action_kind == entry.ADDED:
104            # ... we have to check existings, because of a bug in
105            # darcs: `changes --xml` (as of 1.0.7) emits the changes
106            # in the wrong order, that is, it prefers to start with
107            # renames, *always*, even when they obviously follows the
108            # add of the same entry (even, it should apply this "fix"
109            # by its own).
110            #
111            # So, if there's a rename of this entry there, change that
112            # to an addition instead, and don't insert any other entry
113
114            # darcs hopefully use forward slashes also under win
115            dirname = entry.name+'/'
116
117            for i,e in enumerate(self.entries):
118                if e.action_kind == e.RENAMED:
119                    if e.old_name == entry.name:
120                        # Unfortunately we have to check if the order if
121                        # messed up, in that case we should not do anything.
122                        # Example: mv a a2; mkdir a; mv a2 a/b
123                        skip = False
124                        for j in self.entries:
125                            if j.action_kind == j.RENAMED and j.name.startswith(dirname):
126                                skip = True
127                                break
128                        # Luckily enough (since removes are the first entries
129                        # in the list, that is) by anticipating the add we
130                        # cure also the case below, when addition follows
131                        # edit.
132                        if not skip:
133                            e.action_kind = e.ADDED
134                            e.old_name = None
135                            e.is_directory = entry.is_directory
136
137                            # Collapse "add A; edit A" into "add A"
138                            for j,oe in enumerate(self.entries):
139                                if oe.action_kind == e.UPDATED and e.name == oe.name:
140                                    del self.entries[j]
141
142                            return e
143
144                    # The "rename A B; add B" into "add B"
145                    if e.name == entry.name:
146                        del self.entries[i]
147
148                # Assert also that add_dir events must preceeds any
149                # add_file and ren_file that have that dir as target,
150                # and that add_file preceeds any edit.
151
152                if ((e.name == entry.name or e.name.startswith(dirname))
153                    or (e.action_kind == e.RENAMED and e.old_name.startswith(dirname))):
154                    self.entries.insert(i, entry)
155                    return entry
156
157        # Likewise, if this is a deletion, and there is a rename of
158        # this entry (such as "rename A B; remove B") then turn the
159        # existing rename into a deletion instead.
160
161        # If instead the removed entry was added by the same patch,
162        # annihilate the two: a bug in darcs (possibly fixed in recent
163        # versions) created patches with ADD+EDIT+REMOVE of a single
164        # file (see tailor ticket #71, or darcs issue185). Too bad
165        # another bug (still present in 1.0.8) hides that and makes
166        # very hard (read: impossible) any workaround on the tailor
167        # side. Luckily I learnt another tiny bit of Haskell and
168        # proposed a fix for that: hopefully the patch will be
169        # accepted by darcs developers. In the meantime, I attached it
170        # to ticket #71: without that, tailor does not have enough
171        # information to do the right thing.
172
173        elif entry.action_kind == entry.DELETED:
174            for i,e in enumerate(self.entries):
175                if e.action_kind == e.RENAMED and e.name == entry.name:
176                    e.action_kind = e.DELETED
177                    e.name = e.old_name
178                    e.old_name = None
179                    e.is_directory = entry.is_directory
180                    return e
181                elif e.action_kind == e.ADDED and e.name == entry.name:
182                    del self.entries[i]
183                    return None
184                elif e.action_kind == e.DELETED and e.is_directory and \
185                         entry.name.startswith(e.name+'/'):
186                    self.entries.insert(i, entry)
187                    return e
188
189        # The "rename A B; rename B C" to "rename A C" part
190        elif entry.action_kind == entry.RENAMED:
191            # Adjust previous renames
192            olddirname = entry.old_name+'/'
193            for e in self.entries:
194                if e.action_kind == e.RENAMED and e.name.startswith(olddirname):
195                    e.name = entry.name + '/' + e.name[len(olddirname):]
196
197            for e in self.entries:
198                if e.action_kind == e.RENAMED and e.name == entry.old_name:
199                    e.name = entry.name
200                    return e
201
202            # The "rename A B; add B" into "add B", part two
203            for i,e in enumerate(self.entries):
204                if e.action_kind == e.ADDED and e.name == entry.name:
205                    return None
206
207        # Ok, it must be either an edit or a rename: the former goes
208        # obviously to the end, and since the latter, as said, come
209        # in very early, appending is just good.
210
211        self.entries.append(entry)
212        return entry
213
214
215def changesets_from_darcschanges(changes, unidiff=False, repodir=None,
216                                 chunksize=2**15, replace_badchars=None):
217    """
218    Parse XML output of ``darcs changes``.
219
220    Return a list of ``Changeset`` instances.
221
222    Filters out the (currently incorrect) tag info from
223    changesets_from_darcschanges_unsafe.
224    """
225
226    csets = changesets_from_darcschanges_unsafe(changes, unidiff,
227                                                repodir, chunksize,
228                                                replace_badchars)
229    for cs in csets:
230        yield cs
231
232def changesets_from_darcschanges_unsafe(changes, unidiff=False, repodir=None,
233                                        chunksize=2**15, replace_badchars=None):
234    """
235    Do the real work of parsing the change log, including tags.
236    Warning: the tag information in the changsets returned by this
237    function are only correct if each darcs tag in the repo depends on
238    all of the patches that precede it.  This is not a valid
239    assumption in general--a tag that does not depend on patch P can
240    be pulled in from another darcs repo after P.  We collect the tag
241    info anyway because DarcsWorkingDir._currentTags() can use it
242    safely despite this problem.  Hopefully the problem will
243    eventually be fixed and this function can be renamed
244    changesets_from_darcschanges.
245    """
246    from xml.sax import make_parser
247    from xml.sax.handler import ContentHandler, ErrorHandler
248    from datetime import datetime
249
250    class DarcsXMLChangesHandler(ContentHandler):
251        def __init__(self):
252            self.changesets = []
253            self.current = None
254            self.current_field = []
255            if unidiff and repodir:
256                cmd = ["darcs", "diff", "--unified", "--repodir", repodir,
257                       "--patch", "%(patchname)s"]
258                self.darcsdiff = ExternalCommand(command=cmd)
259            else:
260                self.darcsdiff = None
261
262        def startElement(self, name, attributes):
263            if name == 'patch':
264                self.current = {}
265                self.current['author'] = attributes['author']
266                date = attributes['date']
267                from time import strptime
268                try:
269                    # 20040619130027
270                    timestamp = datetime(*strptime(date, '%Y%m%d%H%M%S')[:6])
271                except ValueError:
272                    # Old darcs patches use the form Sun Oct 20 20:01:05 EDT 2002
273                    timestamp = datetime(*strptime(date[:19] + date[-5:], '%a %b %d %H:%M:%S %Y')[:6])
274
275                timestamp = timestamp.replace(tzinfo=UTC) # not true for the ValueError case, but oh well
276
277                self.current['date'] = timestamp
278                self.current['comment'] = ''
279                self.current['hash'] = attributes['hash']
280                self.current['entries'] = []
281                self.inverted = (attributes['inverted'] == "True")
282            elif name in ['name', 'comment', 'add_file', 'add_directory',
283                          'modify_file', 'remove_file', 'remove_directory']:
284                self.current_field = []
285            elif name == 'move':
286                self.old_name = attributes['from']
287                self.new_name = attributes['to']
288
289        def endElement(self, name):
290            if name == 'patch':
291                cset = DarcsChangeset(self.current['name'],
292                                      self.current['date'],
293                                      self.current['author'],
294                                      self.current['comment'],
295                                      self.current['entries'],
296                                      tags=self.current.get('tags',[]),
297                                      darcs_hash=self.current['hash'])
298                if self.darcsdiff:
299                    cset.unidiff = self.darcsdiff.execute(TZ='UTC',
300                        stdout=PIPE, patchname=cset.revision)[0].read()
301
302                self.changesets.append(cset)
303                self.current = None
304            elif name in ['name', 'comment']:
305                val = ''.join(self.current_field)
306                if val[:4] == 'TAG ':
307                    self.current.setdefault('tags',[]).append(val[4:])
308                self.current[name] = val
309            elif name == 'move':
310                entry = ChangesetEntry(self.new_name)
311                entry.action_kind = entry.RENAMED
312                entry.old_name = self.old_name
313                self.current['entries'].append(entry)
314            elif name in ['add_file', 'add_directory', 'modify_file',
315                          'remove_file', 'remove_directory']:
316                current_field = ''.join(self.current_field).strip()
317                if self.inverted:
318                    # the filenames in file modifications are outdated
319                    # if there are renames
320                    for i in self.current['entries']:
321                        if i.action_kind == i.RENAMED and current_field.startswith(i.old_name):
322                            current_field = current_field.replace(i.old_name, i.name)
323                entry = ChangesetEntry(current_field)
324                entry.action_kind = { 'add_file': entry.ADDED,
325                                      'add_directory': entry.ADDED,
326                                      'modify_file': entry.UPDATED,
327                                      'remove_file': entry.DELETED,
328                                      'remove_directory': entry.DELETED
329                                    }[name]
330                entry.is_directory = name.endswith('directory')
331                self.current['entries'].append(entry)
332
333        def characters(self, data):
334            self.current_field.append(data)
335
336    parser = make_parser()
337    handler = DarcsXMLChangesHandler()
338    parser.setContentHandler(handler)
339    parser.setErrorHandler(ErrorHandler())
340
341    def fixup_badchars(s, map):
342        if not map:
343            return s
344
345        ret = [map.get(c, c) for c in s]
346        return "".join(ret)
347
348    chunk = fixup_badchars(changes.read(chunksize), replace_badchars)
349    while chunk:
350        parser.feed(chunk)
351        for cs in handler.changesets:
352            yield cs
353        handler.changesets = []
354        chunk = fixup_badchars(changes.read(chunksize), replace_badchars)
355    parser.close()
356    for cs in handler.changesets:
357        yield cs
358
359
360class DarcsSourceWorkingDir(UpdatableSourceWorkingDir):
361    """
362    A source working directory under ``darcs``.
363    """
364
365    is_hash_rx = re.compile('[0-9a-f]{14}-[0-9a-f]{5}-[0-9a-f]{40}\.gz')
366
367    def _getUpstreamChangesets(self, sincerev):
368        """
369        Do the actual work of fetching the upstream changeset.
370        """
371
372        # Use the newer pull --xml-output, if possible
373        use_xml = False
374        if self.repository.darcs_version.startswith('2'):
375            cmd = self.repository.command("pull", "--dry-run", "--xml-output")
376            pull = ExternalCommand(cwd=self.repository.basedir, command=cmd)
377            output,error = pull.execute(self.repository.repository,
378                                        stdout=PIPE, stderr=PIPE, TZ='UTC0')
379            # pull --xml-output was introduced *after* 2.0.0
380            if pull.exit_status:
381                errormsg = error.read()
382                if "unrecognized option `--xml-output'" in errormsg:
383                    self.log.warning('Using darcs 1.0 non-XML parser: it may fail '
384                                     'on patches recorded before november 2003! '
385                                     'I would suggest of upgrading to latest darcs 2.0 '
386                                     '(later than 2.0+233).')
387                    # No way, fall back to old behaviour, that will possibly fail,
388                    # on patches recorded before 2003-11-01... :-|
389                else:
390                    raise GetUpstreamChangesetsFailure(
391                        "%s returned status %d saying\n%s" %
392                        (str(pull), pull.exit_status, errormsg))
393            else:
394                use_xml = True
395
396        if not use_xml:
397            cmd = self.repository.command("pull", "--dry-run")
398            pull = ExternalCommand(cwd=self.repository.basedir, command=cmd)
399            output = pull.execute(self.repository.repository,
400                                  stdout=PIPE, stderr=STDOUT, TZ='UTC0')[0]
401
402            if pull.exit_status:
403                raise GetUpstreamChangesetsFailure(
404                    "%s returned status %d saying\n%s" %
405                    (str(pull), pull.exit_status, output.read()))
406
407            return self._parseDarcsPull(output)
408        else:
409            from cStringIO import StringIO
410
411            # My initial implementation of --xml-output on darcs pull
412            # wasn't perfect, as it was printing useless verbosity before
413            # and after the actual xml. Around 2.0.0+275 I removed that...
414            # Darcs 2.4 however still emits the first line :-\
415
416            line = output.readline()
417            if line.startswith('Would pull from '):
418                # Ok, this is either darcs>2.4 or my early even noisier implementation
419                pos = output.tell()
420                line = output.readline()
421                if line.startswith('Would pull the following changes:'):
422                    # This is early implementation: skip the first two lines
423                    # (discarded above) and drop the last two as well
424                    xml = StringIO(''.join(output.readlines()[:-2]))
425                    xml.seek(0)
426                else:
427                    # This is darcs>2.4, go back to the second line,
428                    # thus ignoring only the first
429                    output.seek(pos)
430                    xml = output
431            else:
432                output.seek(0)
433                xml = output
434
435            badchars = self.repository.replace_badchars
436
437            return changesets_from_darcschanges(xml, replace_badchars=badchars)
438
439    def _parseDarcsPull(self, output):
440        """Process 'darcs pull' output to Changesets.
441        """
442        from datetime import datetime
443        from time import strptime
444        from sha import new
445
446        l = output.readline()
447        while l and not (l.startswith('Would pull the following changes:') or
448                         l == 'No remote changes to pull in!\n'):
449            l = output.readline()
450
451        if l <> 'No remote changes to pull in!\n':
452            ## Sat Jul 17 01:22:08 CEST 2004  lele@nautilus
453            ##   * Refix _getUpstreamChangesets for darcs
454
455            fsep = re.compile('[ :]+')
456            l = output.readline()
457            while not l.startswith('Making no changes:  this is a dry run.'):
458                # Assume it's a line like
459                #    Sun Jan  2 00:24:04 UTC 2005  lele@nautilus.homeip.net
460                # Use a regular expression matching multiple spaces or colons
461                # to split it, and use the first 7 fields to build up a datetime.
462                pieces = fsep.split(l.rstrip(), 8)
463                assert len(pieces)>=7, "Cannot parse %r as a patch timestamp" % l
464                date = ' '.join(pieces[:8])
465                try:
466                    author = pieces[8]
467                except IndexError, s:
468                    # darcs allows patches with empty author
469                    author = ""
470                y,m,d,hh,mm,ss,d1,d2,d3 = strptime(date, "%a %b %d %H %M %S %Z %Y")
471                date = datetime(y,m,d,hh,mm,ss,0,UTC)
472                l = output.readline().rstrip()
473                assert (l.startswith('  *') or
474                        l.startswith('  UNDO:') or
475                        l.startswith('  tagged')), \
476                        "Got %r but expected the start of the log" % l
477
478                if l.startswith('  *'):
479                    name = l[4:]
480                else:
481                    name = l[2:]
482
483                changelog = []
484                l = output.readline()
485                while l.startswith('  '):
486                    changelog.append(l[2:-1])
487                    l = output.readline()
488
489                cset = DarcsChangeset(name, date, author, '\n'.join(changelog))
490                compactdate = date.strftime("%Y%m%d%H%M%S")
491                if name.startswith('UNDO: '):
492                    name = name[6:]
493                    inverted = 't'
494                else:
495                    inverted = 'f'
496
497                if name.startswith('tagged '):
498                    name = name[7:]
499                    if cset.tags is None:
500                        cset.tags = [name]
501                    else:
502                        cset.tags.append(name)
503                    name = "TAG " + name
504
505                phash = new()
506                phash.update(name)
507                phash.update(author)
508                phash.update(compactdate)
509                phash.update(''.join(changelog))
510                phash.update(inverted)
511                cset.darcs_hash = '%s-%s-%s.gz' % (compactdate,
512                                                   new(author).hexdigest()[:5],
513                                                   phash.hexdigest())
514
515
516                yield cset
517
518                while not l.strip():
519                    l = output.readline()
520
521    def _applyChangeset(self, changeset):
522        """
523        Do the actual work of applying the changeset to the working copy.
524        """
525
526        needspatchesopt = False
527        if hasattr(changeset, 'darcs_hash'):
528            selector = '--match'
529            revtag = 'hash ' + changeset.darcs_hash
530        elif changeset.revision.startswith('tagged '):
531            selector = '--tag'
532            revtag = changeset.revision[7:]
533        else:
534            selector = '--match'
535            revtag = 'date "%s" && author "%s"' % (
536                changeset.date.strftime("%Y%m%d%H%M%S"),
537                changeset.author)
538            # The 'exact' matcher doesn't groke double quotes:
539            # """currently there is no provision for escaping a double
540            # quote, so you have to choose between matching double
541            # quotes and matching spaces"""
542            if not '"' in changeset.revision:
543                revtag += ' && exact "%s"' % changeset.revision.replace('%', '%%')
544            else:
545                needspatchesopt = True
546
547        cmd = self.repository.command("pull", "--all", "--quiet",
548                                      selector, revtag)
549
550        if needspatchesopt:
551            cmd.extend(['--patches', re.escape(changeset.revision)])
552
553        pull = ExternalCommand(cwd=self.repository.basedir, command=cmd)
554        output = pull.execute(stdout=PIPE, stderr=STDOUT, input='y')[0]
555
556        if pull.exit_status:
557            raise ChangesetApplicationFailure(
558                "%s returned status %d saying\n%s" %
559                (str(pull), pull.exit_status, output.read()))
560
561        conflicts = []
562        line = output.readline()
563        while line:
564            if line.startswith('We have conflicts in the following files:'):
565                files = output.readline()[:-1].split(' ')
566                self.log.warning("Conflict after 'darcs pull': %s",
567                                 ' '.join(files))
568                conflicts.extend(files)
569            line = output.readline()
570
571        # Complete the changeset with its entries
572
573        cmd = self.repository.command("changes", selector, revtag,
574                                      "--xml-output", "--summ")
575        changes = ExternalCommand(cwd=self.repository.basedir, command=cmd)
576        last = changesets_from_darcschanges(changes.execute(stdout=PIPE)[0],
577                                            replace_badchars=self.repository.replace_badchars)
578        try:
579            entries = last.next().entries
580        except StopIteration:
581            entries = None
582
583        if entries:
584            for e in entries:
585                changeset.addEntry(e, changeset.revision)
586
587        return conflicts
588
589    def _handleConflict(self, changeset, conflicts, conflict):
590        """
591        Handle the conflict raised by the application of the upstream changeset.
592
593        Override parent behaviour: with darcs, we need to execute a revert
594        on the conflicted files, **trashing** local changes, but there should
595        be none of them in tailor context.
596        """
597
598        from os import walk, unlink
599        from os.path import join
600        from re import compile
601
602        self.log.info("Reverting changes to %s, to solve the conflict",
603                      ' '.join(conflict))
604        cmd = self.repository.command("revert", "--all")
605        revert = ExternalCommand(cwd=self.repository.basedir, command=cmd)
606        revert.execute(conflict, input="\n")
607
608        # Remove also the backups made by darcs
609        bckre = compile('-darcs-backup[0-9]+$')
610        for root, dirs, files in walk(self.repository.basedir):
611            backups = [f for f in files if bckre.search(f)]
612            for bck in backups:
613                self.log.debug("Removing backup file %r in %r", bck, root)
614                unlink(join(root, bck))
615
616    def _checkoutUpstreamRevision(self, revision):
617        """
618        Concretely do the checkout of the upstream revision and return
619        the last applied changeset.
620        """
621
622        from os.path import join, exists
623        from os import mkdir
624        from vcpx.source import InvocationError
625
626        if not self.repository.repository:
627            raise InvocationError("Must specify a the darcs source repository")
628
629        if revision == 'INITIAL' or self.is_hash_rx.match(revision):
630            initial = True
631
632            if revision == 'INITIAL':
633                cmd = self.repository.command("changes", "--xml-output",
634                                              "--repo", self.repository.repository,
635                                               "--reverse")
636                changes = ExternalCommand(command=cmd)
637                output = changes.execute(stdout=PIPE)[0]
638
639                if changes.exit_status:
640                    raise ChangesetApplicationFailure(
641                        "%s returned status %d saying\n%s" %
642                        (str(changes), changes.exit_status,
643                         output and output.read() or ''))
644
645                csets = changesets_from_darcschanges(output, replace_badchars=self.repository.replace_badchars)
646                try:
647                    changeset = csets.next()
648                except StopIteration:
649                    # No changesets, no party!
650                    return None
651
652                revision = 'hash %s' % changeset.darcs_hash
653            else:
654                revision = 'hash %s' % revision
655        else:
656            initial = False
657
658        # Darcs 2.0 fails with "darcs get --to-match", see issue885
659        darcs2 = self.repository.darcs_version.startswith('2')
660        if darcs2 or self.repository.subdir == '.' or exists(self.repository.basedir):
661            # This is currently *very* slow, compared to the darcs get
662            # below!
663            if not exists(join(self.repository.basedir, '_darcs')):
664                if not exists(self.repository.basedir):
665                    mkdir(self.repository.basedir)
666
667                cmd = self.repository.command("initialize")
668                init = ExternalCommand(cwd=self.repository.basedir, command=cmd)
669                init.execute()
670
671                if init.exit_status:
672                    raise TargetInitializationFailure(
673                        "%s returned status %s" % (str(init),
674                                                   init.exit_status))
675
676                cmd = self.repository.command("pull", "--all", "--quiet")
677                if revision and revision<>'HEAD':
678                    cmd.extend([initial and "--match" or "--tag", revision])
679                dpull = ExternalCommand(cwd=self.repository.basedir, command=cmd)
680                output = dpull.execute(self.repository.repository,
681                                       stdout=PIPE, stderr=STDOUT)[0]
682
683                if dpull.exit_status:
684                    raise TargetInitializationFailure(
685                        "%s returned status %d saying\n%s" %
686                        (str(dpull), dpull.exit_status, output.read()))
687        else:
688            # Use much faster 'darcs get'
689            cmd = self.repository.command("get", "--quiet")
690            if revision and revision<>'HEAD':
691                cmd.extend([initial and "--to-match" or "--tag", revision])
692            else:
693                cmd.append("--partial")
694            dget = ExternalCommand(command=cmd)
695            output = dget.execute(self.repository.repository, self.repository.basedir,
696                                  stdout=PIPE, stderr=STDOUT)[0]
697
698            if dget.exit_status:
699                raise TargetInitializationFailure(
700                    "%s returned status %d saying\n%s" %
701                    (str(dget), dget.exit_status, output.read()))
702
703        cmd = self.repository.command("changes", "--last", "1",
704                                      "--xml-output")
705        changes = ExternalCommand(cwd=self.repository.basedir, command=cmd)
706        output = changes.execute(stdout=PIPE)[0]
707
708        if changes.exit_status:
709            raise ChangesetApplicationFailure(
710                "%s returned status %d saying\n%s" %
711                (str(changes), changes.exit_status, output.read()))
712
713        try:
714            last = changesets_from_darcschanges(
715                output, replace_badchars=self.repository.replace_badchars).next()
716        except StopIteration:
717            last = None
718
719        return last
Note: See TracBrowser for help on using the repository browser.