1# -*- mode: python; coding: utf-8 -*- 2# :Progetto: vcpx -- Dual working directory 3# :Creato: dom 20 giu 2004 11:02:01 CEST 4# :Autore: Lele Gaifax <lele@nautilus.homeip.net> 5# :Licenza: GNU General Public License 6# 7 8""" 9The easiest way to propagate changes from one VC control system to one 10of an another kind is having a single directory containing a live 11working copy shared between the two VC systems. 12 13In a slightly more elaborated way, the source and the target system may 14use separate directories, that gets rsynced when needed. 15 16This module implements `DualWorkingDir`, which instances have a 17`source` and `target` properties offering the right capabilities to do 18the job. 19""" 20from __future__ import absolute_import 21 22__docformat__ = 'reStructuredText' 23 24from .source import UpdatableSourceWorkingDir, InvocationError 25from .target import SynchronizableTargetWorkingDir 26from .shwrap import ExternalCommand 27from datetime import datetime 28 29IGNORED_METADIRS = [] 30 31class DualWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir): 32 """ 33 Dual working directory, one that is under two different VC systems at 34 the same time. 35 36 This class reimplements the two interfaces, dispatching the right method 37 to the right backend. 38 """ 39 40 def __init__(self, source_repo, target_repo): 41 global IGNORED_METADIRS 42 from os.path import sep 43 44 self.source = source_repo.workingDir() 45 self.target = target_repo.workingDir() 46 47 sbdir = self.source.repository.basedir.rstrip(sep)+sep 48 tbdir = self.target.repository.basedir.rstrip(sep)+sep 49 if sbdir == tbdir: 50 shared = True 51 elif tbdir.startswith(sbdir): 52 raise InvocationError('Target base directory "%s" cannot be a ' 53 'subdirectory of source directory "%s"' %( 54 (tbdir, sbdir))) 55 elif sbdir.startswith(tbdir): 56 shared = True 57 else: 58 shared = False 59 self.shared_basedirs = shared 60 self.source.shared_basedirs = shared 61 self.target.shared_basedirs = shared 62 63 IGNORED_METADIRS = [_f for _f in [source_repo.METADIR, 64 target_repo.METADIR] if _f] 65 IGNORED_METADIRS.extend(source_repo.EXTRA_METADIRS) 66 IGNORED_METADIRS.extend(target_repo.EXTRA_METADIRS) 67 68 self.source.prepareSourceRepository() 69 self.target.prepareTargetRepository() 70 71 # UpdatableSourceWorkingDir 72 73 self.getPendingChangesets = self.source.getPendingChangesets 74 self.checkoutUpstreamRevision = self.source.checkoutUpstreamRevision 75 76 # SynchronizableTargetWorkingDir 77 78 self.prepareWorkingDirectory = self.target.prepareWorkingDirectory 79 80 def setStateFile(self, state_file): 81 """ 82 Set the state file used to store the revision and pending changesets. 83 """ 84 85 self.source.setStateFile(state_file) 86 self.target.setStateFile(state_file) 87 88 def setLogfile(self, logfile): 89 """ 90 Set the name of the logfile, just to ignore it. 91 """ 92 93 self.target.logfile = logfile 94 95 def applyPendingChangesets(self, applyable=None, replay=None, applied=None): 96 def pre_replay(changeset): 97 if applyable and not applyable(changeset): 98 return 99 return self.target._prepareToReplayChangeset(changeset) 100 101 return self.source.applyPendingChangesets(replay=self.replayChangeset, 102 applyable=pre_replay, 103 applied=applied) 104 105 def importFirstRevision(self, source_repo, changeset, initial): 106 if not self.shared_basedirs: 107 self._syncTargetWithSource() 108 self.target.importFirstRevision(source_repo, changeset, initial) 109 110 def replayChangeset(self, changeset): 111 if not self.shared_basedirs: 112 self._saveRenamedTargets(changeset) 113 self._syncTargetWithSource() 114 self.target.replayChangeset(changeset) 115 116 def _syncTargetWithSource(self): 117 cmd = ['rsync', '--archive'] 118 now = datetime.now() 119 if hasattr(self, '_last_rsync'): 120 last = self._last_rsync 121 if not (now-last).seconds: 122 cmd.append('--ignore-times') 123 self._last_rsync = now 124 for M in IGNORED_METADIRS: 125 cmd.extend(['--exclude', M]) 126 127 rsync = ExternalCommand(command=cmd) 128 rsync.execute(self.source.repository.basedir+'/', self.target.repository.basedir) 129 130 def _saveRenamedTargets(self, changeset): 131 """ 132 Save old names from `rename`, before rsync replace it with new file. 133 """ 134 135 from os.path import join, exists 136 from os import rename 137 138 for e in changeset.entries: 139 if e.action_kind == e.RENAMED: 140 absold = join(self.target.repository.basedir, e.old_name) 141 if exists(absold): 142 rename(absold, absold + '-TAILOR-HACKED-OLD-NAME') 143