PKq38'p[[vcpx/tzinfo.pyo; Fc@sdkZeidZeiddZdeifdYZeZZdZee_ deifdYZ hd Z ee _ dS( NishoursisUTCcBsbtZdZdZdZdZdZdZedZ edZ dZ d Z RS( sUTC Identical to the reference UTC implementation given in Python docs except that it unpickles using the single module global instance defined beneath this class declaration. Also contains extra attributes and methods to match other pytz tzinfo instances. sUTCcCstSdS(N(sZERO(sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys utcoffset4scCsdSdS(NsUTC((sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pystzname7scCstSdS(N(sZERO(sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pysdst:scCstffSdS(N(s_UTC(sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys __reduce__=scCs1|itj o tdn|id|SdS(s Convert naive time to local times*Not naive datetime (tzinfo is already set)stzinfoN(sdtstzinfosNones ValueErrorsreplacesself(sselfsdtsis_dst((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pyslocalize@s cCs1|itjo tdn|id|SdS(s6Correct the timezone information on the given datetimesNaive time - no tzinfo setstzinfoN(sdtstzinfosNones ValueErrorsreplacesself(sselfsdtsis_dst((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys normalizeFs cCsdSdS(Ns((sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys__repr__LscCsdSdS(NsUTC((sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys__str__Os( s__name__s __module__s__doc__szones utcoffsetstznamesdsts __reduce__sFalseslocalizes normalizes__repr__s__str__(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pysUTC(s        cCstSdS(sFactory function for utc unpickling. Makes sure that unpickling a utc instance always returns the same module global. N(sutc(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys_UTCVss _FixedOffsetcBs\tZeZdZdZdZdZdZdZ e dZ e dZ RS( NcCsHt|djotd|n||_tid||_dS(Nisabsolute offset is too largesminutes(sabssminutess ValueErrorsselfs_minutessdatetimes timedeltas_offset(sselfsminutes((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys__init__hs cCs |iSdS(N(sselfs_offset(sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys utcoffsetnscCst|iffSdS(N(s FixedOffsetsselfs_minutes(sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys __reduce__qscCstSdS(N(sZERO(sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pysdsttscCstSdS(N(sNone(sselfsdt((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pystznamewscCsd|iSdS(Nspytz.FixedOffset(%d)(sselfs_minutes(sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys__repr__zscCs1|itj o tdn|id|SdS(s Convert naive time to local times*Not naive datetime (tzinfo is already set)stzinfoN(sdtstzinfosNones ValueErrorsreplacesself(sselfsdtsis_dst((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pyslocalize}s cCs1|itjo tdn|id|SdS(s6Correct the timezone information on the given datetimesNaive time - no tzinfo setstzinfoN(sdtstzinfosNones ValueErrorsreplacesself(sselfsdtsis_dst((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys normalizes ( s__name__s __module__sNoneszones__init__s utcoffsets __reduce__sdststznames__repr__sFalseslocalizes normalize(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys _FixedOffsetds       cCsU|djotSn|i|}|tjo|i|t|}n|SdS(s7return a fixed-offset timezone based off a number of minutes. >>> one = FixedOffset(-330) >>> one pytz.FixedOffset(-330) >>> one.utcoffset(datetime.datetime.now()) datetime.timedelta(-1, 66600) >>> two = FixedOffset(1380) >>> two pytz.FixedOffset(1380) >>> two.utcoffset(datetime.datetime.now()) datetime.timedelta(0, 82800) The datetime.timedelta must be between the range of -1 and 1 day, non-inclusive. >>> FixedOffset(1440) Traceback (most recent call last): ... ValueError: ('absolute offset is too large', 1440) >>> FixedOffset(-1440) Traceback (most recent call last): ... ValueError: ('absolute offset is too large', -1440) An offset of 0 is special-cased to return UTC. >>> FixedOffset(0) is UTC True There should always be only one instance of a FixedOffset per timedelta. This should be true for multiple creation calls. >>> FixedOffset(-330) is one True >>> FixedOffset(1380) is two True It should also be true for pickling. >>> import pickle >>> pickle.loads(pickle.dumps(one)) is one True >>> pickle.loads(pickle.dumps(two)) is two True iN(soffsetsUTCs_tzinfossgetsinfosNones setdefaults _FixedOffset(soffsets_tzinfossinfo((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys FixedOffsets1  ( sdatetimes timedeltasZEROsHOURstzinfosUTCsutcs_UTCsTrues__safe_for_unpickling__s _FixedOffsets FixedOffset(sUTCs_UTCs _FixedOffsetsHOURsutcsdatetimesZEROs FixedOffset((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tzinfo.pys?!s +   % @PK&f6vcpx/statefile.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx -- Persistent information details # :Creato: ven 19 ago 2005 22:53:18 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ Tailor needs a generic way to remember which was the last migrated revision between the two repositories. For some backends it could derive such information directly from the working dir/repository, others have no such capability. Moreover, since fetching and digesting the history to produce a sequence of ChangeSets it's time and bandwidth consuming, the state file helps keeping a cache of pending changes. """ __docformat__ = 'reStructuredText' from cPickle import load, dump from signal import signal, SIGINT, SIG_IGN class StateFile(object): """ State file that stores current revision and pending changesets. It behaves as an iterator, and source backends loop over not yet applied changesets, calling .applied() after each one: that writes the applied changeset in a *journal* file, much more atomic than rewriting the whole archive each time. When the source backend finishes it's job, either because there are no more pending changeset or stopped by an error, it calls .finalize(), that in presence of a journal file adjust the archive filtering out already applied changesets. Should an hard error prevent .finalize() call, it will happen automatically next time the state file is loaded. """ def __init__(self, fname, config): """ Initialize a new instance, logging to `tailor.statefile`. """ from logging import getLogger self.filename = fname self.archive = None self.last_applied = None self.current = None self.log = getLogger('tailor.statefile') def _load(self): """ Open the pickle file and load the last applied changeset. The second pickled object is ignored for backward compatibility. """ # Take care of the journal file, if present. self.finalize() self.current = None try: self.archive = open(self.filename) self.last_applied = load(self.archive) # compatibility dummity: there was the queuelen here load(self.archive) except IOError: self.archive = None self.last_applied = None def _write(self, changesets): """ Write the state file, that is dump last applied changeset, a dummy None, then one changeset at a time. """ count = 0 previous = signal(SIGINT, SIG_IGN) try: sf = open(self.filename, 'w') dump(self.last_applied, sf) dump(None, sf) for cs in changesets: dump(cs, sf) count += 1 sf.close() finally: signal(SIGINT, previous) self.log.info('Cached information about %d pending changesets', count) def __str__(self): return self.filename def __iter__(self): return self def next(self): if not self.archive: raise StopIteration try: self.current = load(self.archive) except EOFError: self.archive.close() self.archive = None raise StopIteration return self.current def pending(self): """ Verify if there's at least one changeset still pending. """ if self.archive is None: self._load() if self.archive is None: return False pos = self.archive.tell() try: next = load(self.archive) except EOFError: next = None self.archive.seek(pos) return next is not None def applied(self, current=None): """ Write the applied changeset to the journal file. """ previous = signal(SIGINT, SIG_IGN) try: self.last_applied = current or self.current journal = open(self.filename + '.journal', 'w') dump(self.last_applied, journal) journal.close() finally: signal(SIGINT, previous) def finalize(self): """ If there is a journal file, adjust the archive accordingly, dropping already applied changesets. """ from os.path import exists from os import unlink, rename previous = signal(SIGINT, SIG_IGN) try: if self.archive is not None: self.archive.close() self.archive = None if exists(self.filename + '.journal'): self.log.debug('Adjusting the state accordingly to journal') # Load last applied changeset from the journal journal = open(self.filename + '.journal') last_applied = load(journal) journal.close() # If there is an actual archive (ie, this is not # bootstrap time) load the changesets from there, # skipping the changesets until the last_applied one, # then transfer the remaining to the new archive. if exists(self.filename): old = open(self.filename) load(old) # last applied load(old) # dummy queuelen try: cs = load(old) # Skip already applied changesets while cs <> last_applied: cs = load(old) except EOFError: cs = None sf = open(self.filename + '.new', 'w') dump(last_applied, sf) dump(None, sf) if cs is not None: count = 0 while True: try: cs = load(old) except EOFError: break dump(cs, sf) count += 1 self.log.info('%d pending changesets in state file', count) sf.close() old.close() oldname = self.filename + '.old' if exists(oldname): unlink(oldname) rename(self.filename, oldname) rename(sf.name, self.filename) else: sf = open(self.filename, 'w') dump(last_applied, sf) dump(None, sf) sf.close() unlink(journal.name) finally: signal(SIGINT, previous) def lastAppliedChangeset(self): """ Return the last applied changeset, if any, None otherwise. """ if self.archive is None: self._load() return self.last_applied def setPendingChangesets(self, changesets): """ Write pending changesets to the state file. """ if self.archive is not None: self.archive.close() self.archive = None self._write(changesets) self._load() PKq38DE:#:#vcpx/shwrap.pyc; Fc@s|dZydklZlZlZWn,ej o dklZlZlZnXdfdYZdfdYZdS(sreStructuredText(sPopensPIPEsSTDOUTsReopenableNamedTemporaryFilecBs5tZdZeeeedZdZdZRS(s This uses tempfile.mkstemp() to generate a secure temp file. It then closes the file, leaving a zero-length file as a placeholder. You can get the filename with ReopenableNamedTemporaryFile.name. When the ReopenableNamedTemporaryFile instance is garbage collected or its shutdown() method is called, it deletes the file. Copied from Zooko's pyutil.fileutil, http://zooko.com/repos/pyutil cCsFdkl}dkl}|||||\}|_ ||dS(N(smkstemp(sclose( stempfilesmkstempsossclosessuffixsprefixsdirstextsfdsselfsname(sselfssuffixsprefixsdirstextsfdsclosesmkstemp((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pys__init__s  cCs|idS(N(sselfsshutdown(sself((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pys__del__"scCsdkl}||idS(N(sremove(sossremovesselfsname(sselfsremove((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pysshutdown%s (s__name__s __module__s__doc__sNones__init__s__del__sshutdown(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pysReopenableNamedTemporaryFiles  sExternalCommandcBsVtZdZeZeZdZeeedZdZ dZ dZ dZ RS(s2Wrap a single command to be executed by the shell.i@cCsadkl}||_||_t|_t|_t|_ |o t|_ n|d|_ dS(s Initialize a ExternalCommand instance, specifying the command to be executed and eventually the working directory. The instance will use the logger ``tailor.shell``. (s getLoggers tailor.shellN( sloggings getLoggerscommandsselfscwdsNones exit_statuss _last_commandsFalsescapture_stderrsnologslog(sselfscommandscwdsnologs getLogger((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pys__init__7s       cCsOdt|}|io|id|}n|io|d}n|SdS(sX Return a string representation of the command prefixed by working dir. s$s s 2>&1N(sreprsselfsrscwdscapture_stderr(sselfsr((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pys__str__Ts  cCsUg}t}x5|ip|iD] }g}|idd|jp d|j}|o|idnx|D]}|djo|i|qq|djo2|idt |dg}|idqq|o|i |g}n|i|qqW|o|i |n|o|i ||idq q Wdi |SdS( sY Compute a reasonable shell-like representation of the external command. s s s"s\is\"sN( sresultsFalses needquotesselfs _last_commandscommandsargsbs_bufsappendscslensextendsjoin(sselfscs needquotesresultsargsbs_buf((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pys__repr__`s8      cOsDdkl} |ido t|_n t|_t|djot |dt gjot |d}n t |}|i }|tjpt|djo|i||Sntdi|i} t} t}x|o g}| }|i}|i}xC|o ||jo.|d}|t|d7}||qW|i||\} } | tj o1| tjo | } n| i| in| tj o1|tjo | }n|i| in|ioPqqW| tj o| i dn|tj o|i dn| |fSdS(s4Execute the command, avoiding too long command line.(sStringIOsstderriiis N(!s cStringIOsStringIOskwargssgetsTruesselfscapture_stderrsFalseslensargsstypeslistsallargssMAX_CMDLINE_LENGTHsmaxlensNones_executesjoinscommandsstartlensalloutsallerrsthisrunsclenspopsappendsthisargsthisoutsthiserrswritesreads exit_statussseek(sselfsargsskwargsspopsclensappendsallargssthisrunsthisargsstartlensthiserrsalloutsStringIOsthisoutsmaxlensallerr((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/shwrap.pysexecutesR   0                cOsdkl}dkl}dkl}l} dkl}dk l }dk l } t |_gi}|iD]}|||qn~|_t|djot|dtgjo|ii|dn|ii||io|ii|n|iod Sn|id |ip| } ||  ot| d | n|io|ii d || n|i!d  oh}|d <|i#|x9dddgD](}|i!|o||||$>vcpx/tailor.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx -- Frontend capabilities # :Creato: dom 04 lug 2004 00:40:54 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ Implement the frontend functionalities. """ __docformat__ = 'reStructuredText' __version__ = '0.9.29' from logging import getLogger from optparse import OptionParser, OptionGroup, Option from vcpx import TailorException from vcpx.config import Config, ConfigurationError from vcpx.project import Project from vcpx.source import GetUpstreamChangesetsFailure class Tailorizer(Project): """ A Tailorizer has two main capabilities: its able to bootstrap a new Project, or brought it in sync with its current upstream revision. """ def _applyable(self, changeset): """ Print the changeset being applied. """ if self.verbose: self.log.info('Changeset "%s"', changeset.revision) if changeset.log: self.log.info("Log message: %s", changeset.log) self.log.debug("Going to apply changeset:\n%s", str(changeset)) return True def _applied(self, changeset): """ Separate changesets with an empty line. """ if self.verbose: self.log.info('-*'*30) def bootstrap(self): """ Bootstrap a new tailorized module. First of all prepare the target system working directory such that it can host the upstream source tree. This is backend specific. Then extract a copy of the upstream repository and import its content into the target repository. """ self.log.info('Bootstrapping "%s" in "%s"', self.name, self.rootdir) dwd = self.workingDir() try: dwd.prepareWorkingDirectory(self.source) except: self.log.critical('Cannot prepare working directory!') raise revision = self.config.get(self.name, 'start-revision', 'INITIAL') try: actual = dwd.checkoutUpstreamRevision(revision) except: self.log.critical("Checkout of %s failed!", self.name) raise try: dwd.importFirstRevision(self.source, actual, 'INITIAL'==revision) except: self.log.critical('Could not import checked out tree in "%s"!', self.rootdir) raise self.log.info("Bootstrap completed") def update(self): """ Update an existing tailorized project. """ self.log.info('Updating "%s" in "%s"', self.name, self.rootdir) dwd = self.workingDir() try: pendings = dwd.getPendingChangesets() except KeyboardInterrupt: self.log.warning('Leaving "%s" unchanged, stopped by user', self.name) raise except: self.log.critical('Unable to get changes for "%s"', self.name) raise if pendings.pending(): self.log.info("Applying pending upstream changesets") try: last, conflicts = dwd.applyPendingChangesets( applyable=self._applyable, applied=self._applied) except KeyboardInterrupt: self.log.warning('Leaving "%s" incomplete, stopped by user', self.name) raise except: self.log.critical('Upstream change application failed') raise if last: self.log.info('Update completed, now at revision "%s"', last.revision) else: self.log.info("Update completed with no upstream changes") def __call__(self): from shwrap import ExternalCommand from target import SynchronizableTargetWorkingDir from changes import Changeset def pconfig(option, raw=False): return self.config.get(self.name, option, raw=raw) ExternalCommand.DEBUG = pconfig('debug') pname_format = pconfig('patch-name-format', raw=True) if pname_format is not None: SynchronizableTargetWorkingDir.PATCH_NAME_FORMAT = pname_format.strip() SynchronizableTargetWorkingDir.REMOVE_FIRST_LOG_LINE = pconfig('remove-first-log-line') Changeset.REFILL_MESSAGE = pconfig('refill-changelogs') try: if not self.exists(): self.bootstrap() if pconfig('start-revision') == 'HEAD': return self.update() except (UnicodeDecodeError, UnicodeEncodeError), exc: raise ConfigurationError('%s: it seems that the encoding ' 'used by either the source ("%s") or the ' 'target ("%s") repository ' 'cannot properly represent at least one ' 'of the characters in the upstream ' 'changelog. You need to use a wider ' 'character set, using "encoding" option, ' 'or even "encoding-errors-policy".' % (exc, self.source.encoding, self.target.encoding)) class RecogOption(Option): """ Make it possible to recognize an option explicitly given on the command line from those simply coming out for their default value. """ def process (self, opt, value, values, parser): setattr(values, '__seen_' + self.dest, True) return Option.process(self, opt, value, values, parser) GENERAL_OPTIONS = [ RecogOption("-D", "--debug", dest="debug", action="store_true", default=False, help="Print each executed command. This also keeps " "temporary files with the upstream logs, that are " "otherwise removed after use."), RecogOption("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Be verbose, echoing the changelog of each applied " "changeset to stdout."), RecogOption("-c", "--configfile", metavar="CONFNAME", help="Centralized storage of projects info. With this " "option and no other arguments tailor will update " "every project found in the config file."), RecogOption("--encoding", metavar="CHARSET", default=None, help="Force the output encoding to given CHARSET, rather " "then using the user's default settings specified " "in the environment."), ] UPDATE_OPTIONS = [ RecogOption("-F", "--patch-name-format", metavar="FORMAT", help="Specify the prototype that will be used " "to compute the patch name. The prototype may contain " "%(keyword)s such as 'author', 'date', " "'revision', 'firstlogline', 'remaininglog'. It " "defaults to 'Tailorized \"%(revision)s\"'; " "setting it to the empty string means that tailor will " "simply use the original changelog."), RecogOption("-1", "--remove-first-log-line", action="store_true", default=False, help="Remove the first line of the upstream changelog. This " "is intended to pair with --patch-name-format, " "when using its 'firstlogline' variable to build the " "name of the patch."), RecogOption("-N", "--refill-changelogs", action="store_true", default=False, help="Refill every changelog, useful when upstream logs " "are not uniform."), ] BOOTSTRAP_OPTIONS = [ RecogOption("-s", "--source-kind", dest="source_kind", metavar="VC-KIND", help="Select the backend for the upstream source " "version control VC-KIND. Default is 'cvs'.", default="cvs"), RecogOption("-t", "--target-kind", dest="target_kind", metavar="VC-KIND", help="Select VC-KIND as backend for the shadow repository, " "with 'darcs' as default.", default="darcs"), RecogOption("-R", "--repository", "--source-repository", dest="source_repository", metavar="REPOS", help="Specify the upstream repository, from where bootstrap " "will checkout the module. REPOS syntax depends on " "the source version control kind."), RecogOption("-m", "--module", "--source-module", dest="source_module", metavar="MODULE", help="Specify the module to checkout at bootstrap time. " "This has different meanings under the various upstream " "systems: with CVS it indicates the module, while under " "SVN it's the prefix of the tree you want and must begin " "with a slash. Since it's used in the description of the " "target repository, you may want to give it a value with " "darcs too, even though it is otherwise ignored."), RecogOption("-r", "--revision", "--start-revision", dest="start_revision", metavar="REV", help="Specify the revision bootstrap should checkout. REV " "must be a valid 'name' for a revision in the upstream " "version control kind. For CVS it may be either a branch " "name, a timestamp or both separated by a space, and " "timestamp may be 'INITIAL' to denote the beginning of " "time for the given branch. Under Darcs, INITIAL is a " "shortcut for the name of the first patch in the upstream " "repository, otherwise it is interpreted as the name of " "a tag. Under Subversion, 'INITIAL' is the first patch " "that touches given repos/module, otherwise it must be " "an integer revision number. " "'HEAD' means the latest version in all backends.", default="INITIAL"), RecogOption("-T", "--target-repository", dest="target_repository", metavar="REPOS", default=None, help="Specify the target repository, the one that will " "receive the patches coming from the source one."), RecogOption("-M", "--target-module", dest="target_module", metavar="MODULE", help="Specify the module on the target repository that will " "actually contain the upstream source tree."), RecogOption("--subdir", metavar="DIR", help="Force the subdirectory where the checkout will happen, " "by default it's the tail part of the module name."), ] VC_SPECIFIC_OPTIONS = [ RecogOption("--use-propset", action="store_true", default=False, dest="use_propset", help="Use 'svn propset' to set the real date and author of " "each commit, instead of appending these information to " "the changelog. This requires some tweaks on the SVN " "repository to enable revision propchanges."), RecogOption("--ignore-arch-ids", action="store_true", default=False, dest="ignore_ids", help="Ignore .arch-ids directories when using a tla source."), ] class ExistingProjectError(TailorException): "Project seems already tailored" class ProjectNotTailored(TailorException): "Not a tailored project" def main(): """ Script entry point. Parse the command line options and arguments, and for each specified working copy directory (the current working directory by default) execute the tailorization steps. """ import sys from os import getcwd parser = OptionParser(usage='%prog [options] [project ...]', version=__version__, option_list=GENERAL_OPTIONS) bsoptions = OptionGroup(parser, "Bootstrap options") bsoptions.add_options(BOOTSTRAP_OPTIONS) upoptions = OptionGroup(parser, "Update options") upoptions.add_options(UPDATE_OPTIONS) vcoptions = OptionGroup(parser, "VC specific options") vcoptions.add_options(VC_SPECIFIC_OPTIONS) parser.add_option_group(bsoptions) parser.add_option_group(upoptions) parser.add_option_group(vcoptions) options, args = parser.parse_args() defaults = {} for k,v in options.__dict__.items(): if k.startswith('__'): continue if k <> 'configfile' and hasattr(options, '__seen_' + k): defaults[k.replace('_', '-')] = str(v) if options.configfile or (len(sys.argv)==2 and len(args)==1): # Either we have a --configfile, or there are no options # and a single argument (to support shebang style scripts) if not options.configfile: options.configfile = sys.argv[1] args = None config = Config(open(options.configfile), defaults) if not args: args = config.projects() for projname in args: tailorizer = Tailorizer(projname, config) try: tailorizer() except GetUpstreamChangesetsFailure: # Do not stop on this kind of error, but keep going pass else: for omit in ['source-kind', 'target-kind', 'source-module', 'target-module', 'source-repository', 'target-repository', 'start-revision', 'subdir']: if omit in defaults: del defaults[omit] config = Config(None, defaults) config.add_section('project') source = options.source_kind + ':source' config.set('project', 'source', source) target = options.target_kind + ':target' config.set('project', 'target', target) config.set('project', 'root-directory', getcwd()) config.set('project', 'subdir', options.subdir or '.') config.set('project', 'state-file', 'tailor.state') config.set('project', 'start-revision', options.start_revision) config.add_section(source) if options.source_repository: config.set(source, 'repository', options.source_repository) else: logger = getLogger('tailor') logger.warning("By any chance you forgot either the --source-repository or the --configfile option...") if options.source_module: config.set(source, 'module', options.source_module) config.add_section(target) if options.target_repository: config.set(target, 'repository', options.target_repository) if options.target_module: config.set(target, 'module', options.target_module) if options.verbose: sys.stderr.write("You should put the following configuration " "in some file, adjust it as needed\n" "and use --configfile option with that " "file as argument:\n") config.write(sys.stdout) if options.debug: tailorizer = Tailorizer('project', config) tailorizer() elif not options.verbose: sys.stderr.write("Operation not performed, try --verbose\n") PKq38Ҽc Y#Y#vcpx/project.pyo; Fc@scdZdZdklZdklZdklZdefdYZde fdYZ d S( s This module implements a higher level of operations, with a Project class that knows how to drive the two main activities, bootstrap and update, layering on top of DualWorkingDir. sreStructuredText(sTailorException(sConfigurationError(s StateFilesUnknownProjectErrorcBstZdZRS(sProject does not exist(s__name__s __module__s__doc__(((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pysUnknownProjectErrors sProjectcBsMtZdZdZdZdZdZdZdZdZ RS(s This class collects the information related to a single project, such as its source and target repositories and state file. All the setup comes from a section in the configuration file (.ini-like format) with the same name as the project. Mandatory options are: root-directory This is where all the fun will happen: this directory will contain the source and the target working copy, and usually the state and the log file. It support the conventional "~user" to indicate user's home directory. subdir This is the subdirectory, relative to the root-directory, where tailor will extract the source working copy. It may be '.' for some backend kinds. state-file Name of the state file needed to store tailor last activity. source The source repository: a repository name is something like "darcs:somename", that will be loaded from the homonymous section in the configuration. target The counterpart of `source`, the repository that will receive the changes coming from there. Non mandatory options: before-commit This is a function name, or a sequence of function names enclosed by brackets, that will be executed on each changeset just before it get replayed on the target system: this may be used to perform any kind of alteration on the content of the changeset, or to skip some of them. after-commit This is a function name, or a sequence of function names enclosed by brackets, that will be executed on each changeset just after the commit on the target system: this may be used for example to create a tag. start-revision This identifies from when tailor should start the migration. It can be either ``INITIAL``, to indicate the start of the history, or ``HEAD`` to indicate the current latest changeset, or a backend specific way of indicate a particular revision/tag in the history. cCsdkl}t|_|i| otd|n||_||_t|_ y|i Wn4|j o(}t d|it |fnXdS(sL Initialize a new instance representing the project `name`. (sErrors'%s' is not a known projects'Invalid configuration in section %s: %sN(s ConfigParsersErrorsNonesselfs loghandlersconfigs has_sectionsnamesUnknownProjectErrorsdwds_loadsesConfigurationErrorsstr(sselfsnamesconfigsesError((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys__init__Os     c Csbd|i|ifdigi}dddfD]#}|d|t||fq0~SdS(NsProject %s at %s: s ssourcestargets state_files%s = %s(sselfsnamesrootdirsjoinsappends_[1]svsgetattr(sselfs_[1]sv((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys__str__dscCsdkl}dkl}l}l} l} dkl}l }l }l }l } l }|ii|idt|_|ii|idd}| | ||_||i o||in|ii|id|_|i o d|_n||i|id|_|d |i|_|ii|id o|ii|n|d }||ii|id d dt|ii|idddt} ||i|_|ii| |ii||i|i|id|_ |id|_!|ii|id|id}||i|i!i#|}t$||i|_%|ii&|id}y8gi(}|D]}||ii+|q~|_,Wn4t-j o(}t/d|it0|fnX|ii&|id} y8gi(}| D]}||ii+|q~|_2Wn4t-j o(}t/d|it0|fnX|ii|idt oK|}t|_4x6|i5D]'}t7|| o|i|qqWndS(sC Load relevant information from the configuration. (smakedirs(sjoinsexistss expandusersabspath(s getLoggersCRITICALsDEBUGs FileHandlers StreamHandlers Formattersverbosesroot-directorys.ssubdirs.logstailor.project.%ssdebugstailors log-formats'%(asctime)s %(levelname)8s: %(message)ssraws log-datefmts%Y-%m-%d %H:%M:%Sssourcestargets state-files.states before-commits:Project "%s" before-commit references unknown function: %ss after-commits9Project "%s" after-commit references unknown function: %sN(8sossmakedirssos.pathsjoinsexistss expandusersabspathsloggings getLoggersCRITICALsDEBUGs FileHandlers StreamHandlers FormattersselfsconfigsgetsnamesFalsesverbosesrootdirssubdirslogfileslogssetLevels tailorlogsTrues formatters loghandlers setFormatters addHandlers_Project__loadRepositoryssourcestargetssfpaths stateFilePaths StateFiles state_filesgetTuplesbeforesappends_[1]sfs namespaces before_commitsKeyErrorsesConfigurationErrorsstrsafters after_commitsrootlogsdisabledshandlersshs isinstance(sselfsexistssrootlogs Formatterssfpathsbefores FileHandlersmakedirssCRITICALs StreamHandlers formatters expandusersabspathsaftersrootdirsDEBUGsjoinsfshs tailorlogs_[1]s getLoggerse((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys_loadis\ +    $"8$8$   cCs;|itj o'dkl}|di|indS(N(s getLoggerstailor(sselfs loghandlersNonesloggings getLoggers removeHandler(sselfs getLogger((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys__del__s cCsZdkl}|ii|i|}|ido||i7}n||||SdS(s Given a repository named 'somekind:somename', return a Repository (or a subclass of it, if 'SomekindRepository' exists) instance that wraps it. (s Repositorys:N( s repositorys Repositorysselfsconfigsgetsnameswhichsrepnamesendswith(sselfswhichs Repositorysrepname((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys__loadRepositorys  cCs|iitj SdS(sE Return True if the project exists, False otherwise. N(sselfs state_fileslastAppliedChangesetsNone(sself((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pysexistsscCsjdkl}|itjoB||i|i|_|ii|i|ii |i n|iSdS(sB Return a DualWorkingDir instance, ready to work. (sDualWorkingDirN( sdualwdsDualWorkingDirsselfsdwdsNonessourcestargets setStateFiles state_files setLogfileslogfile(sselfsDualWorkingDir((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys workingDirs ( s__name__s __module__s__doc__s__init__s__str__s_loads__del__s_Project__loadRepositorysexistss workingDir(((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pysProjects 4   A   N( s__doc__s __docformat__svcpxsTailorExceptions vcpx.configsConfigurationErrorsvcpx.statefiles StateFilesUnknownProjectErrorsobjectsProject(s StateFilesConfigurationErrorsProjectsUnknownProjectErrors __docformat__sTailorException((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/project.pys? s    PKq384%vcpx/workdir.pyc; Fc@s&dZdZdefdYZdS(s sreStructuredTexts WorkingDircBs tZdZdZdZRS(si This is the common ancestor for working directories, associated to some kind of repository. cCs<dkl}||_|d|ii|if|_dS(N(s getLoggers tailor.%s.%s(sloggings getLoggers repositorysselfs __class__s__name__snameslog(sselfs repositorys getLogger((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/workdir.pys__init__s  cCs ||_dS(sW Set the state file used to store the revision and pending changesets. N(s state_filesself(sselfs state_file((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/workdir.pys setStateFiles(s__name__s __module__s__doc__s__init__s setStateFile(((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/workdir.pys WorkingDir s  N(s__doc__s __docformat__sobjects WorkingDir(s WorkingDirs __docformat__((s0build/bdist.darwin-8.0.1-x86/egg/vcpx/workdir.pys? sPK&f6f$[$[vcpx/target.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx -- Syncable targets # :Creato: ven 04 giu 2004 00:27:07 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ Syncronizable targets are the simplest abstract wrappers around a working directory under two different version control systems. """ __docformat__ = 'reStructuredText' import socket from signal import signal, SIGINT, SIG_IGN from vcpx import TailorBug, TailorException from vcpx.workdir import WorkingDir HOST = socket.getfqdn() AUTHOR = "tailor" BOOTSTRAP_PATCHNAME = 'Tailorization' BOOTSTRAP_CHANGELOG = """\ Import of the upstream sources from %(source_repository)s Revision: %(revision)s """ class TargetInitializationFailure(TailorException): "Failure initializing the target VCS" class ChangesetReplayFailure(TailorException): "Failure replaying the changeset on the target system" class SynchronizableTargetWorkingDir(WorkingDir): """ This is an abstract working dir usable as a *shadow* of another kind of VC, sharing the same working directory. Most interesting entry points are: replayChangeset to replay an already applied changeset, to mimic the actions performed by the upstream VC system on the tree such as renames, deletions and adds. This is an useful argument to feed as ``replay`` to ``applyUpstreamChangesets`` importFirstRevision to initialize a pristine working directory tree under this VC system, possibly extracted under a different kind of VC Subclasses MUST override at least the _underscoredMethods. """ PATCH_NAME_FORMAT = '[%(project)s @ %(revision)s]' """ The format string used to compute the patch name, used by underlying VCS. """ REMOVE_FIRST_LOG_LINE = False """ When true, remove the first line from the upstream changelog. """ def __getPatchNameAndLog(self, changeset): """ Return a tuple (patchname, changelog) interpolating changeset's information with the template above. """ if changeset.log == '': firstlogline = 'Empty log message' remaininglog = '' else: loglines = changeset.log.split('\n') if len(loglines)>1: firstlogline = loglines[0] remaininglog = '\n'.join(loglines[1:]) else: firstlogline = changeset.log remaininglog = '' patchname = self.PATCH_NAME_FORMAT % { 'project': self.repository.projectref().name, 'revision': changeset.revision, 'author': changeset.author, 'date': changeset.date, 'firstlogline': firstlogline, 'remaininglog': remaininglog} if self.REMOVE_FIRST_LOG_LINE: changelog = remaininglog else: changelog = changeset.log return patchname, changelog def replayChangeset(self, changeset): """ Do whatever is needed to replay the changes under the target VC, to register the already applied (under the other VC) changeset. """ try: changeset = self._adaptChangeset(changeset) except: self.log.exception("Failure adapting: %s", str(changeset)) raise if changeset is None: return try: self._replayChangeset(changeset) except: self.log.exception("Failure replaying: %s", str(changeset)) raise patchname, log = self.__getPatchNameAndLog(changeset) entries = self._getCommitEntries(changeset) previous = signal(SIGINT, SIG_IGN) try: self._commit(changeset.date, changeset.author, patchname, log, entries, tags = changeset.tags) if changeset.tags: for tag in changeset.tags: self._tag(tag, changeset.date, changeset.author) finally: signal(SIGINT, previous) try: self._dismissChangeset(changeset) except: self.log.exception("Failure dismissing: %s", str(changeset)) raise def __getPrefixToSource(self): """ Compute and return the "offset" between source and target basedirs, or None when not using shared directories, or there's no offset. """ project = self.repository.projectref() ssubdir = project.source.subdir tsubdir = project.target.subdir if self.shared_basedirs and ssubdir <> tsubdir: if tsubdir == '.': prefix = ssubdir else: if not tsubdir.endswith('/'): tsubdir += '/' prefix = ssubdir[len(tsubdir):] return prefix else: return None def _normalizeEntryPaths(self, entry): """ Normalize the name and old_name of an entry. The ``name`` and ``old_name`` of an entry are pathnames coming from the upstream system, and is usually (although there is no guarantee it actually is) a UNIX style path with forward slashes "/" as separators. This implementation uses normpath to adapt the path to the actual OS convention, but subclasses may eventually override this to use their own canonicalization of ``name`` and ``old_name``. """ from os.path import normpath entry.name = normpath(entry.name) if entry.old_name: entry.old_name = normpath(entry.old_name) def __adaptEntriesPath(self, changeset): """ If the source basedir is a subdirectory of the target, adjust all the pathnames adding the prefix computed by difference. """ from copy import deepcopy from os.path import join if not changeset.entries: return changeset prefix = self.__getPrefixToSource() adapted = deepcopy(changeset) for e in adapted.entries: if prefix: e.name = join(prefix, e.name) if e.old_name: e.old_name = join(prefix, e.old_name) self._normalizeEntryPaths(e) return adapted def _adaptEntries(self, changeset): """ Do whatever is needed to adapt entries to the target system. This implementation adds a prefix to each path if needed, when the target basedir *contains* the source basedir. Also, each path is normalized thru ``normpath()`` or whatever equivalent operation provided by the specific target. It operates on and returns a copy of the given changeset. Subclasses shall eventually extend this to exclude unwanted entries, eventually returning None when all entries were excluded, to avoid the commit on target of an empty changeset. """ adapted = self.__adaptEntriesPath(changeset) return adapted def _adaptChangeset(self, changeset): """ Do whatever needed before replay and return the adapted changeset. This implementation calls ``self._adaptEntries()``, then executes the adapters defined by before-commit on the project: each adapter is run in turn, and may return False to indicate that the changeset shouldn't be replayed at all. They are otherwise free to alter the changeset in any meaningful way. """ from copy import copy adapted = self._adaptEntries(changeset) if adapted: project = self.repository.projectref() if project.before_commit: adapted = copy(adapted) for adapter in project.before_commit: if not adapter(self, adapted): return None return adapted def _dismissChangeset(self, changeset): """ Do whatever needed after commit. This execute the adapters defined by after-commit on the project, for example tagging in some way the target repository upon some particular kind of changeset. """ project = self.repository.projectref() if project.after_commit: for farewell in project.after_commit: farewell(self, changeset) def _getCommitEntries(self, changeset): """ Extract the names of the entries for the commit phase. """ # Since the commit may use cli tools to do its job, and the # machinery may split the list into smaller chunks to avoid # too long command lines, anticipates added stuff. I think # this is needed only when coming from CVS (or HG or in # general from systems that don't handle directories): its # _applyChangeset *appends* to the entries a fake ADD for # each new subdir. entries = [] added = 0 for e in changeset.entries: if e.action_kind == e.ADDED: entries.insert(added, e.name) added += 1 else: # Add also the name of the old file: for some systems # it may not be strictly needed, but it is for most. if e.action_kind == e.RENAMED: entries.append(e.old_name) entries.append(e.name) return entries def _replayChangeset(self, changeset): """ Replicate the actions performed by the changeset on the tree of files. """ from os.path import join, isdir from changes import ChangesetEntry added = [] actions = { ChangesetEntry.ADDED: self._addEntries, ChangesetEntry.DELETED: self._removeEntries, ChangesetEntry.RENAMED: self._renameEntries, ChangesetEntry.UPDATED: self._editEntries } # Group the changes by kind and perform the corresponding action last = None group = [] for e in changeset.entries: if last is None or last.action_kind == e.action_kind: last = e group.append(e) if last.action_kind != e.action_kind: action = actions.get(last.action_kind) if action is not None: action(group) group = [e] last = e if e.action_kind == e.ADDED: added.append(e) if group: action = actions.get(group[0].action_kind) if action is not None: action(group) # Finally, deal with "copied" directories. The simple way is # executing an _addSubtree on each of them, evenif this may # cause "warnings" on items just moved/added above... while added: subdir = added.pop(0).name if isdir(join(self.repository.basedir, subdir)): self._addSubtree(subdir) added = [e for e in added if not e.name.startswith(subdir)] def _addEntries(self, entries): """ Add a sequence of entries """ self._addPathnames([e.name for e in entries]) def _addPathnames(self, names): """ Add some new filesystem objects. """ raise TailorBug("%s should override this method!" % self.__class__) def _addSubtree(self, subdir): """ Add a whole subtree. This implementation crawl down the whole subtree, adding entries (subdirs, skipping the usual VC-specific control directories such as ``.svn``, ``_darcs`` or ``CVS``, and files). Subclasses may use a better way, if the backend implements a recursive add that skips the various metadata directories. """ from os.path import join from os import walk from dualwd import IGNORED_METADIRS exclude = [] if self.state_file.filename.startswith(self.repository.basedir): sfrelname = self.state_file.filename[len(self.repository.basedir)+1:] exclude.append(sfrelname) exclude.append(sfrelname+'.old') exclude.append(sfrelname+'.journal') if self.logfile.startswith(self.repository.basedir): exclude.append(self.logfile[len(self.repository.basedir)+1:]) if subdir and subdir<>'.': self._addPathnames([subdir]) for dir, subdirs, files in walk(join(self.repository.basedir, subdir)): for excd in IGNORED_METADIRS: if excd in subdirs: subdirs.remove(excd) for excf in exclude: if excf in files: files.remove(excf) if subdirs or files: self._addPathnames([join(dir, df)[len(self.repository.basedir)+1:] for df in subdirs + files]) def _commit(self, date, author, patchname, changelog=None, entries=None, tags = [], isinitialcommit = False): """ Commit the changeset. """ raise TailorBug("%s should override this method!" % self.__class__) def _removeEntries(self, entries): """ Remove a sequence of entries. """ self._removePathnames([e.name for e in entries]) def _removePathnames(self, names): """ Remove some filesystem object. """ raise TailorBug("%s should override this method!" % self.__class__) def _editEntries(self, entries): """ Records a sequence of entries as updated. """ self._editPathnames([e.name for e in entries]) def _editPathnames(self, names): """ Records a sequence of filesystem objects as updated. """ pass def _renameEntries(self, entries): """ Rename a sequence of entries, adding all the parent directories of each entry. """ from os import rename, walk from shutil import rmtree from os.path import split, join, exists, isdir added = [] for e in entries: parents = [] parent = split(e.name)[0] while parent: if not parent in added: parents.append(parent) added.append(parent) parent = split(parent)[0] if parents: parents.reverse() self._addPathnames(parents) other = False if self.shared_basedirs: # Check to see if the oldentry is still there. If it is, # that probably means one thing: it's been moved and then # replaced, see svn 'R' event. In this case, rename the # existing old entry to something else to trick targets # (that will assume the move was already done manually) and # finally restore its name. absold = join(self.repository.basedir, e.old_name) renamed = exists(absold) if renamed: rename(absold, absold + '-TAILOR-HACKED-TEMP-NAME') else: # With disjunct directories, old entries are *always* # there because we dropped the --delete option to rsync. # So, instead of renaming the old entry, we temporarily # rename the new one, perform the target system rename # and replace back the real content (it may be a # renamed+edited event). # Hide the real new file from rename absnew = join(self.repository.basedir, e.name) renamed = exists(absnew) if renamed: rename(absnew, absnew + '-TAILOR-HACKED-TEMP-NAME') # If 'absold' exist, then the file was moved and replaced # with an other file. Hide the other file from rename. absold = join(self.repository.basedir, e.old_name) other = exists(absold) if other: rename(absold, absold + '-TAILOR-HACKED-OTHER-NAME') # Restore the old file from backup. oldfile = exists(absold + '-TAILOR-HACKED-OLD-NAME') if oldfile: rename(absold + '-TAILOR-HACKED-OLD-NAME', absold) try: self._renamePathname(e.old_name, e.name) finally: # Restore other NEW target if other: rename(absold + '-TAILOR-HACKED-OTHER-NAME', absold) if renamed: if self.shared_basedirs: rename(absold + '-TAILOR-HACKED-TEMP-NAME', absold) else: # before rsync after rsync the HACK after "svn mv" result # /basedir /basedir /basedir /basedir /basedir # | | | | | # +- /dirold +- /dirold +- /dirold +- /dirnew move | # | | | | | | |~~~~~~ hack | # +- /.svn | +- /.svn | +- /.svn | +- /.svn >-------+ | # | | | | | | | | | # +- /subdir | +- /subdir | +- /subdir | +- /subdir v | # | | | | | | | | | # +- /.svn | +- /.svn | +- /.svn | +- /.svn >--+ | | # | | | | | | # +- /dirnew +- /dirnew-HACKED +- /dirnew-HACKED v | +- /dirnew # |~~~~~~ | ~~~~~~~ | | | | # | | | +-|---> +- /.svn # | | | | | ~~~~ # +- /subdir +- /subdir +- /subdir | +- /subdir # ~~~~~~ | | # +-----> +- /.svn # ~~~~ # Ticket #65, #125 # If the target reposity has files in subdirectory, # then remove the complete dir. # But keep the dir '.svn', '_CVS', or what ever if isdir(absnew): if self.repository.METADIR <> None: for root, dirs, files in walk(absnew): if self.repository.METADIR in dirs: dirs.remove(self.repository.METADIR) # don't visit SVN directories svnnew = join(root, self.repository.METADIR) hacked = join(absnew + '-TAILOR-HACKED-TEMP-NAME' + root[len(absnew):], self.repository.METADIR) rename(svnnew, hacked) rmtree(absnew) rename(absnew + '-TAILOR-HACKED-TEMP-NAME', absnew) def _renamePathname(self, oldname, newname): """ Rename a filesystem object to some other name/location. """ raise TailorBug("%s should override this method!" % self.__class__) def prepareWorkingDirectory(self, source_repo): """ Do anything required to setup the hosting working directory. """ self._prepareWorkingDirectory(source_repo) def _prepareWorkingDirectory(self, source_repo): """ Possibly checkout a working copy of the target VC, that will host the upstream source tree, when overriden by subclasses. """ def prepareTargetRepository(self): """ Do anything required to host the target repository. """ from os import makedirs from os.path import join, exists if not exists(self.repository.basedir): makedirs(self.repository.basedir) self._prepareTargetRepository() prefix = self.__getPrefixToSource() if prefix: if not exists(join(self.repository.basedir, prefix)): # At bootstrap time, we assume that if the user # extracted the source manually, she added # the subdir, before doing that. makedirs(join(self.repository.basedir, prefix)) self._addPathnames([prefix]) def _prepareTargetRepository(self): """ Possibly create or connect to the repository, when overriden by subclasses. """ def importFirstRevision(self, source_repo, changeset, initial): """ Initialize a new working directory, just extracted from some other VC system, importing everything's there. """ self._initializeWorkingDir() # Execute the precommit hooks, but ignore None results changeset = self._adaptChangeset(changeset) or changeset revision = changeset.revision source_repository = str(source_repo) if initial: author = changeset.author patchname, log = self.__getPatchNameAndLog(changeset) else: author = "%s@%s" % (AUTHOR, HOST) patchname = BOOTSTRAP_PATCHNAME log = BOOTSTRAP_CHANGELOG % locals() self._commit(changeset.date, author, patchname, log, isinitialcommit = True) if changeset.tags: for tag in changeset.tags: self._tag(tag, changeset.date, author) self._dismissChangeset(changeset) def _initializeWorkingDir(self): """ Assuming the ``basedir`` directory contains a working copy ``module`` extracted from some VC repository, add it and all its content to the target repository. This implementation recursively add every file in the subtree. Subclasses should override this method doing whatever is appropriate for the backend. """ self._addSubtree('.') def _tag(self, tagname, date, author): """ Tag the current version, if the VC type supports it, otherwise do nothing. """ pass PK&f6Ҩ)mWWvcpx/__init__.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx - Version Control Patch eXchanger # :Creato: mer 16 giu 2004 00:15:54 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ vcpx - Version Control Patch eXchanger ====================================== This package encapsulates the machinery needed to keep the patches in sync across different VC systems. """ __docformat__ = 'reStructuredText' class TailorException(Exception): "Common base for tailor exceptions" class TailorBug(TailorException): "Tailor bug (please report)" PKq38dw==vcpx/tailor.pyo; Fc@sdZdZdZdklZdklZlZlZdk l Z dk l Z l Z dklZdklZd efd YZd efd YZed ddddddeddedddddddeddeddddddeddd dedd!gZed"d#dd$dd%ed&d'dddedd(ed)d*dddedd+gZed,d-dd.dd/dd0dd1ed2d3dd4dd/dd5dd6ed7d8d9dd:dd;dd<ed=d>d?dd@ddAddBedCdDdEddFddGddHddIedJdKddLdd;deddMedNdOddPddAddQedRddSddTgZedUdddeddVddWedXdddeddYddZgZd[e fd\YZd]e fd^YZd_Zd`S(as) Implement the frontend functionalities. sreStructuredTexts0.9.29(s getLogger(s OptionParsers OptionGroupsOption(sTailorException(sConfigsConfigurationError(sProject(sGetUpstreamChangesetsFailures TailorizercBs;tZdZdZdZdZdZdZRS(s A Tailorizer has two main capabilities: its able to bootstrap a new Project, or brought it in sync with its current upstream revision. cCsi|io>|iid|i|io|iid|iqHn|iidt|tSdS(s4 Print the changeset being applied. sChangeset "%s"sLog message: %ssGoing to apply changeset: %sN( sselfsverboseslogsinfos changesetsrevisionsdebugsstrsTrue(sselfs changeset((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys _applyables  cCs&|io|iiddndS(s9 Separate changesets with an empty line. s-*iN(sselfsverboseslogsinfo(sselfs changeset((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys_applied+s cCs|iid|i|i|i}y|i|iWn|ii dnX|i i |idd}y|i |}Wn |ii d|inXy |i|i|d|jWn |ii d|inX|iiddS( sC Bootstrap a new tailorized module. First of all prepare the target system working directory such that it can host the upstream source tree. This is backend specific. Then extract a copy of the upstream repository and import its content into the target repository. sBootstrapping "%s" in "%s"s!Cannot prepare working directory!sstart-revisionsINITIALsCheckout of %s failed!s*Could not import checked out tree in "%s"!sBootstrap completedN(sselfslogsinfosnamesrootdirs workingDirsdwdsprepareWorkingDirectoryssourcescriticalsconfigsgetsrevisionscheckoutUpstreamRevisionsactualsimportFirstRevision(sselfsactualsdwdsrevision((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys bootstrap3s*     cCsL|iid|i|i|i}y|i}WnKt j o |ii d|in!|ii d|inX|i o|iidy(|i d|id|i\}}WnEt j o |ii d|in|ii dnX|o|iid |iqHn|iid d S( s8 Update an existing tailorized project. sUpdating "%s" in "%s"s'Leaving "%s" unchanged, stopped by usersUnable to get changes for "%s"s$Applying pending upstream changesetss applyablesapplieds(Leaving "%s" incomplete, stopped by users"Upstream change application faileds&Update completed, now at revision "%s"s)Update completed with no upstream changesN(sselfslogsinfosnamesrootdirs workingDirsdwdsgetPendingChangesetsspendingssKeyboardInterruptswarningscriticalspendingsapplyPendingChangesetss _applyables_appliedslasts conflictssrevision(sselfslastspendingssdwds conflicts((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pysupdateXs6    (   cs!dkl}dkl}dkl}td}|d|_|ddt }|t j o|i |_ n|d|_|d |_yEi o)i|d d jodSqniWn@ttfj o.}td |iiiifnXdS( N(sExternalCommand(sSynchronizableTargetWorkingDir(s Changesetcs iii|d|SdS(Nsraw(sselfsconfigsgetsnamesoptionsraw(soptionsraw(sself(s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pyspconfigssdebugspatch-name-formatsrawsremove-first-log-linesrefill-changelogssstart-revisionsHEADs%s: it seems that the encoding used by either the source ("%s") or the target ("%s") repository cannot properly represent at least one of the characters in the upstream changelog. You need to use a wider character set, using "encoding" option, or even "encoding-errors-policy".(sshwrapsExternalCommandstargetsSynchronizableTargetWorkingDirschangess ChangesetsFalsespconfigsDEBUGsTrues pname_formatsNonesstripsPATCH_NAME_FORMATsREMOVE_FIRST_LOG_LINEsREFILL_MESSAGEsselfsexistss bootstrapsupdatesUnicodeDecodeErrorsUnicodeEncodeErrorsexcsConfigurationErrorssourcesencoding(sselfs ChangesetsExternalCommandsexcspconfigs pname_formatsSynchronizableTargetWorkingDir((sselfs/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys__call__~s$      (s__name__s __module__s__doc__s _applyables_applieds bootstrapsupdates__call__(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys Tailorizers   % &s RecogOptioncBstZdZdZRS(s Make it possible to recognize an option explicitly given on the command line from those simply coming out for their default value. cCs4t|d|itti|||||SdS(Ns__seen_( ssetattrsvaluessselfsdestsTruesOptionsprocesssoptsvaluesparser(sselfsoptsvaluesvaluessparser((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pysprocesss(s__name__s __module__s__doc__sprocess(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys RecogOptions s-Ds--debugsdestsdebugsactions store_truesdefaultshelpszPrint each executed command. This also keeps temporary files with the upstream logs, that are otherwise removed after use.s-vs --verbosesverbosesFBe verbose, echoing the changelog of each applied changeset to stdout.s-cs --configfilesmetavarsCONFNAMEsCentralized storage of projects info. With this option and no other arguments tailor will update every project found in the config file.s --encodingsCHARSETswForce the output encoding to given CHARSET, rather then using the user's default settings specified in the environment.s-Fs--patch-name-formatsFORMATs4Specify the prototype that will be used to compute the patch name. The prototype may contain %(keyword)s such as 'author', 'date', 'revision', 'firstlogline', 'remaininglog'. It defaults to 'Tailorized "%(revision)s"'; setting it to the empty string means that tailor will simply use the original changelog.s-1s--remove-first-log-linesRemove the first line of the upstream changelog. This is intended to pair with --patch-name-format, when using its 'firstlogline' variable to build the name of the patch.s-Ns--refill-changelogssBRefill every changelog, useful when upstream logs are not uniform.s-ss --source-kinds source_kindsVC-KINDsUSelect the backend for the upstream source version control VC-KIND. Default is 'cvs'.scvss-ts --target-kinds target_kindsMSelect VC-KIND as backend for the shadow repository, with 'darcs' as default.sdarcss-Rs --repositorys--source-repositoryssource_repositorysREPOSsSpecify the upstream repository, from where bootstrap will checkout the module. REPOS syntax depends on the source version control kind.s-ms--modules--source-modules source_modulesMODULEswSpecify the module to checkout at bootstrap time. This has different meanings under the various upstream systems: with CVS it indicates the module, while under SVN it's the prefix of the tree you want and must begin with a slash. Since it's used in the description of the target repository, you may want to give it a value with darcs too, even though it is otherwise ignored.s-rs --revisions--start-revisionsstart_revisionsREVsjSpecify the revision bootstrap should checkout. REV must be a valid 'name' for a revision in the upstream version control kind. For CVS it may be either a branch name, a timestamp or both separated by a space, and timestamp may be 'INITIAL' to denote the beginning of time for the given branch. Under Darcs, INITIAL is a shortcut for the name of the first patch in the upstream repository, otherwise it is interpreted as the name of a tag. Under Subversion, 'INITIAL' is the first patch that touches given repos/module, otherwise it must be an integer revision number. 'HEAD' means the latest version in all backends.sINITIALs-Ts--target-repositorystarget_repositorys`Specify the target repository, the one that will receive the patches coming from the source one.s-Ms--target-modules target_modules`Specify the module on the target repository that will actually contain the upstream source tree.s--subdirsDIRshForce the subdirectory where the checkout will happen, by default it's the tail part of the module name.s --use-propsets use_propsetsUse 'svn propset' to set the real date and author of each commit, instead of appending these information to the changelog. This requires some tweaks on the SVN repository to enable revision propchanges.s--ignore-arch-idss ignore_idss5Ignore .arch-ids directories when using a tla source.sExistingProjectErrorcBstZdZRS(sProject seems already tailored(s__name__s __module__s__doc__(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pysExistingProjectErrors sProjectNotTailoredcBstZdZRS(sNot a tailored project(s__name__s __module__s__doc__(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pysProjectNotTailoreds cCs:dk} dkl} tdddtdt}t|d}|i t t|d}|i t t|d } | i t|i||i||i| |i\}} h}xu|iiD]d\}}|id oqn|d jot|d |o t|||id dddddddddgD]}||jo ||=q(q(Wt t|}|i)d|i*d}|i,dd||i-d} |i,dd| |i,dd| |i,dd|i/pd|i,dd d!|i,dd|i0|i)||i1o|i,|d"|i1nt2d#}|i4d$|i5o|i,|d%|i5n|i)| |i6o|i,| d"|i6n|i7o|i,| d%|i7n|i8o$| i9i:d&|i:| i;n|i<ot%d|}|n |i8 o| i9i:d'ndS((s Script entry point. Parse the command line options and arguments, and for each specified working copy directory (the current working directory by default) execute the tailorization steps. N(sgetcwdsusages%prog [options] [project ...]sversions option_listsBootstrap optionssUpdate optionssVC specific optionss__s configfiles__seen_s_s-iis source-kinds target-kinds source-modules target-modulessource-repositorystarget-repositorysstart-revisionssubdirsprojects:sourcessources:targetstargetsroot-directorys.s state-files tailor.states repositorystailorsUBy any chance you forgot either the --source-repository or the --configfile option...smodulesYou should put the following configuration in some file, adjust it as needed and use --configfile option with that file as argument: s'Operation not performed, try --verbose (=ssyssossgetcwds OptionParsers __version__sGENERAL_OPTIONSsparsers OptionGroups bsoptionss add_optionssBOOTSTRAP_OPTIONSs upoptionssUPDATE_OPTIONSs vcoptionssVC_SPECIFIC_OPTIONSsadd_option_groups parse_argssoptionssargssdefaultss__dict__sitemssksvs startswithshasattrsstrsreplaces configfileslensargvsNonesConfigsopensconfigsprojectssprojnames Tailorizers tailorizersGetUpstreamChangesetsFailuresomits add_sections source_kindssourcessets target_kindstargetssubdirsstart_revisionssource_repositorys getLoggersloggerswarnings source_modulestarget_repositorys target_modulesverbosesstderrswritesstdoutsdebug(s bsoptionssparsers tailorizersprojnamessources upoptionssloggersconfigsdefaultssargssgetcwdssyss vcoptionsstargetsksomitsvsoptions((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pysmains           !$3                    N(s__doc__s __docformat__s __version__sloggings getLoggersoptparses OptionParsers OptionGroupsOptionsvcpxsTailorExceptions vcpx.configsConfigsConfigurationErrors vcpx.projectsProjects vcpx.sourcesGetUpstreamChangesetsFailures Tailorizers RecogOptionsFalsesNonesGENERAL_OPTIONSsUPDATE_OPTIONSsBOOTSTRAP_OPTIONSsVC_SPECIFIC_OPTIONSsExistingProjectErrorsProjectNotTailoredsmain(sGetUpstreamChangesetsFailuresUPDATE_OPTIONSs __docformat__sBOOTSTRAP_OPTIONSsTailorExceptionsProjectNotTailoredsConfigsOptionsVC_SPECIFIC_OPTIONSsmains RecogOptions OptionParsersGENERAL_OPTIONSsProjects OptionGroups __version__sExistingProjectErrorsConfigurationErrors getLoggers Tailorizer((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/tailor.pys? s`         PKq38rU("("vcpx/config.pyc; Fc@s{dZdZdklZdklZlZlZdkl Z de fdYZ dZ dZ d efd YZ d S( s# Handle the configuration details. sreStructuredText(sStringIO(sSafeConfigParsersNoSectionErrors DEFAULTSECT(sTailorExceptionsConfigurationErrorcBstZdZRS(sConfiguration error(s__name__s __module__s__doc__(((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/config.pysConfigurationErrors s [[logging]]s8[formatters] keys = console [formatter_console] format = %(asctime)s [%(levelname).1s] %(message)s datefmt = %H:%M:%S [loggers] keys = root [logger_root] level = INFO handlers = console [handlers] keys = console [handler_console] class = StreamHandler formatter = console args = (sys.stdout,) level = INFO sConfigcBsGtZdZdZdZdZeeedZedZ RS(s Syntactic sugar around standard ConfigParser, for easier access to the configuration. To access any single project use the configuration as a dictionary. The file may be a full fledged Python script, starting with the usual ``"#!..."`` notation: in this case, it gets evaluated and its documentation becomes the actual configuration, while the functions it defines may be referenced by the `before-commit` and `after-commit` slots. This is where the logging system gets initialized, possibly merging a logging specific configuration section, introduced by a *supersection* ``[[logging]]``. cBsei|h|_e}|o|iddjo4|id|ie |iU|id}n|id|i}|i e }e|djo|\}}n |d}|ie|n|o|ii|n|i|o|pedS(Nis#!is__doc__(sSafeConfigParsers__init__sselfs namespacesNones loggingcfgsfpsreadsseeksglobalssconfigssplitsLOGGING_SUPER_SECTIONscfgsslens tailorcfgsreadfpsStringIOsdefaultss _defaultssupdates _setupLoggingsBASIC_LOGGING_CONFIG(sselfsfpsdefaultss loggingcfgscfgss tailorcfgsconfig((s/build/bdist.darwin-8.0.1-x86/egg/vcpx/config.pys__init__Cs$      c!Csbdk}dk}t}|it||i|i dd}|o|i d}h}x|D]} d| }|i|}d|jo|i |dd} nt} d|jo|i |dd}nt}|i| |}||| # :Licenza: GNU General Public License # """ Changesets are an object representation of a set of changes to some files. """ __docformat__ = 'reStructuredText' from vcpx import TailorBug class ChangesetEntry(object): """ Represent a changed entry in a Changeset. For our scope, this simply means an entry ``name``, the original ``old_revision``, the ``new_revision`` after this change, an ``action_kind`` to denote the kind of change, and finally a ``status`` to indicate possible conflicts. """ ADDED = 'ADD' DELETED = 'DEL' UPDATED = 'UPD' RENAMED = 'REN' APPLIED = 'APPLIED' CONFLICT = 'CONFLICT' def __init__(self, name): self.name = name self.old_name = None self.old_revision = None self.new_revision = None self.action_kind = None self.status = None self.unidiff = None # This is the unidiff of this particular entry def __str__(self): s = self.name + '(' + self.action_kind if self.action_kind == self.ADDED: if self.new_revision: s += ' at ' + self.new_revision elif self.action_kind == self.UPDATED: if self.new_revision: s += ' to ' + self.new_revision elif self.action_kind == self.DELETED: if self.new_revision: s += ' at ' + self.new_revision else: s += ' from ' + self.old_name s += ')' if isinstance(s, unicode): s = s.encode('ascii', 'replace') return s from textwrap import TextWrapper from re import compile, MULTILINE itemize_re = compile('^[ ]*[-*] ', MULTILINE) def refill(msg): """ Refill a changelog message. Normalize the message reducing multiple spaces and newlines to single spaces, recognizing common form of ``bullet lists``, that is paragraphs starting with either a dash "-" or an asterisk "*". """ wrapper = TextWrapper() res = [] items = itemize_re.split(msg.strip()) if len(items)>1: # Remove possible first empty split, when the message immediately # starts with a bullet if not items[0]: del items[0] if len(items)>1: wrapper.initial_indent = '- ' wrapper.subsequent_indent = ' '*2 for item in items: if item: words = filter(None, item.strip().replace('\n', ' ').split(' ')) normalized = ' '.join(words) res.append(wrapper.fill(normalized)) return '\n\n'.join(res) class Changeset(object): """ Represent a single upstream Changeset. This is a container of each file affected by this revision of the tree. """ ANONYMOUS_USER = "anonymous" """Author name when it is not known""" REFILL_MESSAGE = False """Refill changelogs""" def _get_date(self): try: return self.__date except AttributeError, e: # handle state-file Changesets created with previous versions of tailor from vcpx.tzinfo import UTC self.__date = self.__dict__['date'].replace(tzinfo=UTC) return self.__date def _set_date(self, date): if date and date.tzinfo is None: raise TailorBug("Changeset dates must have a timezone!") self.__date = date # date has to be a property because some backends (eg. monotone) # update it after the constructor date = property(_get_date, _set_date) def __init__(self, revision, date, author, log, entries=None, **other): """ Initialize a new Changeset. """ self.revision = revision self.date = date # Author name may be missing, to mean a check in made by an # anonymous user. self.author = author or self.ANONYMOUS_USER self.setLog(log) self.entries = entries or [] self.unidiff = None # This is the unidiff of the whole changeset self.tags = other.get('tags', None) def __eq__(self, other): return (self.revision == other.revision and self.date == other.date and self.author == other.author) def __ne__(self, other): return (self.revision <> other.revision or self.date <> other.date or self.author <> other.author) def setLog(self, log): if self.REFILL_MESSAGE: self.log = refill(log) else: self.log = log def addEntry(self, entry, revision, before=None): """ Facility to add an entry, eventually before another one. """ e = ChangesetEntry(entry) e.new_revision = revision if before is None: self.entries.append(e) else: self.entries.insert(self.entries.index(before), e) return e def __str__(self): s = [] s.append('Revision: %s' % self.revision) s.append('Date: %s' % str(self.date)) s.append('Author: %s' % self.author) s.append('Entries: %s' % ', '.join([str(x) for x in self.entries])) s.append('Log: %s' % self.log) s = '\n'.join(s) if isinstance(s, unicode): s = s.encode('ascii', 'replace') return s def applyPatch(self, working_dir, patch_options="-p1"): """ Apply the changeset using ``patch(1)`` to a given directory. """ from shwrap import ExternalCommand from source import ChangesetApplicationFailure if self.unidiff: cmd = ["patch"] if patch_options: if isinstance(patch_options, basestring): cmd.extend(patch_options.split(' ')) else: cmd.extend(patch_options) patch = ExternalCommand(cwd=working_dir, command=cmd) patch.execute(input=self.unidiff) if patch.exit_status: raise ChangesetApplicationFailure( "%s returned status %s" % (str(patch), patch.exit_status)) def addedEntries(self): """ Facility to extract a list of added entries. """ return [e for e in self.entries if e.action_kind == e.ADDED] def modifiedEntries(self): """ Facility to extract a list of modified entries. """ return [e for e in self.entries if e.action_kind == e.UPDATED] def removedEntries(self): """ Facility to extract a list of deleted entries. """ return [e for e in self.entries if e.action_kind == e.DELETED] def renamedEntries(self): """ Facility to extract a list of renamed entries. """ return [e for e in self.entries if e.action_kind == e.RENAMED] PK&f6ejvcpx/dualwd.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx -- Dual working directory # :Creato: dom 20 giu 2004 11:02:01 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ The easiest way to propagate changes from one VC control system to one of an another kind is having a single directory containing a live working copy shared between the two VC systems. In a slightly more elaborated way, the source and the target system may use separate directories, that gets rsynced when needed. This module implements `DualWorkingDir`, which instances have a `source` and `target` properties offering the right capabilities to do the job. """ __docformat__ = 'reStructuredText' from source import UpdatableSourceWorkingDir, InvocationError from target import SynchronizableTargetWorkingDir from shwrap import ExternalCommand from datetime import datetime IGNORED_METADIRS = [] class DualWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir): """ Dual working directory, one that is under two different VC systems at the same time. This class reimplements the two interfaces, dispatching the right method to the right backend. """ def __init__(self, source_repo, target_repo): global IGNORED_METADIRS from os.path import sep self.source = source_repo.workingDir() self.target = target_repo.workingDir() sbdir = self.source.repository.basedir.rstrip(sep)+sep tbdir = self.target.repository.basedir.rstrip(sep)+sep if sbdir == tbdir: shared = True elif tbdir.startswith(sbdir): raise InvocationError('Target base directory "%s" cannot be a ' 'subdirectory of source directory "%s"' %( (tbdir, sbdir))) elif sbdir.startswith(tbdir): shared = True else: shared = False self.shared_basedirs = shared self.source.shared_basedirs = shared self.target.shared_basedirs = shared IGNORED_METADIRS = filter(None, [source_repo.METADIR, target_repo.METADIR]) IGNORED_METADIRS.extend(source_repo.EXTRA_METADIRS) IGNORED_METADIRS.extend(target_repo.EXTRA_METADIRS) self.source.prepareSourceRepository() self.target.prepareTargetRepository() # UpdatableSourceWorkingDir self.getPendingChangesets = self.source.getPendingChangesets self.checkoutUpstreamRevision = self.source.checkoutUpstreamRevision # SynchronizableTargetWorkingDir self.prepareWorkingDirectory = self.target.prepareWorkingDirectory def setStateFile(self, state_file): """ Set the state file used to store the revision and pending changesets. """ self.source.setStateFile(state_file) self.target.setStateFile(state_file) def setLogfile(self, logfile): """ Set the name of the logfile, just to ignore it. """ self.target.logfile = logfile def applyPendingChangesets(self, applyable=None, replay=None, applied=None): return self.source.applyPendingChangesets(replay=self.replayChangeset, applyable=applyable, applied=applied) def importFirstRevision(self, source_repo, changeset, initial): if not self.shared_basedirs: self._syncTargetWithSource() self.target.importFirstRevision(source_repo, changeset, initial) def replayChangeset(self, changeset): if not self.shared_basedirs: self._saveRenamedTargets(changeset) self._syncTargetWithSource() self.target.replayChangeset(changeset) def _syncTargetWithSource(self): cmd = ['rsync', '--archive'] now = datetime.now() if hasattr(self, '_last_rsync'): last = self._last_rsync if not (now-last).seconds: cmd.append('--ignore-times') self._last_rsync = now for M in IGNORED_METADIRS: cmd.extend(['--exclude', M]) rsync = ExternalCommand(command=cmd) rsync.execute(self.source.repository.basedir+'/', self.target.repository.basedir) def _saveRenamedTargets(self, changeset): """ Save old names from `rename`, before rsync replace it with new file. """ from os.path import join, exists from os import rename for e in changeset.entries: if e.action_kind == e.RENAMED: absold = join(self.target.repository.basedir, e.old_name) if exists(absold): rename(absold, absold + '-TAILOR-HACKED-OLD-NAME') PK&f6A5vcpx/project.py# -*- mode: python; coding: utf-8 -*- # :Progetto: vcpx -- Project details # :Creato: gio 04 ago 2005 13:07:31 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # """ This module implements a higher level of operations, with a Project class that knows how to drive the two main activities, bootstrap and update, layering on top of DualWorkingDir. """ __docformat__ = 'reStructuredText' from vcpx import TailorException from vcpx.config import ConfigurationError from vcpx.statefile import StateFile class UnknownProjectError(TailorException): "Project does not exist" class Project(object): """ This class collects the information related to a single project, such as its source and target repositories and state file. All the setup comes from a section in the configuration file (.ini-like format) with the same name as the project. Mandatory options are: root-directory This is where all the fun will happen: this directory will contain the source and the target working copy, and usually the state and the log file. It support the conventional "~user" to indicate user's home directory. subdir This is the subdirectory, relative to the root-directory, where tailor will extract the source working copy. It may be '.' for some backend kinds. state-file Name of the state file needed to store tailor last activity. source The source repository: a repository name is something like "darcs:somename", that will be loaded from the homonymous section in the configuration. target The counterpart of `source`, the repository that will receive the changes coming from there. Non mandatory options: before-commit This is a function name, or a sequence of function names enclosed by brackets, that will be executed on each changeset just before it get replayed on the target system: this may be used to perform any kind of alteration on the content of the changeset, or to skip some of them. after-commit This is a function name, or a sequence of function names enclosed by brackets, that will be executed on each changeset just after the commit on the target system: this may be used for example to create a tag. start-revision This identifies from when tailor should start the migration. It can be either ``INITIAL``, to indicate the start of the history, or ``HEAD`` to indicate the current latest changeset, or a backend specific way of indicate a particular revision/tag in the history. """ def __init__(self, name, config): """ Initialize a new instance representing the project `name`. """ from ConfigParser import Error self.loghandler = None if not config.has_section(name): raise UnknownProjectError("'%s' is not a known project" % name) self.config = config self.name = name self.dwd = None try: self._load() except Error, e: raise ConfigurationError('Invalid configuration in section %s: %s' % (self.name, str(e))) def __str__(self): return "Project %s at %s:\n\t" % (self.name, self.rootdir) + \ "\n\t".join(['%s = %s' % (v, getattr(self, v)) for v in ('source', 'target', 'state_file')]) def _load(self): """ Load relevant information from the configuration. """ from os import makedirs from os.path import join, exists, expanduser, abspath from logging import getLogger, CRITICAL, DEBUG, FileHandler, \ StreamHandler, Formatter self.verbose = self.config.get(self.name, 'verbose', False) rootdir = self.config.get(self.name, 'root-directory', '.') self.rootdir = abspath(expanduser(rootdir)) if not exists(self.rootdir): makedirs(self.rootdir) self.subdir = self.config.get(self.name, 'subdir') if not self.subdir: self.subdir = '.' self.logfile = join(self.rootdir, self.name + '.log') self.log = getLogger('tailor.project.%s' % self.name) if self.config.get(self.name, 'debug'): self.log.setLevel(DEBUG) tailorlog = getLogger('tailor') formatter = Formatter(self.config.get( self.name, 'log-format', '%(asctime)s %(levelname)8s: %(message)s', raw=True), self.config.get( self.name, 'log-datefmt', '%Y-%m-%d %H:%M:%S', raw=True)) self.loghandler = FileHandler(self.logfile) self.loghandler.setFormatter(formatter) self.loghandler.setLevel(DEBUG) tailorlog.addHandler(self.loghandler) self.source = self.__loadRepository('source') self.target = self.__loadRepository('target') sfpath = self.config.get(self.name, 'state-file', self.name + '.state') sfpath = join(self.rootdir, self.target.stateFilePath(sfpath)) self.state_file = StateFile(sfpath, self.config) before = self.config.getTuple(self.name, 'before-commit') try: self.before_commit = [self.config.namespace[f] for f in before] except KeyError, e: raise ConfigurationError('Project "%s" before-commit references ' 'unknown function: %s' % (self.name, str(e))) after = self.config.getTuple(self.name, 'after-commit') try: self.after_commit = [self.config.namespace[f] for f in after] except KeyError, e: raise ConfigurationError('Project "%s" after-commit references ' 'unknown function: %s' % (self.name, str(e))) if not self.config.get(self.name, 'verbose', False): # Disable console output rootlog = getLogger() rootlog.disabled = True for h in rootlog.handlers: if isinstance(h, StreamHandler): h.setLevel(CRITICAL) def __del__(self): if self.loghandler is not None: from logging import getLogger getLogger('tailor').removeHandler(self.loghandler) def __loadRepository(self, which): """ Given a repository named 'somekind:somename', return a Repository (or a subclass of it, if 'SomekindRepository' exists) instance that wraps it. """ from repository import Repository repname = self.config.get(self.name, which) if repname.endswith(':'): repname += self.name return Repository(repname, self, which) def exists(self): """ Return True if the project exists, False otherwise. """ return self.state_file.lastAppliedChangeset() is not None def workingDir(self): """ Return a DualWorkingDir instance, ready to work. """ from dualwd import DualWorkingDir if self.dwd is None: self.dwd = DualWorkingDir(self.source, self.target) self.dwd.setStateFile(self.state_file) self.dwd.setLogfile(self.logfile) return self.dwd PK&f6,1f#f#vcpx/shwrap.py# -*- mode: python; coding: iso-8859-1 -*- # :Progetto: vcpx -- Tiny wrapper around external command # :Creato: sab 10 apr 2004 16:43:48 CEST # :Autore: Lele Gaifax # :Licenza: GNU General Public License # __docformat__ = 'reStructuredText' try: # Python 2.4 from subprocess import Popen, PIPE, STDOUT except ImportError: # Older snakes from _process import Popen, PIPE, STDOUT class ReopenableNamedTemporaryFile: """ This uses tempfile.mkstemp() to generate a secure temp file. It then closes the file, leaving a zero-length file as a placeholder. You can get the filename with ReopenableNamedTemporaryFile.name. When the ReopenableNamedTemporaryFile instance is garbage collected or its shutdown() method is called, it deletes the file. Copied from Zooko's pyutil.fileutil, http://zooko.com/repos/pyutil """ def __init__(self, suffix=None, prefix=None, dir=None, text=None): from tempfile import mkstemp from os import close fd, self.name = mkstemp(suffix, prefix, dir, text) close(fd) def __del__(self): self.shutdown() def shutdown(self): from os import remove remove(self.name) class ExternalCommand: """Wrap a single command to be executed by the shell.""" DEBUG = False """Print the output of the command, when not PIPEd to the caller.""" DRY_RUN = False """Don't really execute the command.""" MAX_CMDLINE_LENGTH = 8000 """Don't execute commands longer than this number of characters.""" def __init__(self, command=None, cwd=None, nolog=False): """ Initialize a ExternalCommand instance, specifying the command to be executed and eventually the working directory. The instance will use the logger ``tailor.shell``. """ from logging import getLogger self.command = command """The command to be executed.""" self.cwd = cwd """The working directory, go there before execution.""" self.exit_status = None """Once the command has been executed, this is its exit status.""" self._last_command = None """Last executed command.""" self.capture_stderr = False if nolog: self.log = False else: self.log = getLogger('tailor.shell') def __str__(self): """ Return a string representation of the command prefixed by working dir. """ r = '$'+repr(self) if self.cwd: r = self.cwd + ' ' + r if self.capture_stderr: r = r + ' 2>&1' return r def __repr__(self): """ Compute a reasonable shell-like representation of the external command. """ result = [] needquote = False for arg in self._last_command or self.command: bs_buf = [] # Add a space to separate this argument from the others result.append(' ') needquote = (" " in arg) or ("\t" in arg) if needquote: result.append('"') for c in arg: if c == '\\': # Don't know if we need to double yet. bs_buf.append(c) elif c == '"': # Double backspaces. result.append('\\' * len(bs_buf)*2) bs_buf = [] result.append('\\"') else: # Normal char if bs_buf: result.extend(bs_buf) bs_buf = [] result.append(c) # Add remaining backspaces, if any. if bs_buf: result.extend(bs_buf) if needquote: result.extend(bs_buf) result.append('"') return ''.join(result) def execute(self, *args, **kwargs): """Execute the command, avoiding too long command line.""" from cStringIO import StringIO if kwargs.get('stderr'): self.capture_stderr = True else: self.capture_stderr = False if len(args) == 1 and type(args[0]) == type([]): allargs = list(args[0]) else: allargs = list(args) maxlen = self.MAX_CMDLINE_LENGTH if maxlen is None or len(allargs) < 2: return self._execute(allargs, **kwargs) startlen = len(' '.join(self.command)) allout = None allerr = None while allargs: thisrun = [] clen = startlen pop = allargs.pop append = thisrun.append while allargs and clen