81
59
it will mark a conflict. A conflict means that you need to fix something,
82
60
before you should commit.
84
Once you have fixed a problem, use "bzr resolve" to automatically mark
85
text conflicts as fixed, resolve FILE to mark a specific conflict as
86
resolved, or "bzr resolve --all" to mark all conflicts as resolved.
62
Once you have fixed a problem, use "bzr resolve FILE.." to mark
63
individual files as fixed, or "bzr resolve --all" to mark all conflicts as
88
66
See also bzr conflicts.
90
68
aliases = ['resolved']
91
69
takes_args = ['file*']
93
Option('all', help='Resolve all conflicts in this tree.'),
70
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
95
71
def run(self, file_list=None, all=False):
96
from bzrlib.workingtree import WorkingTree
99
raise errors.BzrCommandError("If --all is specified,"
100
" no FILE may be provided")
101
tree = WorkingTree.open_containing('.')[0]
74
raise BzrCommandError(
75
"command 'resolve' needs one or more FILE, or --all")
76
tree = WorkingTree.open_containing(u'.')[0]
77
file_list = list(tree.abspath(f) for f in tree.iter_conflicts())
104
tree, file_list = builtins.tree_files(file_list)
105
if file_list is None:
106
un_resolved, resolved = tree.auto_resolve()
107
if len(un_resolved) > 0:
108
trace.note('%d conflict(s) auto-resolved.', len(resolved))
109
trace.note('Remaining conflicts:')
110
for conflict in un_resolved:
80
raise BzrCommandError(
81
"If --all is specified, no FILE may be provided")
82
for filename in file_list:
84
for suffix in CONFLICT_SUFFIXES:
86
os.unlink(filename+suffix)
88
if e.errno != errno.ENOENT:
92
if failures == len(CONFLICT_SUFFIXES):
93
if not os.path.exists(filename):
94
print "%s does not exist" % filename
114
trace.note('All conflicts resolved.')
117
resolve(tree, file_list)
120
def resolve(tree, paths=None, ignore_misses=False):
121
tree.lock_tree_write()
123
tree_conflicts = tree.conflicts()
125
new_conflicts = ConflictList()
126
selected_conflicts = tree_conflicts
128
new_conflicts, selected_conflicts = \
129
tree_conflicts.select_conflicts(tree, paths, ignore_misses)
131
tree.set_conflicts(new_conflicts)
132
except errors.UnsupportedOperation:
134
selected_conflicts.remove_files(tree)
96
print "%s is not conflicted" % filename
139
98
def restore(filename):
161
120
if e.errno != errno.ENOENT:
163
122
if not conflicted:
164
raise errors.NotConflicted(filename)
167
class ConflictList(object):
168
"""List of conflicts.
170
Typically obtained from WorkingTree.conflicts()
172
Can be instantiated from stanzas or from Conflict subclasses.
175
def __init__(self, conflicts=None):
176
object.__init__(self)
177
if conflicts is None:
180
self.__list = conflicts
183
return len(self.__list) == 0
186
return len(self.__list)
189
return iter(self.__list)
191
def __getitem__(self, key):
192
return self.__list[key]
194
def append(self, conflict):
195
return self.__list.append(conflict)
197
def __eq__(self, other_list):
198
return list(self) == list(other_list)
200
def __ne__(self, other_list):
201
return not (self == other_list)
204
return "ConflictList(%r)" % self.__list
207
def from_stanzas(stanzas):
208
"""Produce a new ConflictList from an iterable of stanzas"""
209
conflicts = ConflictList()
210
for stanza in stanzas:
211
conflicts.append(Conflict.factory(**stanza.as_dict()))
214
def to_stanzas(self):
215
"""Generator of stanzas"""
216
for conflict in self:
217
yield conflict.as_stanza()
219
def to_strings(self):
220
"""Generate strings for the provided conflicts"""
221
for conflict in self:
224
def remove_files(self, tree):
225
"""Remove the THIS, BASE and OTHER files for listed conflicts"""
226
for conflict in self:
227
if not conflict.has_files:
229
for suffix in CONFLICT_SUFFIXES:
231
osutils.delete_any(tree.abspath(conflict.path+suffix))
233
if e.errno != errno.ENOENT:
236
def select_conflicts(self, tree, paths, ignore_misses=False):
237
"""Select the conflicts associated with paths in a tree.
239
File-ids are also used for this.
240
:return: a pair of ConflictLists: (not_selected, selected)
242
path_set = set(paths)
244
selected_paths = set()
245
new_conflicts = ConflictList()
246
selected_conflicts = ConflictList()
248
file_id = tree.path2id(path)
249
if file_id is not None:
252
for conflict in self:
254
for key in ('path', 'conflict_path'):
255
cpath = getattr(conflict, key, None)
258
if cpath in path_set:
260
selected_paths.add(cpath)
261
for key in ('file_id', 'conflict_file_id'):
262
cfile_id = getattr(conflict, key, None)
266
cpath = ids[cfile_id]
270
selected_paths.add(cpath)
272
selected_conflicts.append(conflict)
274
new_conflicts.append(conflict)
275
if ignore_misses is not True:
276
for path in [p for p in paths if p not in selected_paths]:
277
if not os.path.exists(tree.abspath(path)):
278
print "%s does not exist" % path
280
print "%s is not conflicted" % path
281
return new_conflicts, selected_conflicts
284
class Conflict(object):
285
"""Base class for all types of conflict"""
289
def __init__(self, path, file_id=None):
291
# warn turned off, because the factory blindly transfers the Stanza
292
# values to __init__ and Stanza is purely a Unicode api.
293
self.file_id = osutils.safe_file_id(file_id, warn=False)
296
s = rio.Stanza(type=self.typestring, path=self.path)
297
if self.file_id is not None:
298
# Stanza requires Unicode apis
299
s.add('file_id', self.file_id.decode('utf8'))
303
return [type(self), self.path, self.file_id]
305
def __cmp__(self, other):
306
if getattr(other, "_cmp_list", None) is None:
308
return cmp(self._cmp_list(), other._cmp_list())
311
return hash((type(self), self.path, self.file_id))
313
def __eq__(self, other):
314
return self.__cmp__(other) == 0
316
def __ne__(self, other):
317
return not self.__eq__(other)
320
return self.format % self.__dict__
323
rdict = dict(self.__dict__)
324
rdict['class'] = self.__class__.__name__
325
return self.rformat % rdict
328
def factory(type, **kwargs):
330
return ctype[type](**kwargs)
333
def sort_key(conflict):
334
if conflict.path is not None:
335
return conflict.path, conflict.typestring
336
elif getattr(conflict, "conflict_path", None) is not None:
337
return conflict.conflict_path, conflict.typestring
339
return None, conflict.typestring
342
class PathConflict(Conflict):
343
"""A conflict was encountered merging file paths"""
345
typestring = 'path conflict'
347
format = 'Path conflict: %(path)s / %(conflict_path)s'
349
rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
350
def __init__(self, path, conflict_path=None, file_id=None):
351
Conflict.__init__(self, path, file_id)
352
self.conflict_path = conflict_path
355
s = Conflict.as_stanza(self)
356
if self.conflict_path is not None:
357
s.add('conflict_path', self.conflict_path)
361
class ContentsConflict(PathConflict):
362
"""The files are of different types, or not present"""
366
typestring = 'contents conflict'
368
format = 'Contents conflict in %(path)s'
371
class TextConflict(PathConflict):
372
"""The merge algorithm could not resolve all differences encountered."""
376
typestring = 'text conflict'
378
format = 'Text conflict in %(path)s'
381
class HandledConflict(Conflict):
382
"""A path problem that has been provisionally resolved.
383
This is intended to be a base class.
386
rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
388
def __init__(self, action, path, file_id=None):
389
Conflict.__init__(self, path, file_id)
393
return Conflict._cmp_list(self) + [self.action]
396
s = Conflict.as_stanza(self)
397
s.add('action', self.action)
401
class HandledPathConflict(HandledConflict):
402
"""A provisionally-resolved path problem involving two paths.
403
This is intended to be a base class.
406
rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
407
" %(file_id)r, %(conflict_file_id)r)"
409
def __init__(self, action, path, conflict_path, file_id=None,
410
conflict_file_id=None):
411
HandledConflict.__init__(self, action, path, file_id)
412
self.conflict_path = conflict_path
413
# warn turned off, because the factory blindly transfers the Stanza
414
# values to __init__.
415
self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
419
return HandledConflict._cmp_list(self) + [self.conflict_path,
420
self.conflict_file_id]
423
s = HandledConflict.as_stanza(self)
424
s.add('conflict_path', self.conflict_path)
425
if self.conflict_file_id is not None:
426
s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
431
class DuplicateID(HandledPathConflict):
432
"""Two files want the same file_id."""
434
typestring = 'duplicate id'
436
format = 'Conflict adding id to %(conflict_path)s. %(action)s %(path)s.'
439
class DuplicateEntry(HandledPathConflict):
440
"""Two directory entries want to have the same name."""
442
typestring = 'duplicate'
444
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
447
class ParentLoop(HandledPathConflict):
448
"""An attempt to create an infinitely-looping directory structure.
449
This is rare, but can be produced like so:
458
typestring = 'parent loop'
460
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
463
class UnversionedParent(HandledConflict):
464
"""An attempt to version an file whose parent directory is not versioned.
465
Typically, the result of a merge where one tree unversioned the directory
466
and the other added a versioned file to it.
469
typestring = 'unversioned parent'
471
format = 'Conflict because %(path)s is not versioned, but has versioned'\
472
' children. %(action)s.'
475
class MissingParent(HandledConflict):
476
"""An attempt to add files to a directory that is not present.
477
Typically, the result of a merge where THIS deleted the directory and
478
the OTHER added a file to it.
479
See also: DeletingParent (same situation, reversed THIS and OTHER)
482
typestring = 'missing parent'
484
format = 'Conflict adding files to %(path)s. %(action)s.'
487
class DeletingParent(HandledConflict):
488
"""An attempt to add files to a directory that is not present.
489
Typically, the result of a merge where one OTHER deleted the directory and
490
the THIS added a file to it.
493
typestring = 'deleting parent'
495
format = "Conflict: can't delete %(path)s because it is not empty. "\
502
def register_types(*conflict_types):
503
"""Register a Conflict subclass for serialization purposes"""
505
for conflict_type in conflict_types:
506
ctype[conflict_type.typestring] = conflict_type
509
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
510
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
123
raise NotConflicted(filename)