source: tailor/vcpx/monotone.py @ 705

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

cleaning up

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