1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 """Internal module providing changeset handling.
19
20 This module implements some of public interface for the
21 pybaz_ package. But for convenience reasons the author prefers
22 to store this code in a file separate from ``__init__.py``.
23
24 .. _pybaz: pybaz-module.html
25
26 This module is strictly internal and should never be used.
27 """
28
29 import os
30 import re
31 from sets import ImmutableSet
32
33 import errors
34 from pathname import DirName
35 from _escaping import name_unescape
36 from _output import classify_changeset_application
37 from _output import classify_changeset_creation
38
39 __all__ = [
40 'Changeset',
41 'ChangesetApplication',
42 'ChangesetCreation',
43 'delta',
44 'iter_delta',
45 ]
46
47
51
55
59
63
64
66
67 """Arch whole-tree changeset."""
68
70 DirName.__init__(self, name)
71 self.__index = {}
72 self.__index_excl = {}
73 self.__metadata = {}
74 self.exclude_re = re.compile('^[AE]_')
75 self._impl = _Changeset_Baz_1_0
76
78 """Load and parse an index file from the changeset.
79
80 Expectable indexes are:
81 mod-dirs mod-files orig-dirs orig-files (more?)
82 """
83 key = (name, all)
84 if name in self.__index: return self.__index[key]
85 if all:
86 not_exclude = lambda(id_): True
87 else:
88 not_exclude = lambda(id_): not self.exclude_re.match(id_)
89 retval = {}
90 fullname = self/(name + '-index')
91 if os.path.exists(fullname):
92 index_file = open(fullname)
93 try:
94 for line in index_file:
95 n, id_ = map(lambda(s): s.strip(), line.split())
96 if not_exclude(id_):
97 retval[id_] = os.path.normpath(name_unescape(n))
98 finally:
99 index_file.close()
100 self.__index[key] = retval
101 return retval
102
104 """Is the changeset a no-op?"""
105
106
107 for index_name in ('mod-files', 'orig-files', 'mod-dirs', 'orig-dirs'):
108 index = self.get_index(index_name, True)
109 if index: return False
110 return True
111 does_nothing = property(_get_does_nothing)
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
130 __pychecker__ = 'no-abstract'
131 orig_index = self.get_index('orig-' + what, all)
132 mod_index = self.get_index('mod-' + what, all)
133 orig_ids = ImmutableSet(orig_index.iterkeys())
134 mod_ids = ImmutableSet(mod_index.iterkeys())
135 for id_ in orig_ids & mod_ids:
136 yield (id_, orig_index[id_], mod_index[id_])
137
139 """Iterator over (id, orig, mod) tuples for files which are are
140 patched, renamed, or have their permissions changed."""
141 return self.__iter_mod_helper('files', all)
142
144 """Iterate over (id, orig, mod) of patched files."""
145 patchdir = self/'patches'
146 for f in self.iter_mod_files(all):
147 if os.path.isfile(patchdir/f[1]+'.patch'):
148 yield f
149 else:
150 print "file does not exists: %s" % str(patchdir/f[1]+'.patch')
151
152
153
155 return self/'patches'/modname+'.patch'
156
158 for id_, orig, mod in self.__iter_mod_helper(what, all=False):
159 if orig != mod:
160 yield (id_, orig, mod)
161
163 """Iterate over (id, orig, dest) triples representing renames.
164
165 id is the persistant file tag, and the key of the dictionnary.
166 orig is the name in the original tree.
167 dest is the name in the modified tree.
168 """
169 from itertools import chain
170 return chain(*map(self.__iter_renames_helper, ('files', 'dirs')))
171
173 __pychecker__ = 'no-abstract'
174 orig_index = self.get_index('orig-' + what, all)
175 mod_index = self.get_index('mod-' + what, all)
176 if removed: orig_index, mod_index = mod_index, orig_index
177 orig_ids = ImmutableSet(orig_index.iterkeys())
178 mod_ids = ImmutableSet(mod_index.iterkeys())
179 for id_ in mod_ids - orig_ids:
180 yield (id_, mod_index[id_])
181
183 """Iterator over tuples (id, dest) for created files.
184
185 :param all: include Arch control files.
186 :type all: bool
187 """
188 return self.__iter_created_helper('files', all=all)
189
191 return self/'new-files-archive'/name
192
194 """Iterator over tuples (id, orig) for removed files.
195
196 :param all: include Arch control files.
197 :type all: bool
198 """
199 return self.__iter_created_helper('files', removed=True, all=all)
200
202 return self/'removed-files-archive'/name
203
205 """Iterator over tuples (id, dest) for created directories.
206
207 :param all: include Arch control files.
208 :type all: bool
209 """
210 return self.__iter_created_helper('dirs', all=all)
211
213 """Iterator over tuples (id, orig) for removed directories.
214
215 :param all: include Arch control files.
216 :type all: bool
217 """
218 return self.__iter_created_helper('dirs', removed=True, all=all)
219
221 """Apply this changeset to a tree, with incremental output.
222
223 :param tree: the tree to apply changes to.
224 :type tree: `WorkingTree`
225 :param reverse: invert the meaning of the changeset; adds
226 become deletes, etc.
227 :type reverse: bool
228 :rtype: `ChangesetApplication`
229 """
230 _check_working_tree_param(tree, 'tree')
231 args = self._impl.apply_changeset_args(self, tree, reverse)
232 proc = backend().sequence_cmd(args, expected=(0,1))
233 return ChangesetApplication(proc)
234
235 - def apply(self, tree, reverse=False):
236 """Apply this changeset to a tree. Raise on conflict.
237
238 :param tree: the tree to apply changes to.
239 :type tree: `WorkingTree`
240 :param reverse: invert the meaning of the changeset; adds
241 become deletes, etc.
242 :type reverse: bool
243 :raise errors.ChangesetConflict: a conflict occured while applying the
244 changeset.
245 """
246 _check_working_tree_param(tree, 'tree')
247 args = self._impl.apply_changeset_args(self, tree, reverse)
248 status = backend().status_cmd(args, expected=(0,1))
249 if status == 1:
250 raise errors.ChangesetConflict(tree, self)
251
252
254
256 args = ['apply-changeset']
257 if reverse:
258 args.append('--reverse')
259 args.extend((str(cset), str(tree)))
260 return args
261
262 apply_changeset_args = staticmethod(apply_changeset_args)
263
264
266 """Incremental changeset application process."""
267
269 """For internal use only."""
270 self._iter_process = proc
271
275
277 "Did conflicts occur during changeset application?"
278 if not self.finished:
279 raise AttributeError("Changeset application is not complete.")
280 return self._iter_process.status == 1
281 conflicting = property(_get_conflicting)
282
284 "Is the changeset application complete?"
285 return self._iter_process.finished
286 finished = property(_get_finished)
287
288
290 """Incremental changeset generation process."""
291
293 """For internal use only."""
294 self._iter_process = proc
295 self._dest = dest
296 self._changeset = None
297
301
309 changeset = property(_get_changeset)
310
312 "Is the changeset creation complete?"
313 return self._iter_process.finished
314 finished = property(_get_finished)
315
316
318 """Compute a whole-tree changeset with incremental output.
319
320 :param orig: old revision or directory.
321 :type orig: `Revision`, `ArchSourceTree`
322 :param mod: new revision or directory,
323 :type mod: `Revision`, `ArchSourceTree`
324 :param dest: path of the changeset to create.
325 :type dest: str
326 :rtype: `ChangesetCreation`
327 """
328 orig = _tree_or_existing_revision_param(orig, 'orig')
329 mod = _tree_or_existing_revision_param(mod, 'mod')
330 _check_str_param(dest, 'dest')
331 args = ['delta', orig, mod, dest]
332 proc = backend().sequence_cmd(args)
333 return ChangesetCreation(proc, dest)
334
335
347
348 -def delta(orig, mod, dest):
349 """Compute a whole-tree changeset.
350
351 Create the output directory ``dest`` (it must not already exist).
352
353 Compare the source trees ``orig`` and ``mod`` (which may be source
354 arch source tree or revisions). Create a changeset in ``dest``.
355
356 :param orig: the old revision or directory.
357 :type orig: `Revision`, `ArchSourceTree`
358 :param mod: the new revision or directory.
359 :type mod: `Revision`, `ArchSourceTree`
360 :param dest: path of the changeset to create.
361 :type dest: str
362 :return: changeset from ``orig`` to ``mod``.
363 :rtype: `Changeset`
364 """
365 __pychecker__ = 'unusednames=line'
366 iter_ = iter_delta(orig, mod, dest)
367 for line in iter_: pass
368 return iter_.changeset
369