source: tailor/vcpx/monotone.py @ 689

Revision 689, 28.9 KB checked in by lele@…, 8 years ago (diff)

Monotone DB initialization
This definitely needs review from a Monotone user, it does not even pass
the test!

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