1
# Copyright (C) 2005 Aaron Bentley, Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: Move this into builtins
19
# TODO: 'bzr resolve' should accept a directory name and work from that
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):
44
"""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.
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):
77
"""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
aliases = ['resolved']
91
takes_args = ['file*']
92
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
93
def run(self, file_list=None, all=False):
94
from bzrlib.workingtree import WorkingTree
97
raise errors.BzrCommandError("If --all is specified,"
98
" no FILE may be provided")
99
tree = WorkingTree.open_containing('.')[0]
102
tree, file_list = builtins.tree_files(file_list)
103
if file_list is None:
104
un_resolved, resolved = tree.auto_resolve()
105
if len(un_resolved) > 0:
106
trace.note('%d conflict(s) auto-resolved.', len(resolved))
107
trace.note('Remaining conflicts:')
108
for conflict in un_resolved:
112
trace.note('All conflicts resolved.')
115
resolve(tree, file_list)
118
def resolve(tree, paths=None, ignore_misses=False):
119
tree.lock_tree_write()
121
tree_conflicts = tree.conflicts()
123
new_conflicts = ConflictList()
124
selected_conflicts = tree_conflicts
126
new_conflicts, selected_conflicts = \
127
tree_conflicts.select_conflicts(tree, paths, ignore_misses)
129
tree.set_conflicts(new_conflicts)
130
except errors.UnsupportedOperation:
132
selected_conflicts.remove_files(tree)
137
def restore(filename):
139
Restore a conflicted file to the state it was in before merging.
140
Only text restoration supported at present.
144
osutils.rename(filename + ".THIS", filename)
147
if e.errno != errno.ENOENT:
150
os.unlink(filename + ".BASE")
153
if e.errno != errno.ENOENT:
156
os.unlink(filename + ".OTHER")
159
if e.errno != errno.ENOENT:
162
raise errors.NotConflicted(filename)
165
class ConflictList(object):
166
"""List of conflicts.
168
Typically obtained from WorkingTree.conflicts()
170
Can be instantiated from stanzas or from Conflict subclasses.
173
def __init__(self, conflicts=None):
174
object.__init__(self)
175
if conflicts is None:
178
self.__list = conflicts
181
return len(self.__list) == 0
184
return len(self.__list)
187
return iter(self.__list)
189
def __getitem__(self, key):
190
return self.__list[key]
192
def append(self, conflict):
193
return self.__list.append(conflict)
195
def __eq__(self, other_list):
196
return list(self) == list(other_list)
198
def __ne__(self, other_list):
199
return not (self == other_list)
202
return "ConflictList(%r)" % self.__list
205
def from_stanzas(stanzas):
206
"""Produce a new ConflictList from an iterable of stanzas"""
207
conflicts = ConflictList()
208
for stanza in stanzas:
209
conflicts.append(Conflict.factory(**stanza.as_dict()))
212
def to_stanzas(self):
213
"""Generator of stanzas"""
214
for conflict in self:
215
yield conflict.as_stanza()
217
def to_strings(self):
218
"""Generate strings for the provided conflicts"""
219
for conflict in self:
222
def remove_files(self, tree):
223
"""Remove the THIS, BASE and OTHER files for listed conflicts"""
224
for conflict in self:
225
if not conflict.has_files:
227
for suffix in CONFLICT_SUFFIXES:
229
osutils.delete_any(tree.abspath(conflict.path+suffix))
231
if e.errno != errno.ENOENT:
234
def select_conflicts(self, tree, paths, ignore_misses=False):
235
"""Select the conflicts associated with paths in a tree.
237
File-ids are also used for this.
238
:return: a pair of ConflictLists: (not_selected, selected)
240
path_set = set(paths)
242
selected_paths = set()
243
new_conflicts = ConflictList()
244
selected_conflicts = ConflictList()
246
file_id = tree.path2id(path)
247
if file_id is not None:
250
for conflict in self:
252
for key in ('path', 'conflict_path'):
253
cpath = getattr(conflict, key, None)
256
if cpath in path_set:
258
selected_paths.add(cpath)
259
for key in ('file_id', 'conflict_file_id'):
260
cfile_id = getattr(conflict, key, None)
264
cpath = ids[cfile_id]
268
selected_paths.add(cpath)
270
selected_conflicts.append(conflict)
272
new_conflicts.append(conflict)
273
if ignore_misses is not True:
274
for path in [p for p in paths if p not in selected_paths]:
275
if not os.path.exists(tree.abspath(path)):
276
print "%s does not exist" % path
278
print "%s is not conflicted" % path
279
return new_conflicts, selected_conflicts
282
class Conflict(object):
283
"""Base class for all types of conflict"""
287
def __init__(self, path, file_id=None):
289
# warn turned off, because the factory blindly transfers the Stanza
290
# values to __init__ and Stanza is purely a Unicode api.
291
self.file_id = osutils.safe_file_id(file_id, warn=False)
294
s = rio.Stanza(type=self.typestring, path=self.path)
295
if self.file_id is not None:
296
# Stanza requires Unicode apis
297
s.add('file_id', self.file_id.decode('utf8'))
301
return [type(self), self.path, self.file_id]
303
def __cmp__(self, other):
304
if getattr(other, "_cmp_list", None) is None:
306
return cmp(self._cmp_list(), other._cmp_list())
309
return hash((type(self), self.path, self.file_id))
311
def __eq__(self, other):
312
return self.__cmp__(other) == 0
314
def __ne__(self, other):
315
return not self.__eq__(other)
318
return self.format % self.__dict__
321
rdict = dict(self.__dict__)
322
rdict['class'] = self.__class__.__name__
323
return self.rformat % rdict
326
def factory(type, **kwargs):
328
return ctype[type](**kwargs)
331
def sort_key(conflict):
332
if conflict.path is not None:
333
return conflict.path, conflict.typestring
334
elif getattr(conflict, "conflict_path", None) is not None:
335
return conflict.conflict_path, conflict.typestring
337
return None, conflict.typestring
340
class PathConflict(Conflict):
341
"""A conflict was encountered merging file paths"""
343
typestring = 'path conflict'
345
format = 'Path conflict: %(path)s / %(conflict_path)s'
347
rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
348
def __init__(self, path, conflict_path=None, file_id=None):
349
Conflict.__init__(self, path, file_id)
350
self.conflict_path = conflict_path
353
s = Conflict.as_stanza(self)
354
if self.conflict_path is not None:
355
s.add('conflict_path', self.conflict_path)
359
class ContentsConflict(PathConflict):
360
"""The files are of different types, or not present"""
364
typestring = 'contents conflict'
366
format = 'Contents conflict in %(path)s'
369
class TextConflict(PathConflict):
370
"""The merge algorithm could not resolve all differences encountered."""
374
typestring = 'text conflict'
376
format = 'Text conflict in %(path)s'
379
class HandledConflict(Conflict):
380
"""A path problem that has been provisionally resolved.
381
This is intended to be a base class.
384
rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
386
def __init__(self, action, path, file_id=None):
387
Conflict.__init__(self, path, file_id)
391
return Conflict._cmp_list(self) + [self.action]
394
s = Conflict.as_stanza(self)
395
s.add('action', self.action)
399
class HandledPathConflict(HandledConflict):
400
"""A provisionally-resolved path problem involving two paths.
401
This is intended to be a base class.
404
rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
405
" %(file_id)r, %(conflict_file_id)r)"
407
def __init__(self, action, path, conflict_path, file_id=None,
408
conflict_file_id=None):
409
HandledConflict.__init__(self, action, path, file_id)
410
self.conflict_path = conflict_path
411
# warn turned off, because the factory blindly transfers the Stanza
412
# values to __init__.
413
self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
417
return HandledConflict._cmp_list(self) + [self.conflict_path,
418
self.conflict_file_id]
421
s = HandledConflict.as_stanza(self)
422
s.add('conflict_path', self.conflict_path)
423
if self.conflict_file_id is not None:
424
s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
429
class DuplicateID(HandledPathConflict):
430
"""Two files want the same file_id."""
432
typestring = 'duplicate id'
434
format = 'Conflict adding id to %(conflict_path)s. %(action)s %(path)s.'
437
class DuplicateEntry(HandledPathConflict):
438
"""Two directory entries want to have the same name."""
440
typestring = 'duplicate'
442
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
445
class ParentLoop(HandledPathConflict):
446
"""An attempt to create an infinitely-looping directory structure.
447
This is rare, but can be produced like so:
456
typestring = 'parent loop'
458
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
461
class UnversionedParent(HandledConflict):
462
"""An attempt to version an file whose parent directory is not versioned.
463
Typically, the result of a merge where one tree unversioned the directory
464
and the other added a versioned file to it.
467
typestring = 'unversioned parent'
469
format = 'Conflict because %(path)s is not versioned, but has versioned'\
470
' children. %(action)s.'
473
class MissingParent(HandledConflict):
474
"""An attempt to add files to a directory that is not present.
475
Typically, the result of a merge where THIS deleted the directory and
476
the OTHER added a file to it.
477
See also: DeletingParent (same situation, reversed THIS and OTHER)
480
typestring = 'missing parent'
482
format = 'Conflict adding files to %(path)s. %(action)s.'
485
class DeletingParent(HandledConflict):
486
"""An attempt to add files to a directory that is not present.
487
Typically, the result of a merge where one OTHER deleted the directory and
488
the THIS added a file to it.
491
typestring = 'deleting parent'
493
format = "Conflict: can't delete %(path)s because it is not empty. "\
500
def register_types(*conflict_types):
501
"""Register a Conflict subclass for serialization purposes"""
503
for conflict_type in conflict_types:
504
ctype[conflict_type.typestring] = conflict_type
507
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
508
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,