Package pybaz :: Module _changeset
[frames] | no frames]

Source Code for Module pybaz._changeset

  1  # arch-tag: b8a786b6-51c4-42de-80b1-b6130ba39502 
  2  # Copyright (C) 2003 David Allouche <david@allouche.net> 
  3  # 
  4  #    This program is free software; you can redistribute it and/or modify 
  5  #    it under the terms of the GNU General Public License as published by 
  6  #    the Free Software Foundation; either version 2 of the License, or 
  7  #    (at your option) any later version. 
  8  # 
  9  #    This program is distributed in the hope that it will be useful, 
 10  #    but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 12  #    GNU General Public License for more details. 
 13  # 
 14  #    You should have received a copy of the GNU General Public License 
 15  #    along with this program; if not, write to the Free Software 
 16  #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 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   
48 -def backend():
49 import _builtin 50 return _builtin.backend
51
52 -def factory():
53 import _builtin 54 return _builtin.factory
55
56 -def _check_working_tree_param(param, name):
57 import _builtin 58 _builtin._check_working_tree_param(param, name)
59
60 -def _check_str_param(param, name):
61 import _builtin 62 _builtin._check_str_param(param, name)
63 64
65 -class Changeset(DirName):
66 67 """Arch whole-tree changeset.""" 68
69 - def __init__(self, name):
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
77 - def get_index(self, name, all=False):
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
103 - def _get_does_nothing(self):
104 """Is the changeset a no-op?""" 105 # TODO: checking that all indices are empty can yield false negatives 106 # rather we should check for the effective absence of changes. 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 # def get_metadata(self, name): 114 # """Load and parse a metadata file from the changeset. 115 # 116 # Expectable metadata tables are: 117 # modified-only-dir original-only-dir (more?) 118 # """ 119 # raise NotImplementedError, "not yet implemented" 120 # if name in self.__metadata: return self.__metadata[name] 121 # retval = [] 122 # fullname = self/(name + '-metadata') 123 # if os.path.exists(fullname): 124 # for line in open(fullname): 125 # pass # Not implemented 126 # self.__metadata[name] = retval 127 # return retval 128
129 - def __iter_mod_helper(self, what, all):
130 __pychecker__ = 'no-abstract' # for ImmutableSet 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
138 - def iter_mod_files(self, all=False):
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
143 - def iter_patched_files(self, all=False):
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 #return itertools.ifilter(lambda(f): os.path.isfile(patchdir/f[2]), 152 # self.iter_mod_files()) 153
154 - def patch_file(self, modname):
155 return self/'patches'/modname+'.patch'
156
157 - def __iter_renames_helper(self, what):
158 for id_, orig, mod in self.__iter_mod_helper(what, all=False): 159 if orig != mod: 160 yield (id_, orig, mod)
161
162 - def iter_renames(self):
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
172 - def __iter_created_helper(self, what, removed=False, all=False):
173 __pychecker__ = 'no-abstract' # for ImmutableSet 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
182 - def iter_created_files(self, all=False):
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
190 - def created_file(self, name):
191 return self/'new-files-archive'/name
192
193 - def iter_removed_files(self, all=False):
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
201 - def removed_file(self, name):
202 return self/'removed-files-archive'/name
203
204 - def iter_created_dirs(self, all=False):
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
212 - def iter_removed_dirs(self, all=False):
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
220 - def iter_apply(self, tree, reverse=False):
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
253 -class _Changeset_Baz_1_0(object):
254
255 - def apply_changeset_args(cset, tree, reverse):
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
265 -class ChangesetApplication(object):
266 """Incremental changeset application process.""" 267
268 - def __init__(self, proc):
269 """For internal use only.""" 270 self._iter_process = proc
271
272 - def __iter__(self):
273 """Return an iterator of `MergeOutcome`""" 274 return classify_changeset_application(self._iter_process)
275
276 - def _get_conflicting(self):
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
283 - def _get_finished(self):
284 "Is the changeset application complete?" 285 return self._iter_process.finished
286 finished = property(_get_finished)
287 288
289 -class ChangesetCreation(object):
290 """Incremental changeset generation process.""" 291
292 - def __init__(self, proc, dest):
293 """For internal use only.""" 294 self._iter_process = proc 295 self._dest = dest 296 self._changeset = None
297
298 - def __iter__(self):
299 """Return an iterator of `TreeChange`""" 300 return classify_changeset_creation(self._iter_process)
301
302 - def _get_changeset(self):
303 """Generated changeset.""" 304 if self._changeset is None: 305 if not self.finished: 306 raise AttributeError("Changeset generation is not complete.") 307 self._changeset = Changeset(self._dest) 308 return self._changeset
309 changeset = property(_get_changeset) 310
311 - def _get_finished(self):
312 "Is the changeset creation complete?" 313 return self._iter_process.finished
314 finished = property(_get_finished)
315 316
317 -def iter_delta(orig, mod, dest):
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
336 -def _tree_or_existing_revision_param(param, name):
337 if factory().isRevision(param): 338 # should check for actual existence, but that is expensive 339 if not param.archive.is_registered(): 340 raise errors.ArchiveNotRegistered(param.archive) 341 return param.fullname 342 elif factory().isSourceTree(param): 343 return param 344 else: 345 raise TypeError("Parameter \"%s\" must be SourceTree or Revision" 346 " but was: %r" % (name, param))
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