122
131
if not conflicted:
123
132
raise NotConflicted(filename)
135
class ConflictList(object):
137
Can be instantiated from stanzas or from Conflict subclasses.
140
def __init__(self, conflicts=None):
141
object.__init__(self)
142
if conflicts is None:
145
self.__list = conflicts
148
return len(self.__list)
151
return iter(self.__list)
153
def __getitem__(self, key):
154
return self.__list[key]
156
def append(self, conflict):
157
return self.__list.append(conflict)
159
def __eq__(self, other_list):
160
return list(self) == list(other_list)
162
def __ne__(self, other_list):
163
return not (self == other_list)
166
return "ConflictList(%r)" % self.__list
169
def from_stanzas(stanzas):
170
"""Produce a new ConflictList from an iterable of stanzas"""
171
conflicts = ConflictList()
172
for stanza in stanzas:
173
conflicts.append(Conflict.factory(**stanza.as_dict()))
176
def to_stanzas(self):
177
"""Generator of stanzas"""
178
for conflict in self:
179
yield conflict.as_stanza()
181
def to_strings(self):
182
"""Generate strings for the provided conflicts"""
183
for conflict in self:
186
def remove_files(self, tree):
187
"""Remove the THIS, BASE and OTHER files for listed conflicts"""
188
for conflict in self:
189
if not conflict.has_files:
191
for suffix in CONFLICT_SUFFIXES:
193
os.unlink(tree.abspath(conflict.path+suffix))
195
if e.errno != errno.ENOENT:
198
def select_conflicts(self, tree, paths, ignore_misses=False):
199
"""Select the conflicts associated with paths in a tree.
201
File-ids are also used for this.
203
path_set = set(paths)
205
selected_paths = set()
206
new_conflicts = ConflictList()
207
selected_conflicts = ConflictList()
209
file_id = tree.path2id(path)
210
if file_id is not None:
213
for conflict in self:
215
for key in ('path', 'conflict_path'):
216
cpath = getattr(conflict, key, None)
219
if cpath in path_set:
221
selected_paths.add(cpath)
222
for key in ('file_id', 'conflict_file_id'):
223
cfile_id = getattr(conflict, key, None)
227
cpath = ids[cfile_id]
231
selected_paths.add(cpath)
233
selected_conflicts.append(conflict)
235
new_conflicts.append(conflict)
236
if ignore_misses is not True:
237
for path in [p for p in paths if p not in selected_paths]:
238
if not os.path.exists(tree.abspath(path)):
239
print "%s does not exist" % path
241
print "%s is not conflicted" % path
242
return new_conflicts, selected_conflicts
245
class Conflict(object):
246
"""Base class for all types of conflict"""
250
def __init__(self, path, file_id=None):
252
self.file_id = file_id
255
s = Stanza(type=self.typestring, path=self.path)
256
if self.file_id is not None:
257
s.add('file_id', self.file_id)
261
return [type(self), self.path, self.file_id]
263
def __cmp__(self, other):
264
if getattr(other, "_cmp_list", None) is None:
266
return cmp(self._cmp_list(), other._cmp_list())
268
def __eq__(self, other):
269
return self.__cmp__(other) == 0
271
def __ne__(self, other):
272
return not self.__eq__(other)
275
return self.format % self.__dict__
278
rdict = dict(self.__dict__)
279
rdict['class'] = self.__class__.__name__
280
return self.rformat % rdict
283
def factory(type, **kwargs):
285
return ctype[type](**kwargs)
288
class PathConflict(Conflict):
289
"""A conflict was encountered merging file paths"""
291
typestring = 'path conflict'
293
format = 'Path conflict: %(path)s / %(conflict_path)s'
295
rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
296
def __init__(self, path, conflict_path=None, file_id=None):
297
Conflict.__init__(self, path, file_id)
298
self.conflict_path = conflict_path
301
s = Conflict.as_stanza(self)
302
if self.conflict_path is not None:
303
s.add('conflict_path', self.conflict_path)
307
class ContentsConflict(PathConflict):
308
"""The files are of different types, or not present"""
312
typestring = 'contents conflict'
314
format = 'Contents conflict in %(path)s'
317
class TextConflict(PathConflict):
318
"""The merge algorithm could not resolve all differences encountered."""
322
typestring = 'text conflict'
324
format = 'Text conflict in %(path)s'
327
class HandledConflict(Conflict):
328
"""A path problem that has been provisionally resolved.
329
This is intended to be a base class.
332
rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
334
def __init__(self, action, path, file_id=None):
335
Conflict.__init__(self, path, file_id)
339
return Conflict._cmp_list(self) + [self.action]
342
s = Conflict.as_stanza(self)
343
s.add('action', self.action)
347
class HandledPathConflict(HandledConflict):
348
"""A provisionally-resolved path problem involving two paths.
349
This is intended to be a base class.
352
rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
353
" %(file_id)r, %(conflict_file_id)r)"
355
def __init__(self, action, path, conflict_path, file_id=None,
356
conflict_file_id=None):
357
HandledConflict.__init__(self, action, path, file_id)
358
self.conflict_path = conflict_path
359
self.conflict_file_id = conflict_file_id
362
return HandledConflict._cmp_list(self) + [self.conflict_path,
363
self.conflict_file_id]
366
s = HandledConflict.as_stanza(self)
367
s.add('conflict_path', self.conflict_path)
368
if self.conflict_file_id is not None:
369
s.add('conflict_file_id', self.conflict_file_id)
374
class DuplicateID(HandledPathConflict):
375
"""Two files want the same file_id."""
377
typestring = 'duplicate id'
379
format = 'Conflict adding id to %(conflict_path)s. %(action)s %(path)s.'
382
class DuplicateEntry(HandledPathConflict):
383
"""Two directory entries want to have the same name."""
385
typestring = 'duplicate'
387
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
390
class ParentLoop(HandledPathConflict):
391
"""An attempt to create an infinitely-looping directory structure.
392
This is rare, but can be produced like so:
401
typestring = 'parent loop'
403
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
406
class UnversionedParent(HandledConflict):
407
"""An attempt to version an file whose parent directory is not versioned.
408
Typically, the result of a merge where one tree unversioned the directory
409
and the other added a versioned file to it.
412
typestring = 'unversioned parent'
414
format = 'Conflict adding versioned files to %(path)s. %(action)s.'
417
class MissingParent(HandledConflict):
418
"""An attempt to add files to a directory that is not present.
419
Typically, the result of a merge where one tree deleted the directory and
420
the other added a file to it.
423
typestring = 'missing parent'
425
format = 'Conflict adding files to %(path)s. %(action)s.'
432
def register_types(*conflict_types):
433
"""Register a Conflict subclass for serialization purposes"""
435
for conflict_type in conflict_types:
436
ctype[conflict_type.typestring] = conflict_type
439
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
440
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)