source: tailor/vcpx/source.py @ 561

Revision 561, 5.7 KB checked in by lele@…, 8 years ago (diff)

Accept a sincerev argument for getPendingChangesets()

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
25Either abort the session with Ctrl-C, or manually correct the situation
26with a Ctrl-Z and a few "svn resolved". What would you like to do?
27"""
28
29class GetUpstreamChangesetsFailure(Exception):
30    "Failure getting upstream changes"
31
32class ChangesetApplicationFailure(Exception):
33    "Failure applying upstream changes"
34
35class InvocationError(Exception):
36    "Bad invocation, use --help for details"
37
38class UpdatableSourceWorkingDir(WorkingDir):
39    """
40    This is an abstract working dir able to follow an upstream
41    source of ``changesets``.
42
43    It has three main functionalities:
44
45    getPendingChangesets
46        to query the upstream server about new changesets
47
48    applyPendingChangesets
49        to apply them to the working directory
50
51    checkoutUpstreamRevision
52        to extract a new copy of the sources, actually initializing
53        the mechanism.
54
55    Subclasses MUST override at least the _underscoredMethods.
56    """
57
58    def applyPendingChangesets(self, applyable=None, replayable=None,
59                               replay=None, applied=None):
60        """
61        Apply the collected upstream changes.
62
63        Loop over the collected changesets, doing whatever is needed
64        to apply each one to the working dir and if the changes do
65        not raise conflicts call the `replay` function to mirror the
66        changes on the target.
67
68        Return a tuple of two elements:
69
70        - the last applied changeset, if any
71        - the sequence (potentially empty!) of conflicts.
72        """
73
74        c = None
75        last = None
76        conflicts = []
77
78        if not self.pending:
79            return last, conflicts
80
81        remaining = self.pending[:]
82        for c in self.pending:
83            if not self._willApplyChangeset(c, applyable):
84                break
85
86            self.log_info("Applying changeset %s" % c.revision)
87
88            try:
89                res = self._applyChangeset(c)
90            except:
91                self.log_error("Couldn't apply changeset %s" % c.revision,
92                               exc=True)
93                raise
94
95            if res:
96                conflicts.append((c, res))
97                try:
98                    raw_input(CONFLICTS_PROMPT % (str(c), '\n * '.join(res)))
99                except KeyboardInterrupt:
100                    self.log_info("INTERRUPTED BY THE USER!")
101                    return last, conflicts
102
103            if not self._didApplyChangeset(c, replayable):
104                continue
105
106            if replay:
107                replay(c)
108
109            remaining.remove(c)
110            self.state_file.write(c.revision, remaining)
111
112            if applied:
113                applied(c)
114
115            last = c
116
117        self.pending = remaining
118        return last, conflicts
119
120    def _willApplyChangeset(self, changeset, applyable=None):
121        """
122        This gets called just before applying each changeset.  The whole
123        process will be stopped if this returns False.
124
125        Subclasses may use this to stop the process on some conditions,
126        or to do whatever before application.
127        """
128
129        if applyable:
130            return applyable(changeset)
131        else:
132            return True
133
134    def _didApplyChangeset(self, changeset, replayable=None):
135        """
136        This gets called right after changeset application.  The final
137        commit on the target system won't be carried out if this
138        returns False.
139
140        Subclasses may use this to alter the changeset in any way, before
141        committing its changes to the target system.
142        """
143
144        if replayable:
145            return replayable(changeset)
146        else:
147            return True
148
149    def getPendingChangesets(self, sincerev=None):
150        """
151        Load the pending changesets from the state file, or query the
152        upstream repository if there's none.
153        """
154
155        revision, self.pending = self.state_file.load()
156        if not self.pending:
157            self.pending = self._getUpstreamChangesets(revision or sincerev)
158        return self.pending
159
160    def _getUpstreamChangesets(self, sincerev):
161        """
162        Query the upstream repository about what happened on the
163        sources since last sync, returning a sequence of Changesets
164        instances.
165
166        This method must be overridden by subclasses.
167        """
168
169        raise "%s should override this method" % self.__class__
170
171    def _applyChangeset(self, changeset):
172        """
173        Do the actual work of applying the changeset to the working copy.
174
175        Subclasses should reimplement this method performing the
176        necessary steps to *merge* given `changeset`, returning a list
177        with the conflicts, if any.
178        """
179
180        raise "%s should override this method" % self.__class__
181
182    def checkoutUpstreamRevision(self, revision):
183        """
184        Extract a working copy of the given revision from a repository.
185
186        Return the last applied changeset.
187        """
188
189        last = self._checkoutUpstreamRevision(revision)
190        self.state_file.write(last.revision, None)
191
192        return last
193
194    def _checkoutUpstreamRevision(self, revision):
195        """
196        Concretely do the checkout of the upstream revision.
197        """
198
199        raise "%s should override this method" % self.__class__
Note: See TracBrowser for help on using the repository browser.