19
19
# TODO: 'bzr resolve' should accept a directory name and work from that
22
# TODO: bzr revert should resolve; even when reverting the whole tree
23
# or particular directories
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
37
from bzrlib.option import Option
40
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
43
class cmd_conflicts(commands.Command):
29
from bzrlib.branch import Branch
30
from bzrlib.errors import BzrCommandError, NotConflicted
31
from bzrlib.commands import register_command
32
from bzrlib.workingtree import CONFLICT_SUFFIXES
34
class cmd_conflicts(bzrlib.commands.Command):
44
35
"""List files with conflicts.
46
Merge will do its best to combine the changes in two branches, but there
47
are some kinds of problems only a human can fix. When it encounters those,
48
it will mark a conflict. A conflict means that you need to fix something,
49
before you should commit.
51
Conflicts normally are listed as short, human-readable messages. If --text
52
is supplied, the pathnames of files with text conflicts are listed,
53
instead. (This is useful for editing all files with text conflicts.)
55
Use bzr resolve when you have fixed a problem.
36
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
61
help='List paths of files with text conflicts.'),
64
def run(self, text=False):
65
from bzrlib.workingtree import WorkingTree
66
wt = WorkingTree.open_containing(u'.')[0]
67
for conflict in wt.conflicts():
69
if conflict.typestring != 'text conflict':
71
self.outf.write(conflict.path + '\n')
73
self.outf.write(str(conflict) + '\n')
76
class cmd_resolve(commands.Command):
40
for path in Branch.open_containing('.')[0].working_tree().iter_conflicts():
43
class cmd_resolve(bzrlib.commands.Command):
77
44
"""Mark a conflict as resolved.
79
Merge will do its best to combine the changes in two branches, but there
80
are some kinds of problems only a human can fix. When it encounters those,
81
it will mark a conflict. A conflict means that you need to fix something,
82
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.
88
See also bzr conflicts.
90
46
aliases = ['resolved']
91
47
takes_args = ['file*']
93
Option('all', help='Resolve all conflicts in this tree.'),
48
takes_options = ['all']
95
49
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]
52
raise BzrCommandError(
53
"command 'resolve' needs one or more FILE, or --all")
54
tree = Branch.open_containing('.')[0].working_tree()
55
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:
58
raise BzrCommandError(
59
"If --all is specified, no FILE may be provided")
60
for filename in file_list:
62
for suffix in CONFLICT_SUFFIXES:
64
os.unlink(filename+suffix)
66
if e.errno != errno.ENOENT:
70
if failures == len(CONFLICT_SUFFIXES):
71
if not os.path.exists(filename):
72
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)
74
print "%s is not conflicted" % filename
139
76
def restore(filename):
161
98
if e.errno != errno.ENOENT:
163
100
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,
101
raise NotConflicted(filename)