source: tailor/vcpx/monotone.py @ 704

Revision 704, 29.6 KB checked in by R.Ghetta <birrachiara@…>, 8 years ago (diff)

aligned wc handling to other backends, small cleanings

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        chlist = []
444        anc=sincerev
445        for r in childs:
446            ch = Changeset(r, None, None, "")
447            ch.lin_ancestor = anc
448            anc = r
449            chlist.append(ch)
450        return chlist
451
452    def _applyChangeset(self, changeset):
453        cmd = [self.repository.MONOTONE_CMD, "update", "--revision", changeset.revision]
454        mtl = ExternalCommand(cwd=self.basedir, command=cmd)
455        mtl.execute()
456        if mtl.exit_status:
457#        self.log_info("'mtn update' returned status %s" % mtl.exit_status)
458            raise ChangesetApplicationFailure("'mtn update' returned status %s" % mtl.exit_status)
459        self.oldrev = changeset.lin_ancestor
460        mtr = MonotoneRevToCset(repository=self.repository, working_dir=self.basedir)
461        mtr.updateCset( self.oldrev, changeset )
462       
463        return False   # no conflicts
464   
465    def _checkoutUpstreamRevision(self, revision):
466        """
467        Concretely do the checkout of the upstream revision.
468        """
469        effrev = self.convert_head_initial(self.repository.repository, self.repository.module, revision, self.basedir)
470        if not exists(join(self.basedir, 'MT')):
471            self.log_info("checking out a working copy")
472            cmd = [self.repository.MONOTONE_CMD, "co", "--db", self.repository.repository, "--revision", effrev, 
473                    "--branch", self.repository.module, self.repository.subdir]
474            mtl = ExternalCommand(cwd=self.repository.rootdir, command=cmd)
475            mtl.execute()
476            if mtl.exit_status:
477                raise TargetInitializationFailure(
478                    "'monotone co' returned status %s" % mtl.exit_status)
479        else:
480            self.log_info("%s already exists, assuming it's a monotone working dir" % self.basedir)
481       
482        # ok, now the workdir contains the checked out revision. We need to return a changeset
483        # describing it.
484        mtr = MonotoneRevToCset(repository=self.repository, working_dir=self.basedir)
485        csetlist = mtr.getCset( [None, effrev], True )
486        return csetlist[0]
487           
488    ## SyncronizableTargetWorkingDir
489
490    def _addPathnames(self, names):
491        """
492        Add some new filesystem objects, skipping directories (directory addition is implicit in monotone)
493        """
494        fnames=[]
495        for fn in names:
496            if isdir(join(self.basedir, fn)):
497                self.log_info("ignoring addition of directory '%s' (%s)" % (fn, join(self.basedir, fn)) );
498            else:
499                fnames.append(fn)
500        if len(fnames):
501            # ok, we still have something to add
502            cmd = [self.repository.MONOTONE_CMD, "add"]
503            add = ExternalCommand(cwd=self.basedir, command=cmd)
504            add.execute(fnames)
505            if add.exit_status:
506                raise ChangesetApplicationFailure("%s returned status %s" % (str(add),add.exit_status))
507       
508
509    def _addSubtree(self, subdir):
510        """
511        Add a whole subtree
512        """
513        cmd = [self.repository.MONOTONE_CMD, "add"]
514        add = ExternalCommand(cwd=self.basedir, command=cmd)
515        add.execute(subdir)
516        if add.exit_status:
517            raise ChangesetApplicationFailure("%s returned status %s" % (str(add),add.exit_status))
518
519    def _commit(self, date, author, patchname, changelog=None, entries=None):
520        """
521        Commit the changeset.
522        """
523
524        from sys import getdefaultencoding
525
526        encoding = ExternalCommand.FORCE_ENCODING or getdefaultencoding()
527
528        logmessage = []
529        if patchname:
530            logmessage.append(patchname.encode(encoding))
531        if changelog:
532            logmessage.append(changelog.encode(encoding))
533
534        rontf = ReopenableNamedTemporaryFile('mtn', 'tailor')
535        log = open(rontf.name, "w")
536        log.write('\n'.join(logmessage))
537        log.close()
538
539        cmd = [self.repository.MONOTONE_CMD, "commit", "--author", author,
540               "--date", date.isoformat(),
541               "--message-file", rontf.name]
542        commit = ExternalCommand(cwd=self.basedir, command=cmd)
543
544        if not entries:
545            entries = ['.']
546
547        output, error = commit.execute(entries, stdout=PIPE, stderr=PIPE)
548
549        # monotone complaints if there are no changes from the last commit.
550        # we ignore those errors ...
551        if commit.exit_status:
552            text = error.read()
553            if text.find("monotone: misuse: no changes to commit") == -1:
554                self.log_error(text)
555                raise ChangesetApplicationFailure(
556                    "%s returned status %s" % (str(commit),commit.exit_status))
557            else:
558                stderr.write("No changes to commit - changeset ignored\n")
559
560    def _removePathnames(self, names):
561        """
562        Remove some filesystem object.
563        """
564
565        # Monotone currently doesn't allow removing a directory,
566        # so we must remove every item separately and intercept monotone directory errore messages.
567        # We can't just filter the directories, because the wc doesn't contain them anymore ...
568        cmd = [self.repository.MONOTONE_CMD, "drop"]
569        drop = ExternalCommand(cwd=self.basedir, command=cmd)
570        for fn in names:
571            dum, error = drop.execute(fn, stderr=PIPE)
572            if drop.exit_status:
573                if not error.read().find("drop <directory>"):
574                    log_error(error.read())
575                    raise ChangesetApplicationFailure("%s returned status %s" % (str(drop),drop.exit_status))
576
577    def _renamePathname(self, oldname, newname):
578        """
579        Rename a filesystem object.
580        """
581        # this function is called *after* the file/dir has changed name,
582        # and monotone doesn't like it.
583        # we put names back to make it happy ...
584        if access(join(self.basedir, newname), F_OK):
585            if access(join(self.basedir, oldname), F_OK):
586                raise ChangesetApplicationFailure("Can't rename %s to %s. Both names already exist" % (oldname, newname) )
587            renames(join(self.basedir, newname), join(self.basedir, oldname))
588            self.log_info("preparing to rename %s->%s" % (oldname, newname))
589       
590        cmd = [self.repository.MONOTONE_CMD, "rename"]
591        rename = ExternalCommand(cwd=self.basedir, command=cmd)
592        rename.execute(oldname, newname)
593       
594        # redo the rename ...
595        renames(join(self.basedir, oldname), join(self.basedir, newname))
596        if rename.exit_status:
597            raise ChangesetApplicationFailure("%s returned status %s" % (str(rename),rename.exit_status))
598
599    def __createRepository(self, target_repository):
600        """
601        Create a new monotone DB, storing the commit keys, if available
602        """
603
604        cmd = [self.repository.MONOTONE_CMD, "db", "init", "--db",
605               target_repository.repository]
606        init = ExternalCommand(command=cmd)
607        init.execute()
608
609        if init.exit_status:
610            raise TargetInitializationFailure("Was not able to initialize "
611                                              "the monotone db at %r" %
612                                              target_repository)
613
614        if target_repository.keyfile:
615            # a key file is available, read into the database
616            keyfile = file(target_repository.keyfile)
617            cmd = [self.repository.MONOTONE_CMD, "read", "--db",
618                   target_repository.repository]
619            regkey = ExternalCommand(command=cmd)
620            regkey.execute(input=keyfile)
621        else:
622            # no keyfile specified, generate a new key - if a passphrase is defined, automatically
623            # provide it
624            # The keyid must be available
625            if not target_repository.keyid:
626                raise TargetInitializationFailure("Can't setup the monotone repository %r\n"
627                                                  "A keyfile or keyid must be provided." %
628                                                  target_repository)
629            cmd = [self.repository.MONOTONE_CMD, "genkey", "--db",
630                   target_repository.repository]
631            regkey = ExternalCommand(command=cmd)
632            if target_repository.passphrase:
633                passp="%s\n%s\n" % (target_repository.passphrase,target_repository.passphrase)
634            regkey.execute(target_repository.keyid, input=passp)
635
636        if regkey.exit_status:
637            raise TargetInitializationFailure("Was not able to setup "
638                                              "the monotone initial key at %r" %
639                                              target_repository)
640
641    def _prepareTargetRepository(self):
642        """
643        Check for target repository existence, eventually create it.
644        """
645
646        from os.path import exists
647
648        if not self.repository.repository:
649            return
650
651        if not exists(self.repository.repository):
652            self.__createRepository(self.repository)
653
654    def _prepareWorkingDirectory(self, source_repo):
655        """
656        Possibly checkout a working copy of the target VC, that will host the
657        upstream source tree, when overriden by subclasses.
658        """
659
660        from os.path import join, exists
661
662        if not self.repository.repository or exists(join(self.basedir, 'MT')):
663            return
664
665        cmd = [self.repository.MONOTONE_CMD, "setup",
666               "--db", self.repository.repository, "--branch", self.repository.module]
667       
668        if not self.repository.module:
669            raise TargetInitializationFailure("Monotone needs a module defined (to be used as commit branch)")
670
671        setup = ExternalCommand(command=cmd)
672        setup.execute(self.basedir)
673
674        if self.repository.passphrase:
675            monotonerc = open(join(self.basedir, 'MT', 'monotonerc'), 'w')
676            monotonerc.write(MONOTONERC % self.repository.passphrase)
677            monotonerc.close()
678           
679    def _initializeWorkingDir(self):
680        """
681        Setup the monotone working copy
682
683        The user must setup a monotone working directory himself or use the
684        tailor config file to provide parameters for creation. Then
685        we simply use 'monotone commit', without having to specify a database
686        file or branch. Monotone looks up the database and branch in it's MT
687        directory.
688        """
689
690        if not exists(join(self.basedir, 'MT')):
691            raise TargetInitializationFailure("Please setup '%s' as a monotone working directory" % self.basedir)
692
693#        self._addSubtree([self.repository.subdir])
694        SyncronizableTargetWorkingDir._initializeWorkingDir(self)
Note: See TracBrowser for help on using the repository browser.