source: tailor/vcpx/source.py @ 505

Revision 505, 7.2 KB checked in by lele@…, 8 years ago (diff)

Added a new hook executed after application and before final commit

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                               replayable=None, replay=None, applied=None,
71                               logger=None, 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 not self._didApplyChangeset(root, c, replayable):
119                continue
120
121            if replay:
122                replay(root, module, c, delayed_commit=delayed_commit,
123                       logger=logger)
124
125            remaining.remove(c)
126            self.state_file.write(c.revision, remaining)
127
128            if applied:
129                applied(root, c)
130
131            last = c
132
133        self.pending = remaining
134        return last, conflicts
135
136    def _willApplyChangeset(self, root, changeset, applyable=None):
137        """
138        This gets called just before applying each changeset.  The whole
139        process will be stopped if this returns False.
140
141        Subclasses may use this to stop the process on some conditions,
142        or to do whatever before application.
143        """
144
145        if applyable:
146            return applyable(root, changeset)
147        else:
148            return True
149
150    def _didApplyChangeset(self, root, changeset, replayable=None):
151        """
152        This gets called right after changeset application.  The final
153        commit on the target system won't be carried out if this
154        returns False.
155
156        Subclasses may use this to alter the changeset in any way, before
157        committing its changes to the target system.
158        """
159
160        if replayable:
161            return replayable(root, changeset)
162        else:
163            return True
164
165    def getPendingChangesets(self,  root, repository, module):
166        """
167        Load the pending changesets from the state file, or query the
168        upstream repository if there's none.
169        """
170
171        revision, self.pending = self.state_file.load()
172        if not self.pending:
173            self.pending = self._getUpstreamChangesets(root, repository, module,
174                                                       revision)
175        return self.pending
176
177    def _getUpstreamChangesets(self, root, repository, module, sincerev):
178        """
179        Query the upstream repository about what happened on the
180        sources since last sync, returning a sequence of Changesets
181        instances.
182
183        This method must be overridden by subclasses.
184        """
185
186        raise "%s should override this method" % self.__class__
187
188    def _applyChangeset(self, root, changeset, logger=None):
189        """
190        Do the actual work of applying the changeset to the working copy.
191
192        Subclasses should reimplement this method performing the
193        necessary steps to *merge* given `changeset`, returning a list
194        with the conflicts, if any.
195        """
196
197        raise "%s should override this method" % self.__class__
198
199    def checkoutUpstreamRevision(self, root, repository, module, revision,
200                                 **kwargs):
201        """
202        Extract a working copy from a repository.
203
204        :root: the name of the directory (that **must** exists)
205               that will contain the working copy of the sources under the
206               *module* subdirectory
207
208        :repository: the address of the repository (the format depends on
209                     the actual method used by the subclass)
210
211        :module: the name of the module to extract
212
213        :revision: extract that revision/branch
214
215        Return the last applied changeset.
216        """
217
218        if not root:
219            raise InvocationError("Must specify a root directory")
220        if not repository:
221            raise InvocationError("Must specify an upstream repository")
222
223        last = self._checkoutUpstreamRevision(root, repository,
224                                              module, revision,
225                                              **kwargs)
226        self.state_file.write(last.revision, None)
227
228        return last
229
230    def _checkoutUpstreamRevision(self, basedir, repository, module, revision,
231                                  subdir=None, logger=None, **kwargs):
232        """
233        Concretely do the checkout of the upstream revision.
234        """
235
236        raise "%s should override this method" % self.__class__
Note: See TracBrowser for help on using the repository browser.