source: tailor/vcpx/source.py @ 504

Revision 504, 6.6 KB checked in by lele@…, 8 years ago (diff)

If the applied hooks return False, stop the process

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Updatable VC working directory
3# :Creato:   mer 09 giu 2004 13:55:35 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9Updatable sources are the simplest abstract wrappers around a working
10directory under some kind of version control system.
11"""
12
13__docformat__ = 'reStructuredText'
14
15CONFLICTS_PROMPT = """
16The changeset
17
18%s
19caused conflicts on the following files:
20
21 * %s
22
23Either abort the session with Ctrl-C, or manually correct the situation
24with a Ctrl-Z and a few "svn resolved". What would you like to do?
25"""
26
27class GetUpstreamChangesetsFailure(Exception):
28    "Failure getting upstream changes"
29
30    pass
31
32class ChangesetApplicationFailure(Exception):
33    "Failure applying upstream changes"
34
35    pass
36
37class InvocationError(Exception):
38    "Bad invocation, use --help for details"
39
40    pass
41
42class UpdatableSourceWorkingDir(object):
43    """
44    This is an abstract working dir able to follow an upstream
45    source of ``changesets``.
46
47    It has three main functionalities:
48
49    getPendingChangesets
50        to query the upstream server about new changesets
51
52    applyPendingChangesets
53        to apply them to the working directory
54
55    checkoutUpstreamRevision
56        to extract a new copy of the sources, actually initializing
57        the mechanism.
58
59    Subclasses MUST override at least the _underscoredMethods.
60    """
61
62    def setStateFile(self, state_file):
63        """
64        Set the state file used to store the revision and pending changesets.
65        """
66
67        self.state_file = state_file
68
69    def applyPendingChangesets(self, root, module, applyable=None,
70                               replay=None, applied=None, logger=None,
71                               delayed_commit=False):
72        """
73        Apply the collected upstream changes.
74
75        Loop over the collected changesets, doing whatever is needed
76        to apply each one to the working dir and if the changes do
77        not raise conflicts call the `replay` function to mirror the
78        changes on the target.
79
80        Return a tuple of two elements:
81
82        - the last applied changeset, if any
83        - the sequence (potentially empty!) of conflicts.
84        """
85
86        c = None
87        last = None
88        conflicts = []
89
90        if not self.pending:
91            return last, conflicts
92
93        remaining = self.pending[:]
94        for c in self.pending:
95            if not self._willApplyChangeset(root, c, applyable):
96                break
97
98            if logger:
99                logger.info("Applying changeset %s", c.revision)
100
101            try:
102                res = self._applyChangeset(root, c, logger=logger)
103            except:
104                if logger:
105                    logger.critical("Couldn't apply changeset %s",
106                                    c.revision, exc_info=True)
107                    logger.debug(str(c))
108                raise
109
110            if res:
111                conflicts.append((c, res))
112                try:
113                    raw_input(CONFLICTS_PROMPT % (str(c), '\n * '.join(res)))
114                except KeyboardInterrupt:
115                    if logger: logger.info("INTERRUPTED BY THE USER!")
116                    return last, conflicts
117
118            if replay:
119                replay(root, module, c, delayed_commit=delayed_commit,
120                       logger=logger)
121
122            remaining.remove(c)
123            self.state_file.write(c.revision, remaining)
124
125            if applied:
126                applied(root, c)
127
128            last = c
129
130        self.pending = remaining
131        return last, conflicts
132
133    def _willApplyChangeset(self, root, changeset, applyable=None):
134        """
135        This gets called just before applying each changeset.  The whole
136        process will be stopped if this returns False.
137
138        Subclasses may use this to stop the process on some conditions,
139        or to do whatever before application.
140        """
141
142        if applyable:
143            return applyable(root, changeset)
144        else:
145            return True
146
147    def getPendingChangesets(self,  root, repository, module):
148        """
149        Load the pending changesets from the state file, or query the
150        upstream repository if there's none.
151        """
152
153        revision, self.pending = self.state_file.load()
154        if not self.pending:
155            self.pending = self._getUpstreamChangesets(root, repository, module,
156                                                       revision)
157        return self.pending
158
159    def _getUpstreamChangesets(self, root, repository, module, sincerev):
160        """
161        Query the upstream repository about what happened on the
162        sources since last sync, returning a sequence of Changesets
163        instances.
164
165        This method must be overridden by subclasses.
166        """
167
168        raise "%s should override this method" % self.__class__
169
170    def _applyChangeset(self, root, changeset, logger=None):
171        """
172        Do the actual work of applying the changeset to the working copy.
173
174        Subclasses should reimplement this method performing the
175        necessary steps to *merge* given `changeset`, returning a list
176        with the conflicts, if any.
177        """
178
179        raise "%s should override this method" % self.__class__
180
181    def checkoutUpstreamRevision(self, root, repository, module, revision,
182                                 **kwargs):
183        """
184        Extract a working copy from a repository.
185
186        :root: the name of the directory (that **must** exists)
187               that will contain the working copy of the sources under the
188               *module* subdirectory
189
190        :repository: the address of the repository (the format depends on
191                     the actual method used by the subclass)
192
193        :module: the name of the module to extract
194
195        :revision: extract that revision/branch
196
197        Return the last applied changeset.
198        """
199
200        if not root:
201            raise InvocationError("Must specify a root directory")
202        if not repository:
203            raise InvocationError("Must specify an upstream repository")
204
205        last = self._checkoutUpstreamRevision(root, repository,
206                                              module, revision,
207                                              **kwargs)
208        self.state_file.write(last.revision, None)
209
210        return last
211
212    def _checkoutUpstreamRevision(self, basedir, repository, module, revision,
213                                  subdir=None, logger=None, **kwargs):
214        """
215        Concretely do the checkout of the upstream revision.
216        """
217
218        raise "%s should override this method" % self.__class__
Note: See TracBrowser for help on using the repository browser.