source: tailor/vcpx/source.py @ 579

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

Really don't fail at bootstrap if whole history is missing

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
15from workdir import WorkingDir
16
17CONFLICTS_PROMPT = """
18The changeset
19
20%s
21caused conflicts on the following files:
22
23 * %s
24
25This is quite unusual, and most probably it means someone else has
26changed the working dir, beyond tailor control, or maybe a tailor bug
27is showing up.
28
29Either abort the session with Ctrl-C, or manually correct the situation
30with a Ctrl-Z, explore and correct, and coming back from the shell with
31'fg'.
32
33What would you like to do?
34"""
35
36class GetUpstreamChangesetsFailure(Exception):
37    "Failure getting upstream changes"
38
39class ChangesetApplicationFailure(Exception):
40    "Failure applying upstream changes"
41
42class InvocationError(Exception):
43    "Bad invocation, use --help for details"
44
45class UpdatableSourceWorkingDir(WorkingDir):
46    """
47    This is an abstract working dir able to follow an upstream
48    source of ``changesets``.
49
50    It has three main functionalities:
51
52    getPendingChangesets
53        to query the upstream server about new changesets
54
55    applyPendingChangesets
56        to apply them to the working directory
57
58    checkoutUpstreamRevision
59        to extract a new copy of the sources, actually initializing
60        the mechanism.
61
62    Subclasses MUST override at least the _underscoredMethods.
63    """
64
65    def applyPendingChangesets(self, applyable=None, replayable=None,
66                               replay=None, applied=None):
67        """
68        Apply the collected upstream changes.
69
70        Loop over the collected changesets, doing whatever is needed
71        to apply each one to the working dir and if the changes do
72        not raise conflicts call the `replay` function to mirror the
73        changes on the target.
74
75        Return a tuple of two elements:
76
77        - the last applied changeset, if any
78        - the sequence (potentially empty!) of conflicts.
79        """
80
81        c = None
82        last = None
83        conflicts = []
84
85        if not self.pending:
86            return last, conflicts
87
88        try:
89            while self.pending:
90                c = self.pending[0]
91
92                # Give the opportunity to subclasses to stop the application
93                # of the queue, before the application of the patch by the
94                # source backend.
95                if not self._willApplyChangeset(c, applyable):
96                    break
97
98                self.log_info("Applying changeset %s" % c.revision)
99
100                try:
101                    res = self._applyChangeset(c)
102                except:
103                    self.log_error("Couldn't apply changeset %s" % c.revision,
104                                   exc=True)
105                    raise
106
107                if res:
108                    # Uh, we have a conflict: this should not ever
109                    # happen, but the user may have manually tweaked
110                    # the working directory. Give her a chance of
111                    # fixing the situation, or abort with Ctrl-C, or
112                    # whatever the subclasses decide.
113                    try:
114                        self._handleConflict(c, conflicts, res)
115                    except KeyboardInterrupt:
116                        self.log_info("INTERRUPTED BY THE USER!")
117                        break
118
119                # Give the opportunity to subclasses to skip the commit on
120                # the target backend.
121                if self._didApplyChangeset(c, replayable):
122                    if replay:
123                        replay(c)
124
125                # Remove it from the queue and remember it for the finally
126                # clause
127                last = self.pending.pop(0)
128
129                # Another hook (last==c here)
130                if applied:
131                    applied(last)
132        finally:
133            # For whatever reason we exit the loop, save the last state
134            if last is not None:
135                self.state_file.write(last.revision, self.pending)
136
137        return last, conflicts
138
139    def _willApplyChangeset(self, changeset, applyable=None):
140        """
141        This gets called just before applying each changeset.  The whole
142        process will be stopped if this returns False.
143
144        Subclasses may use this to stop the process on some conditions,
145        or to do whatever before application.
146        """
147
148        if applyable:
149            return applyable(changeset)
150        else:
151            return True
152
153    def _didApplyChangeset(self, changeset, replayable=None):
154        """
155        This gets called right after changeset application.  The final
156        commit on the target system won't be carried out if this
157        returns False.
158
159        Subclasses may use this to alter the changeset in any way, before
160        committing its changes to the target system.
161        """
162
163        if replayable:
164            return replayable(changeset)
165        else:
166            return True
167
168    def _handleConflict(self, changeset, conflicts, conflict):
169        """
170        Handle the conflict raised by the application of the upstream changeset.
171
172        This implementation just append a (changeset, conflict) to the
173        list of all conflicts, and present a prompt to the user that
174        may abort with Ctrl-C (that in turn generates a KeyboardInterrupt).
175        """
176
177        conflicts.append((changeset, conflict))
178        raw_input(CONFLICTS_PROMPT % (str(changeset), '\n * '.join(conflict)))
179
180    def getPendingChangesets(self, sincerev=None):
181        """
182        Load the pending changesets from the state file, or query the
183        upstream repository if there's none.
184        """
185
186        revision, self.pending = self.state_file.load()
187        if not self.pending:
188            self.pending = self._getUpstreamChangesets(revision or sincerev)
189        return self.pending
190
191    def _getUpstreamChangesets(self, sincerev):
192        """
193        Query the upstream repository about what happened on the
194        sources since last sync, returning a sequence of Changesets
195        instances.
196
197        This method must be overridden by subclasses.
198        """
199
200        raise "%s should override this method" % self.__class__
201
202    def _applyChangeset(self, changeset):
203        """
204        Do the actual work of applying the changeset to the working copy.
205
206        Subclasses should reimplement this method performing the
207        necessary steps to *merge* given `changeset`, returning a list
208        with the conflicts, if any.
209        """
210
211        raise "%s should override this method" % self.__class__
212
213    def checkoutUpstreamRevision(self, revision):
214        """
215        Extract a working copy of the given revision from a repository.
216
217        Return the last applied changeset.
218        """
219
220        last = self._checkoutUpstreamRevision(revision)
221        self.state_file.write(last.revision, getattr(self, 'pending', None))
222        return last
223
224    def _checkoutUpstreamRevision(self, revision):
225        """
226        Concretely do the checkout of the upstream revision.
227        """
228
229        raise "%s should override this method" % self.__class__
Note: See TracBrowser for help on using the repository browser.