1
# Copyright (C) 2005 Aaron Bentley, Canonical Ltd
1
# Copyright (C) 2005 by Aaron Bentley
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
26
from bzrlib.commands import register_command
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
37
28
from bzrlib.option import Option
29
from bzrlib.osutils import rename
30
from bzrlib.rio import Stanza
40
33
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
43
class cmd_conflicts(commands.Command):
36
class cmd_conflicts(bzrlib.commands.Command):
44
37
"""List files with conflicts.
46
39
Merge will do its best to combine the changes in two branches, but there
48
41
it will mark a conflict. A conflict means that you need to fix something,
49
42
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
44
Use bzr resolve when you have fixed a problem.
46
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
57
49
See also bzr resolve.
61
help='List paths of files with text conflicts.'),
64
def run(self, text=False):
65
52
from bzrlib.workingtree import WorkingTree
66
53
wt = WorkingTree.open_containing(u'.')[0]
67
54
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):
57
class cmd_resolve(bzrlib.commands.Command):
77
58
"""Mark a conflict as resolved.
79
60
Merge will do its best to combine the changes in two branches, but there
81
62
it will mark a conflict. A conflict means that you need to fix something,
82
63
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.
65
Once you have fixed a problem, use "bzr resolve FILE.." to mark
66
individual files as fixed, or "bzr resolve --all" to mark all conflicts as
88
69
See also bzr conflicts.
90
71
aliases = ['resolved']
91
72
takes_args = ['file*']
93
Option('all', help='Resolve all conflicts in this tree.'),
73
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
95
74
def run(self, file_list=None, all=False):
96
75
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]
78
raise BzrCommandError(
79
"command 'resolve' needs one or more FILE, or --all")
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:
114
trace.note('All conflicts resolved.')
117
resolve(tree, file_list)
82
raise BzrCommandError(
83
"If --all is specified, no FILE may be provided")
84
tree = WorkingTree.open_containing(u'.')[0]
85
resolve(tree, file_list)
120
88
def resolve(tree, paths=None, ignore_misses=False):
121
tree.lock_tree_write()
123
91
tree_conflicts = tree.conflicts()
129
97
tree_conflicts.select_conflicts(tree, paths, ignore_misses)
131
99
tree.set_conflicts(new_conflicts)
132
except errors.UnsupportedOperation:
100
except UnsupportedOperation:
134
102
selected_conflicts.remove_files(tree)
161
129
if e.errno != errno.ENOENT:
163
131
if not conflicted:
164
raise errors.NotConflicted(filename)
132
raise NotConflicted(filename)
167
135
class ConflictList(object):
168
"""List of conflicts.
170
Typically obtained from WorkingTree.conflicts()
172
137
Can be instantiated from stanzas or from Conflict subclasses.
289
250
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)
252
self.file_id = file_id
295
254
def as_stanza(self):
296
s = rio.Stanza(type=self.typestring, path=self.path)
255
s = Stanza(type=self.typestring, path=self.path)
297
256
if self.file_id is not None:
298
# Stanza requires Unicode apis
299
s.add('file_id', self.file_id.decode('utf8'))
257
s.add('file_id', self.file_id)
302
260
def _cmp_list(self):
308
266
return cmp(self._cmp_list(), other._cmp_list())
311
return hash((type(self), self.path, self.file_id))
313
268
def __eq__(self, other):
314
269
return self.__cmp__(other) == 0
330
285
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
288
class PathConflict(Conflict):
343
289
"""A conflict was encountered merging file paths"""
410
356
conflict_file_id=None):
411
357
HandledConflict.__init__(self, action, path, file_id)
412
358
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,
359
self.conflict_file_id = conflict_file_id
418
361
def _cmp_list(self):
419
362
return HandledConflict._cmp_list(self) + [self.conflict_path,
423
366
s = HandledConflict.as_stanza(self)
424
367
s.add('conflict_path', self.conflict_path)
425
368
if self.conflict_file_id is not None:
426
s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
369
s.add('conflict_file_id', self.conflict_file_id)
469
412
typestring = 'unversioned parent'
471
format = 'Conflict because %(path)s is not versioned, but has versioned'\
472
' children. %(action)s.'
414
format = 'Conflict adding versioned files to %(path)s. %(action)s.'
475
417
class MissingParent(HandledConflict):
476
418
"""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)
419
Typically, the result of a merge where one tree deleted the directory and
420
the other added a file to it.
482
423
typestring = 'missing parent'
484
425
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. "\
509
439
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
510
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
440
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)