source: tailor/vcpx/monotone.py @ 799

Revision 799, 33.2 KB checked in by lele@…, 8 years ago (diff)

M-x whitespace-cleanup and several documentation/comment refillment

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Monotone details
3# :Creato:   Tue Apr 12 01:28:10 CEST 2005
4# :Autore:   Markus Schiltknecht <markus@bluegap.ch>
5# :Autore:   Riccardo Ghetta <birrachiara@tin.it>
6# :Licenza:  GNU General Public License
7#
8
9"""
10This module contains supporting classes for Monotone.
11"""
12
13__docformat__ = 'reStructuredText'
14
15from shwrap import ExternalCommand, PIPE, ReopenableNamedTemporaryFile, STDOUT
16from source import UpdatableSourceWorkingDir, InvocationError, \
17     ChangesetApplicationFailure, GetUpstreamChangesetsFailure
18from target import SyncronizableTargetWorkingDir, TargetInitializationFailure
19from changes import ChangesetEntry,Changeset
20from sys import stderr
21from os.path import exists, join, isdir
22from os import renames, access, F_OK
23from string import whitespace
24
25MONOTONERC = """\
26function get_passphrase(KEYPAIR_ID)
27  return "%s"
28end
29"""
30
31class ExternalCommandChain:
32    """
33    This class implements command piping, i.e. a chain of
34    ExternalCommand, each feeding its stdout to the stdin of next
35    command in the chain If a command fails, the chain breaks and
36    returns error.
37
38    Note:
39    This class implements only a subset of ExternalCommand functionality
40    """
41    def __init__(self, command, cwd=None):
42        self.commandchain =command
43        self.cwd = cwd
44        self.exit_status = 0
45
46    def execute(self):
47        outstr = None
48        for cmd in self.commandchain:
49            input = outstr
50            exc = ExternalCommand(cwd=self.cwd, command=cmd)
51            out, err = exc.execute(input=input, stdout=PIPE, stderr=PIPE)
52            self.exit_status = exc.exit_status
53            if self.exit_status:
54                break
55            outstr = out.getvalue()
56        return out, err
57
58class MonotoneChangeset(Changeset):
59    """
60    Monotone changesets differ from standard Changeset because:
61    1. only the "revision" field is used for eq/ne comparison
62    2. have additional properties used to handle history linearization
63    """
64
65    def __init__(self, linearized_ancestor, revision):
66        """
67        Initializes a new MonotoneChangeset. The linearized_ancestor
68        parameters is the fake ancestor used for linearization. The
69        very first revision tailorized has lin_ancestor==None
70        """
71
72        Changeset.__init__(self, revision=revision, date=None, author=None, log="")
73        self.lin_ancestor = linearized_ancestor
74
75    def __eq__(self, other):
76        return (self.revision == other.revision)
77
78    def __ne__(self, other):
79        return (self.revision <> other.revision)
80
81    def __str__(self):
82        s = [Changeset.__str__(self)]
83        s.append('linearized ancestor: %s' % self.lin_ancestor)
84        s.append('real ancestor(s): %s' % ','.join(self.real_ancestors))
85        return '\n'.join(s)
86
87    def update(self, real_dates, authors, log, real_ancestors):
88        """
89        Updates the monotone changeset secondary data
90        """
91        self.author=".".join(authors)
92        self.setLog(log)
93        self.date = real_dates[0]
94        self.real_dates = real_dates
95        self.real_ancestors = real_ancestors
96
97class MonotoneLogParser:
98    """
99    Obtain and parse a *single* "monotone log" output, reconstructing
100    the revision information
101    """
102
103    class PrefixRemover:
104        """
105        Helper class. Matches a prefix, allowing access to the text following
106        """
107        def __init__(self, str):
108            self.str = str
109            self.value=""
110
111        def __call__(self, prefix):
112            if self.str.startswith(prefix):
113                self.value = self.str[len(prefix):].strip()
114                return True
115            else:
116                return False
117
118    # logfile states
119    SINGLE = 0  # single line state
120    ADD = 1 # in add file/dir listing
121    MOD = 2 # in mod file/dir listing
122    DEL = 3 # in delete file/dir listing
123    REN = 4 # in renamed file/dir listing
124    LOG = 5 # in changelog listing
125    CMT = 6 # in comment listing
126
127    def __init__(self, repository, working_dir):
128        self.working_dir = working_dir
129        self.repository = repository
130
131    def parse(self, revision):
132        from datetime import datetime
133
134        self.revision=""
135        self.ancestors=[]
136        self.authors=[]
137        self.dates=[]
138        self.changelog=""
139
140        cmd = self.repository.command("log", "--db", self.repository.repository,
141                                      "--last", "1", "--revision", revision)
142        mtl = ExternalCommand(cwd=self.working_dir, command=cmd)
143        outstr = mtl.execute(stdout=PIPE)
144        if mtl.exit_status:
145            raise GetUpstreamChangesetsFailure("monotone log returned status %d" % mtl.exit_status)
146
147        logs = ""
148        comments = ""
149        state = self.SINGLE
150        loglines = outstr[0].getvalue().splitlines()
151        for curline in loglines:
152
153            pr = self.PrefixRemover(curline)
154            if pr("Revision:"):
155                if pr.value != revision:
156                    raise GetUpstreamChangesetsFailure("Revision doesn't match. Expected %s, found %s" % revision, pr.value)
157                state = self.SINGLE
158            elif pr("Ancestor:"):
159                if pr.value:
160                    self.ancestors.append(pr.value) # cset could be a merge and have multiple ancestors
161                state = self.SINGLE
162            elif pr("Author:"):
163                self.authors.append(pr.value)
164                state = self.SINGLE
165            elif pr("Date:"):
166                    # monotone dates are expressed in ISO8601, always UTC
167                    dateparts = pr.value.split('T')
168                    assert len(dateparts) >= 2, `dateparts`
169                    day = dateparts[0]
170                    time = dateparts[1]
171                    y,m,d = map(int, day.split(day[4]))
172                    hh,mm,ss = map(int, time.split(':'))
173                    date = datetime(y,m,d,hh,mm,ss)
174                    self.dates.append(date)
175                    state = self.SINGLE
176            elif pr("Branch:") or pr("Tag"):
177                # unused data, just resetting state
178                state = self.SINGLE
179            elif pr("Deleted files:") or pr("Deleted directories:"):
180                state=self.DEL
181            elif pr("Renamed files:") or pr("Renamed directories:"):
182                state=self.DEL
183            elif pr("Added files:") or pr("Added directories:"):
184                state=self.ADD
185            elif pr("Modified files:") or pr("Modified directories:"):
186                state=self.ADD
187            elif pr("ChangeLog:"):
188                state=self.LOG
189            elif pr("Comments:"):
190                comments=comments + "Note:\n"
191                state=self.CMT
192            else:
193                # otherwise, it must be a log/comment/changeset entry, or an unknown cert line
194                if state == self.SINGLE:
195                    # line coming from an unknown cert
196                    pass
197                elif state == self.LOG:
198                    # log line, accumulate string
199                    logs = logs + curline + "\n"
200                elif state == self.CMT:
201                    # comment line, accumulate string
202                    comments = comments + curline + "\n"
203                else:
204                    # parse_cset_entry(mode, chset, curline.strip()) # cset entry, handle
205                    pass # we ignore cset info
206
207        # parsing terminated, verify the data
208        if len(self.authors)<1 or len(self.dates)<1 or revision=="":
209            raise GetUpstreamChangesetsFailure("Error parsing log of revision %s. Missing data" % revision)
210        self.changelog = logs + comments
211
212    def convertLog(self, chset):
213        self.parse(chset.revision)
214
215        chset.update(real_dates=self.dates,
216                     authors=self.authors,
217                     log=self.changelog,
218                     real_ancestors=self.ancestors)
219
220        return chset
221
222class MonotoneDiffParser:
223    """
224    This class obtains a diff beetween two arbitrary revisions, parsing
225    it to get changeset entries.
226
227    Note: since monotone tracks directories implicitly, a fake "add dir"
228    cset entry is generated when a file is added to a subdir
229    """
230
231    class BasicIOTokenizer:
232        # To write its control files, monotone uses a format called
233        # internally "basic IO", a stanza file format with items
234        # separated by blank lines. Lines are terminated by newlines.
235        # The format supports strings, sequence of chars contained by
236        # ". String could contain newlines and to insert a " in the
237        # middle you escape it with \ (and \\ is used to obtain the \
238        # char itself) basic IO files are always UTF-8
239        # This class implements a small tokenizer for basic IO
240
241        def __init__(self, stream):
242            self.stream = stream
243
244        def _string_token(self):
245            # called at start of string, returns the complete string
246            # Note: Exceptions checked outside
247            escape = False
248            str=['"']
249            while True:
250                ch = self.it.next()
251                if escape:
252                    escape=False
253                    str.append(ch)
254                    continue
255                elif ch=='\\':
256                    escape=True
257                    continue
258                else:
259                    str.append(ch)
260                    if ch=='"':
261                        break   # end of filename string
262            return "".join(str)
263
264        def _normal_token(self, startch):
265            # called at start of a token, stops at first whitespace
266            # Note: Exceptions checked outside
267            tok=[startch]
268            while True:
269                ch = self.it.next()
270                if ch in whitespace:
271                    break
272                tok.append(ch)
273
274            return "".join(tok)
275
276        def __iter__(self):
277            # restart the iteration
278            self.it = iter(self.stream)
279            return self
280
281        def next(self):
282            token =""
283            while True:
284                ch = self.it.next() # here we just propagate the StopIteration ...
285                if ch in whitespace or ch=='#':
286                    continue  # skip spaces beetween tokens ...
287                elif ch == '"':
288                    try:
289                        token = self._string_token()
290                        break
291                    except StopIteration:
292                        # end of stream reached while in a string: Error!!
293                        raise GetUpstreamChangesetsFailure("diff end while in string parsing.")
294                else:
295                    token = self._normal_token(ch)
296                    break
297            return token
298
299    def __init__(self, repository, working_dir):
300        self.working_dir = working_dir
301        self.repository = repository
302
303    def convertDiff(self, chset):
304        """
305        Fills a chset with the details data coming by a diff beetween
306        chset lin_ancestor and revision (i.e. the linearized history)
307        """
308        if (not chset.lin_ancestor or
309            not chset.revision or
310            chset.lin_ancestor == chset.revision):
311            raise GetUpstreamChangesetsFailure(
312                "Internal error: MonotoneDiffParser.convertDiff called "
313                "with invalid parameters: lin_ancestor %s, revision %s" %
314                (chset.lin_ancestor, chset.revision))
315
316        # the order of revisions is very important. Monotone gives a
317        # diff from the first to the second
318        cmd = self.repository.command("diff",
319                                      "--db", self.repository.repository,
320                                      "--revision", chset.lin_ancestor,
321                                      "--revision", chset.revision)
322
323        mtl = ExternalCommand(cwd=self.working_dir, command=cmd)
324        outstr = mtl.execute(stdout=PIPE)
325        if mtl.exit_status:
326            raise GetUpstreamChangesetsFailure(
327                "monotone diff returned status %d" % mtl.exit_status)
328
329        # monotone diffs are prefixed by a section containing
330        # metainformations about files
331        # The section terminates with the first file diff, and each
332        # line is prepended by the patch comment char (#).
333        tk = self.BasicIOTokenizer(outstr[0].getvalue())
334        tkiter = iter(tk)
335        in_item = False
336        try:
337            while True:
338                token = tkiter.next()
339                if token.startswith("========"):
340                    # found first patch marker. Changeset info terminated
341                    in_item = False
342                    break
343                else:
344                    in_item = False
345                    # now, next token should be a filename
346                    fname = tkiter.next()
347                    if fname[0] != '"':
348                        raise GetUpstreamChangesetsFailure(
349                            "Unexpected token sequence: '%s' "
350                            "followed by '%s'" %(token, fname))
351
352                    # ok, is a file, control changesets data
353                    if token == "add_file" or token=="add_directory":
354                        chentry = chset.addEntry(fname[1:-1], chset.revision)
355                        chentry.action_kind = chentry.ADDED
356                    elif token == "delete_file" or token=="delete_directory":
357                        chentry = chset.addEntry(fname[1:-1], chset.revision)
358                        chentry.action_kind = chentry.DELETED
359                    elif token == "rename_file" or token=="rename_directory":
360                        # renames are in the form:  oldname to newname
361                        tow = tkiter.next()
362                        newname = tkiter.next()
363                        if tow != "to" or fname[0]!='"':
364                            raise GetUpstreamChangesetsFailure(
365                                "Unexpected rename token sequence: '%s' "
366                                "followed by '%s'" %(tow, newname))
367                        chentry = chset.addEntry(newname[1:-1], chset.revision)
368                        chentry.action_kind = chentry.RENAMED
369                        chentry.old_name= fname[1:-1]
370                    elif token == "patch":
371                        # patch entries are in the form: from oldrev to newrev
372                        fromw = tkiter.next()
373                        oldr = tkiter.next()
374                        tow = tkiter.next()
375                        newr = tkiter.next()
376                        if fromw != "from" or tow != "to":
377                            raise GetUpstreamChangesetsFailure(
378                                "Unexpected patch token sequence: '%s' "
379                                "followed by '%s','%s','%s'" % (fromw, oldr,
380                                                                tow, newr))
381
382                        # patch entries are generated also for files
383                        # added, so we must ignore the entry if
384                        # already present
385                        if len( [e for e in chset.entries if e.name==fname[1:-1]])==0:
386                            # is a real update
387                            chentry = chset.addEntry(fname[1:-1], chset.revision)
388                            chentry.action_kind = chentry.UPDATED
389
390        except StopIteration:
391            if in_item:
392                raise GetUpstreamChangesetsFailure("Unexpected end of 'diff' parsing changeset info")
393
394
395class MonotoneRevToCset:
396    """
397    This class is used to create changesets from revision ids.
398
399    Since most backends (and tailor itself) doesn't support monotone
400    multihead feature, sometimes we need to linearize the revision
401    graph, creating syntethized (i.e. fake) edges beetween revisions.
402
403    The revision itself is real, only its ancestors (and all changes
404    beetween) are faked.
405
406    To properly do this, changeset are created by a mixture of 'log'
407    and 'diff' output. Log gives the revision data, diff the
408    differences beetween revisions.
409
410    Monotone also supports multiple authors/tags/comments for each
411    revision, while tailor allows only single values.
412
413    We collapse those multiple data (when present) to single entries
414    in the following manner:
415
416    author
417      all entries separated by a comma
418
419    date
420      chooses only one, at random
421
422    changelog
423      all entries appended, without a specific order
424
425    comment
426      all comments are appended to the changelog string, prefixed by a
427      "Note:" line
428
429    tag
430      not used by tailor. Ignored
431
432    branch
433      ignored (tailor follows only a single branch)
434
435    testresult
436      ignored
437
438    other certs
439      ignored
440
441    Changesets created by monotone will have additional fields with
442    the original data:
443
444    real_ancestors
445      list of the real revision ancestor(s)
446
447    real_dates
448      list with all date certs
449
450    lin_ancestor
451      linearized ancestor (i.e. previous revision in the linearized history)
452    """
453
454    def __init__(self, repository, working_dir):
455        self.working_dir = working_dir
456        self.repository = repository
457        self.logparser = MonotoneLogParser(repository=repository,
458                                           working_dir=working_dir)
459        self.diffparser = MonotoneDiffParser(repository=repository,
460                                             working_dir=working_dir)
461
462    def _cset_from_rev(self, lin_ancestor, revision):
463        # prepare a new changeset and fill it with rev data
464        chset = MonotoneChangeset(lin_ancestor, revision)
465        self.updateCset(chset)
466        return chset
467
468    def updateCset(self, chset):
469        # Parsing the log fills the changeset from revision data
470        self.logparser.convertLog(chset)
471
472        # if an ancestor is available, fills the cset with file/dir entries
473        if chset.lin_ancestor:
474            self.diffparser.convertDiff(chset)
475
476    def getCset(self, revlist):
477        # receives a revlist, already toposorted (i.e. ordered by
478        # ancestry) and outputs a list of changesets
479        cslist=[]
480        anc=revlist[0]
481        for r in revlist[1:]:
482            cslist.append(self._cset_from_rev(anc, r))
483            anc=r
484        return cslist
485
486
487class MonotoneWorkingDir(UpdatableSourceWorkingDir, SyncronizableTargetWorkingDir):
488
489    def convert_head_initial(self, repository, module, revision, working_dir):
490        """
491        This method handles HEAD and INITIAL pseudo-revisions, converting
492        them to monotone revids
493        """
494        effective_rev = revision
495        if revision == 'HEAD' or revision=='INITIAL':
496            # in both cases we need the head(s) of the requested branch
497            cmd = self.repository.command("automate","heads",
498                                          "--db", repository, module)
499            mtl = ExternalCommand(cwd=working_dir, command=cmd)
500            outstr = mtl.execute(stdout=PIPE)
501            if mtl.exit_status:
502                raise InvocationError("The branch '%s' is empty" % module)
503
504            revision = outstr[0].getvalue().split()
505            if revision == 'HEAD':
506                if len(revision)>1:
507                    raise InvocationError("Branch '%s' has multiple heads. "
508                                          "Please choose only one." % module)
509                effective_rev=revision[0]
510            else:
511                # INITIAL requested. We must get the ancestors of
512                # current head(s), topologically sort them and pick
513                # the first (i.e. the "older" revision). Unfortunately
514                # if the branch has multiple heads then we could end
515                # up with only part of the ancestry graph.
516                if len(revision)>1:
517                    stderr.write("Branch '%s' has multiple heads. There "
518                                 "is no guarantee to reconstruct the "
519                                 "full history." % module)
520                cmd = [ self.repository.command("automate","ancestors",
521                                                "--db",repository),
522                        self.repository.command("automate","toposort",
523                                                "--db",repository, "-@-")
524                        ]
525                cmd[0].extend(revision)
526                cld = ExternalCommandChain(cwd=working_dir, command=cmd)
527                outstr = cld.execute()
528                if cld.exit_status:
529                    raise InvocationError("Ancestor reading returned "
530                                          "status %d" % cld.exit_status)
531                revision = outstr[0].getvalue().split()
532                effective_rev=revision[0]
533        return effective_rev
534
535    ## UpdatableSourceWorkingDir
536
537    def _getUpstreamChangesets(self, sincerev=None):
538        # monotone descendents returns results sorted in alpha order
539        # here we want ancestry order, so descendents output is feed back to
540        # mtn for a toposort ...
541        cmd = [ self.repository.command("automate","descendents",
542                                        "--db", self.repository.repository,
543                                        sincerev),
544                self.repository.command("automate","toposort",
545                                        "--db", self.repository.repository,
546                                        "-@-")
547                ]
548        cld = ExternalCommandChain(cwd=self.repository.rootdir, command=cmd)
549        outstr = cld.execute()
550        if cld.exit_status:
551            raise InvocationError("monotone descendents returned "
552                                  "status %d" % cld.exit_status)
553
554        # now childs is a list of revids, we must transform it in a
555        # list of monotone changesets at this time we fill only the
556        # linearized ancestor and revision ids, because at this time
557        # we need only to know WICH changesets must be applied to the
558        # target repo, not WHAT are the changesets
559        childs = outstr[0].getvalue().split()
560        chlist = []
561        lin_anc=sincerev
562        for r in childs:
563            chlist.append(MonotoneChangeset(lin_anc, r))
564            lin_anc = r
565        return chlist
566
567    def _applyChangeset(self, changeset):
568        cmd = self.repository.command("update", "--revision", changeset.revision)
569        mtl = ExternalCommand(cwd=self.basedir, command=cmd)
570        mtl.execute()
571        if mtl.exit_status:
572            raise ChangesetApplicationFailure("'mtn update' returned "
573                                              "status %s" % mtl.exit_status)
574        mtr = MonotoneRevToCset(repository=self.repository,
575                                working_dir=self.basedir)
576        mtr.updateCset( changeset )
577
578        return False   # no conflicts
579
580    def _checkoutUpstreamRevision(self, revision):
581        """
582        Concretely do the checkout of the FIRST upstream revision.
583        """
584        effrev = self.convert_head_initial(self.repository.repository,
585                                           self.repository.module, revision,
586                                           self.basedir)
587        if not exists(join(self.basedir, 'MT')):
588            self.log_info("checking out a working copy")
589            cmd = self.repository.command("co",
590                                          "--db", self.repository.repository,
591                                          "--revision", effrev,
592                                          "--branch", self.repository.module,
593                                          self.repository.subdir)
594            mtl = ExternalCommand(cwd=self.repository.rootdir, command=cmd)
595            mtl.execute()
596            if mtl.exit_status:
597                raise TargetInitializationFailure(
598                    "'monotone co' returned status %s" % mtl.exit_status)
599        else:
600            self.log_info("%s already exists, assuming it's a monotone "
601                          "working dir" % self.basedir)
602
603        # Ok, now the workdir contains the checked out revision. We
604        # need to return a changeset describing it.  Since this is the
605        # first revision checked out, we don't have a (linearized)
606        # ancestor, so we must use None as the lin_ancestor parameter
607        chset = MonotoneChangeset(None, effrev)
608
609        # now we update the new chset with basic data - without the
610        # linearized ancestor, changeset entries will NOT be filled
611        mtr = MonotoneRevToCset(repository=self.repository,
612                                working_dir=self.basedir)
613        mtr.updateCset(chset)
614        return chset
615
616    ## SyncronizableTargetWorkingDir
617
618    def _addPathnames(self, names):
619        """
620        Add some new filesystem objects, skipping directories (directory
621        addition is implicit in monotone)
622        """
623        fnames=[]
624        for fn in names:
625            if isdir(join(self.basedir, fn)):
626                self.log_info("ignoring addition of directory '%s' (%s)" %
627                              (fn, join(self.basedir, fn)) )
628            else:
629                fnames.append(fn)
630        if len(fnames):
631            # ok, we still have something to add
632            cmd = self.repository.command("add")
633            add = ExternalCommand(cwd=self.basedir, command=cmd)
634            add.execute(fnames)
635            if add.exit_status:
636                raise ChangesetApplicationFailure("%s returned status %s" %
637                                                  (str(add),add.exit_status))
638
639
640    def _addSubtree(self, subdir):
641        """
642        Add a whole subtree
643        """
644        cmd = self.repository.command("add")
645        add = ExternalCommand(cwd=self.basedir, command=cmd)
646        add.execute(subdir)
647        if add.exit_status:
648            raise ChangesetApplicationFailure("%s returned status %s" %
649                                              (str(add),add.exit_status))
650
651    def _commit(self, date, author, patchname, changelog=None, entries=None):
652        """
653        Commit the changeset.
654        """
655
656        from sys import getdefaultencoding
657
658        encoding = ExternalCommand.FORCE_ENCODING or getdefaultencoding()
659
660        logmessage = []
661        if patchname:
662            logmessage.append(patchname.encode(encoding))
663        if changelog:
664            logmessage.append(changelog.encode(encoding))
665
666        rontf = ReopenableNamedTemporaryFile('mtn', 'tailor')
667        log = open(rontf.name, "w")
668        log.write('\n'.join(logmessage))
669        log.close()
670
671        cmd = self.repository.command("commit", "--author", author,
672                                      "--date", date.isoformat(),
673                                      "--message-file", rontf.name)
674        commit = ExternalCommand(cwd=self.basedir, command=cmd)
675
676        if not entries:
677            entries = ['.']
678
679        output, error = commit.execute(entries, stdout=PIPE, stderr=PIPE)
680
681        # monotone complaints if there are no changes from the last commit.
682        # we ignore those errors ...
683        if commit.exit_status:
684            text = error.read()
685            if text.find("monotone: misuse: no changes to commit") == -1:
686                self.log_error(text)
687                raise ChangesetApplicationFailure(
688                    "%s returned status %s" % (str(commit),commit.exit_status))
689            else:
690                stderr.write("No changes to commit - changeset ignored\n")
691
692    def _removePathnames(self, names):
693        """
694        Remove some filesystem object.
695        """
696
697        # Monotone currently doesn't allow removing a directory, so we
698        # must remove every item separately and intercept monotone
699        # directory errore messages.  We can't just filter the
700        # directories, because the wc doesn't contain them anymore ...
701        cmd = self.repository.command("drop")
702        drop = ExternalCommand(cwd=self.basedir, command=cmd)
703        for fn in names:
704            dum, error = drop.execute(fn, stderr=PIPE)
705            if drop.exit_status:
706                if not error.read().find("drop <directory>"):
707                    log_error(error.read())
708                    raise ChangesetApplicationFailure("%s returned status %s" %
709                                                      (str(drop),
710                                                       drop.exit_status))
711
712    def _renamePathname(self, oldname, newname):
713        """
714        Rename a filesystem object.
715        """
716        # this function is called *after* the file/dir has changed name,
717        # and monotone doesn't like it.
718        # we put names back to make it happy ...
719        if access(join(self.basedir, newname), F_OK):
720            if access(join(self.basedir, oldname), F_OK):
721                raise ChangesetApplicationFailure("Can't rename %s to %s. "
722                                                  "Both names already exist" %
723                                                  (oldname, newname))
724            renames(join(self.basedir, newname), join(self.basedir, oldname))
725            self.log_info("preparing to rename %s->%s" % (oldname, newname))
726
727        cmd = self.repository.command("rename")
728        rename = ExternalCommand(cwd=self.basedir, command=cmd)
729        rename.execute(oldname, newname)
730
731        # redo the rename ...
732        renames(join(self.basedir, oldname), join(self.basedir, newname))
733        if rename.exit_status:
734            raise ChangesetApplicationFailure("%s returned status %s" %
735                                              (str(rename),rename.exit_status))
736
737    def __createRepository(self, target_repository):
738        """
739        Create a new monotone DB, storing the commit keys, if available
740        """
741
742        cmd = self.repository.command("db", "init", "--db",
743                                      target_repository.repository)
744        init = ExternalCommand(command=cmd)
745        init.execute()
746
747        if init.exit_status:
748            raise TargetInitializationFailure("Was not able to initialize "
749                                              "the monotone db at %r" %
750                                              target_repository)
751
752        if target_repository.keyfile:
753            # a key file is available, read into the database
754            keyfile = file(target_repository.keyfile)
755            cmd = self.repository.command("read", "--db",
756                                          target_repository.repository)
757            regkey = ExternalCommand(command=cmd)
758            regkey.execute(input=keyfile)
759        else:
760            # no keyfile specified, generate a new key - if a
761            # passphrase is defined, automatically provide it The
762            # keyid must be available
763            if not target_repository.keyid:
764                raise TargetInitializationFailure("Can't setup the monotone "
765                                                  "repository %r. "
766                                                  "A keyfile or keyid must "
767                                                  "be provided." %
768                                                  target_repository)
769            cmd = self.repository.command("genkey", "--db",
770                                          target_repository.repository)
771            regkey = ExternalCommand(command=cmd)
772            if target_repository.passphrase:
773                passp="%s\n%s\n" % (target_repository.passphrase,
774                                    target_repository.passphrase)
775            regkey.execute(target_repository.keyid, input=passp)
776
777        if regkey.exit_status:
778            raise TargetInitializationFailure("Was not able to setup "
779                                              "the monotone initial key at %r" %
780                                              target_repository)
781
782    def _prepareTargetRepository(self):
783        """
784        Check for target repository existence, eventually create it.
785        """
786
787        from os.path import exists
788
789        if not self.repository.repository:
790            return
791
792        if not exists(self.repository.repository):
793            self.__createRepository(self.repository)
794
795    def _prepareWorkingDirectory(self, source_repo):
796        """
797        Possibly checkout a working copy of the target VC, that will host the
798        upstream source tree, when overriden by subclasses.
799        """
800
801        from os.path import join, exists
802
803        if not self.repository.repository or exists(join(self.basedir, 'MT')):
804            return
805
806        cmd = self.repository.command("setup",
807                                      "--db", self.repository.repository,
808                                      "--branch", self.repository.module)
809
810        if not self.repository.module:
811            raise TargetInitializationFailure("Monotone needs a module "
812                                              "defined (to be used as "
813                                              "commit branch)")
814
815        setup = ExternalCommand(command=cmd)
816        setup.execute(self.basedir)
817
818        if self.repository.passphrase:
819            monotonerc = open(join(self.basedir, 'MT', 'monotonerc'), 'w')
820            monotonerc.write(MONOTONERC % self.repository.passphrase)
821            monotonerc.close()
822
823    def _initializeWorkingDir(self):
824        """
825        Setup the monotone working copy
826
827        The user must setup a monotone working directory himself or use the
828        tailor config file to provide parameters for creation. Then
829        we simply use 'monotone commit', without having to specify a database
830        file or branch. Monotone looks up the database and branch in it's MT
831        directory.
832        """
833
834        if not exists(join(self.basedir, 'MT')):
835            raise TargetInitializationFailure("Please setup '%s' as a "
836                                              "monotone working directory" %
837                                              self.basedir)
838
839        SyncronizableTargetWorkingDir._initializeWorkingDir(self)
Note: See TracBrowser for help on using the repository browser.