source: tailor/vcpx/source.py @ 573

Revision 573, 5.9 KB checked in by lele@…, 8 years ago (diff)

Emit a a better and more generic prompt to warn the user about conflict

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        remaining = self.pending[:]
89        for c in self.pending:
90            if not self._willApplyChangeset(c, applyable):
91                break
92
93            self.log_info("Applying changeset %s" % c.revision)
94
95            try:
96                res = self._applyChangeset(c)
97            except:
98                self.log_error("Couldn't apply changeset %s" % c.revision,
99                               exc=True)
100                raise
101
102            if res:
103                conflicts.append((c, res))
104                try:
105                    raw_input(CONFLICTS_PROMPT % (str(c), '\n * '.join(res)))
106                except KeyboardInterrupt:
107                    self.log_info("INTERRUPTED BY THE USER!")
108                    return last, conflicts
109
110            if not self._didApplyChangeset(c, replayable):
111                continue
112
113            if replay:
114                replay(c)
115
116            remaining.remove(c)
117            self.state_file.write(c.revision, remaining)
118
119            if applied:
120                applied(c)
121
122            last = c
123
124        self.pending = remaining
125        return last, conflicts
126
127    def _willApplyChangeset(self, changeset, applyable=None):
128        """
129        This gets called just before applying each changeset.  The whole
130        process will be stopped if this returns False.
131
132        Subclasses may use this to stop the process on some conditions,
133        or to do whatever before application.
134        """
135
136        if applyable:
137            return applyable(changeset)
138        else:
139            return True
140
141    def _didApplyChangeset(self, changeset, replayable=None):
142        """
143        This gets called right after changeset application.  The final
144        commit on the target system won't be carried out if this
145        returns False.
146
147        Subclasses may use this to alter the changeset in any way, before
148        committing its changes to the target system.
149        """
150
151        if replayable:
152            return replayable(changeset)
153        else:
154            return True
155
156    def getPendingChangesets(self, sincerev=None):
157        """
158        Load the pending changesets from the state file, or query the
159        upstream repository if there's none.
160        """
161
162        revision, self.pending = self.state_file.load()
163        if not self.pending:
164            self.pending = self._getUpstreamChangesets(revision or sincerev)
165        return self.pending
166
167    def _getUpstreamChangesets(self, sincerev):
168        """
169        Query the upstream repository about what happened on the
170        sources since last sync, returning a sequence of Changesets
171        instances.
172
173        This method must be overridden by subclasses.
174        """
175
176        raise "%s should override this method" % self.__class__
177
178    def _applyChangeset(self, changeset):
179        """
180        Do the actual work of applying the changeset to the working copy.
181
182        Subclasses should reimplement this method performing the
183        necessary steps to *merge* given `changeset`, returning a list
184        with the conflicts, if any.
185        """
186
187        raise "%s should override this method" % self.__class__
188
189    def checkoutUpstreamRevision(self, revision):
190        """
191        Extract a working copy of the given revision from a repository.
192
193        Return the last applied changeset.
194        """
195
196        last = self._checkoutUpstreamRevision(revision)
197        self.state_file.write(last.revision, self.pending)
198        return last
199
200    def _checkoutUpstreamRevision(self, revision):
201        """
202        Concretely do the checkout of the upstream revision.
203        """
204
205        raise "%s should override this method" % self.__class__
Note: See TracBrowser for help on using the repository browser.