source: tailor/vcpx/dualwd.py @ 1624

Revision 1624, 5.0 KB checked in by lele@…, 5 years ago (diff)

Use rsync --delete when the target is darcs
The darcs target backend now writes directly to the pending file to
assemble new patches, and thus it assumes the source already applied
all the changes, in particular removed deleted files.

Line 
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"""
20
21__docformat__ = 'reStructuredText'
22
23from source import UpdatableSourceWorkingDir, InvocationError
24from target import SynchronizableTargetWorkingDir
25from shwrap import ExternalCommand
26from datetime import datetime
27
28IGNORED_METADIRS = []
29
30class DualWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir):
31    """
32    Dual working directory, one that is under two different VC systems at
33    the same time.
34
35    This class reimplements the two interfaces, dispatching the right method
36    to the right backend.
37    """
38
39    def __init__(self, source_repo, target_repo):
40        global IGNORED_METADIRS
41        from os.path import sep
42
43        self.source = source_repo.workingDir()
44        self.target = target_repo.workingDir()
45
46        sbdir = self.source.repository.basedir.rstrip(sep)+sep
47        tbdir = self.target.repository.basedir.rstrip(sep)+sep
48        if sbdir == tbdir:
49            shared = True
50        elif tbdir.startswith(sbdir):
51            raise InvocationError('Target base directory "%s" cannot be a '
52                                  'subdirectory of source directory "%s"' %(
53                (tbdir, sbdir)))
54        elif sbdir.startswith(tbdir):
55            shared = True
56        else:
57            shared = False
58        self.shared_basedirs = shared
59        self.source.shared_basedirs = shared
60        self.target.shared_basedirs = shared
61
62        IGNORED_METADIRS = filter(None, [source_repo.METADIR,
63                                         target_repo.METADIR])
64        IGNORED_METADIRS.extend(source_repo.EXTRA_METADIRS)
65        IGNORED_METADIRS.extend(target_repo.EXTRA_METADIRS)
66
67        self.source.prepareSourceRepository()
68        self.target.prepareTargetRepository()
69
70        # UpdatableSourceWorkingDir
71
72        self.getPendingChangesets = self.source.getPendingChangesets
73        self.checkoutUpstreamRevision = self.source.checkoutUpstreamRevision
74
75        # SynchronizableTargetWorkingDir
76
77        self.prepareWorkingDirectory = self.target.prepareWorkingDirectory
78
79    def setStateFile(self, state_file):
80        """
81        Set the state file used to store the revision and pending changesets.
82        """
83
84        self.source.setStateFile(state_file)
85        self.target.setStateFile(state_file)
86
87    def setLogfile(self, logfile):
88        """
89        Set the name of the logfile, just to ignore it.
90        """
91
92        self.target.logfile = logfile
93
94    def applyPendingChangesets(self, applyable=None, replay=None, applied=None):
95        def pre_replay(changeset):
96            if applyable and not applyable(changeset):
97                return
98            return self.target._prepareToReplayChangeset(changeset)
99
100        return self.source.applyPendingChangesets(replay=self.replayChangeset,
101                                                  applyable=pre_replay,
102                                                  applied=applied)
103
104    def importFirstRevision(self, source_repo, changeset, initial):
105        if not self.shared_basedirs:
106            self._syncTargetWithSource()
107        self.target.importFirstRevision(source_repo, changeset, initial)
108
109    def replayChangeset(self, changeset):
110        if not self.shared_basedirs:
111            self._saveRenamedTargets(changeset)
112            self._syncTargetWithSource()
113        self.target.replayChangeset(changeset)
114
115    def _syncTargetWithSource(self):
116        cmd = ['rsync', '--archive']
117        now = datetime.now()
118        if hasattr(self, '_last_rsync'):
119            last = self._last_rsync
120            if not (now-last).seconds:
121                cmd.append('--ignore-times')
122        # Add per target specific flags
123        if self.target.repository.EXTRA_RSYNC_FLAGS:
124            cmd.extend(self.target.repository.EXTRA_RSYNC_FLAGS)
125        self._last_rsync = now
126        for md in IGNORED_METADIRS:
127            cmd.extend(['--exclude', md])
128
129        rsync = ExternalCommand(command=cmd)
130        rsync.execute(self.source.repository.basedir+'/', self.target.repository.basedir)
131
132    def _saveRenamedTargets(self, changeset):
133        """
134        Save old names from `rename`, before rsync replace it with new file.
135        """
136
137        from os.path import join, exists
138        from os import rename
139
140        for e in changeset.entries:
141            if e.action_kind == e.RENAMED:
142                absold = join(self.target.repository.basedir, e.old_name)
143                if exists(absold):
144                    rename(absold, absold + '-TAILOR-HACKED-OLD-NAME')
Note: See TracBrowser for help on using the repository browser.