1
# Copyright (C) 2005 by Aaron Bentley
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
26
from bzrlib.commands import register_command
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
28
from bzrlib.option import Option
29
from bzrlib.osutils import rename
30
from bzrlib.rio import Stanza
33
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
36
class cmd_conflicts(bzrlib.commands.Command):
37
"""List files with conflicts.
39
Merge will do its best to combine the changes in two branches, but there
40
are some kinds of problems only a human can fix. When it encounters those,
41
it will mark a conflict. A conflict means that you need to fix something,
42
before you should commit.
44
Use bzr resolve when you have fixed a problem.
46
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
52
from bzrlib.workingtree import WorkingTree
53
from transform import conflicts_strings
54
wt = WorkingTree.open_containing(u'.')[0]
55
for conflict in conflicts_strings(wt.conflict_lines()):
58
class cmd_resolve(bzrlib.commands.Command):
59
"""Mark a conflict as resolved.
61
Merge will do its best to combine the changes in two branches, but there
62
are some kinds of problems only a human can fix. When it encounters those,
63
it will mark a conflict. A conflict means that you need to fix something,
64
before you should commit.
66
Once you have fixed a problem, use "bzr resolve FILE.." to mark
67
individual files as fixed, or "bzr resolve --all" to mark all conflicts as
70
See also bzr conflicts.
72
aliases = ['resolved']
73
takes_args = ['file*']
74
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
75
def run(self, file_list=None, all=False):
76
from bzrlib.workingtree import WorkingTree
79
raise BzrCommandError(
80
"command 'resolve' needs one or more FILE, or --all")
83
raise BzrCommandError(
84
"If --all is specified, no FILE may be provided")
85
tree = WorkingTree.open_containing(u'.')[0]
86
resolve(tree, file_list)
89
def resolve(tree, paths=None, ignore_misses=False):
92
tree_conflicts = list(tree.conflict_lines())
95
selected_conflicts = tree_conflicts
97
new_conflicts, selected_conflicts = \
98
select_conflicts(tree, paths, tree_conflicts, ignore_misses)
100
tree.set_conflict_lines(new_conflicts)
101
except UnsupportedOperation:
103
remove_conflict_files(tree, selected_conflicts)
108
def select_conflicts(tree, paths, tree_conflicts, ignore_misses=False):
109
path_set = set(paths)
111
selected_paths = set()
113
selected_conflicts = []
115
file_id = tree.path2id(path)
116
if file_id is not None:
119
for conflict, stanza in zip(tree_conflicts,
120
conflict_stanzas(tree_conflicts)):
122
for key in ('path', 'conflict_path'):
127
if cpath in path_set:
129
selected_paths.add(cpath)
130
for key in ('file_id', 'conflict_file_id'):
132
cfile_id = stanza[key]
136
cpath = ids[cfile_id]
140
selected_paths.add(cpath)
142
selected_conflicts.append(conflict)
144
new_conflicts.append(conflict)
145
if ignore_misses is not True:
146
for path in [p for p in paths if p not in selected_paths]:
147
if not os.path.exists(tree.abspath(path)):
148
print "%s does not exist" % path
150
print "%s is not conflicted" % path
151
return new_conflicts, selected_conflicts
153
def remove_conflict_files(tree, conflicts):
154
for stanza in conflict_stanzas(conflicts):
155
if stanza['type'] in ("text conflict", "contents conflict"):
156
for suffix in CONFLICT_SUFFIXES:
158
os.unlink(tree.abspath(stanza['path']+suffix))
160
if e.errno != errno.ENOENT:
164
def restore(filename):
166
Restore a conflicted file to the state it was in before merging.
167
Only text restoration supported at present.
171
rename(filename + ".THIS", filename)
174
if e.errno != errno.ENOENT:
177
os.unlink(filename + ".BASE")
180
if e.errno != errno.ENOENT:
183
os.unlink(filename + ".OTHER")
186
if e.errno != errno.ENOENT:
189
raise NotConflicted(filename)
192
def conflict_stanzas(conflicts):
193
for conflict in conflicts:
194
yield conflict.as_stanza()
196
def stanza_conflicts(stanzas):
197
for stanza in stanzas:
198
yield Conflict.factory(**stanza.as_dict())
201
class Conflict(object):
202
"""Base class for all types of conflict"""
203
def __init__(self, path, file_id=None):
205
self.file_id = file_id
208
s = Stanza(type=self.typestring, path=self.path)
209
if self.file_id is not None:
210
s.add('file_id', self.file_id)
213
def __cmp__(self, other):
214
result = cmp(type(self), type(other))
217
result = cmp(self.path, other.path)
220
return cmp(self.file_id, other.file_id)
222
def __eq__(self, other):
223
return self.__cmp__(other) == 0
225
def __ne__(self, other):
226
return not self.__eq__(other)
229
return self.format % self.__dict__
232
def factory(type, **kwargs):
234
return ctype[type](**kwargs)
237
class PathConflict(Conflict):
238
typestring = 'path conflict'
239
format = 'Path conflict: %(path)s / %(conflict_path)s'
240
def __init__(self, path, conflict_path=None, file_id=None):
241
Conflict.__init__(self, path, file_id)
242
self.conflict_path = conflict_path
245
s = Conflict.as_stanza(self)
246
if self.conflict_path is not None:
247
s.add('conflict_path', self.conflict_path)
251
class ContentsConflict(PathConflict):
252
typestring = 'contents conflict'
253
format = 'Contents conflict in %(path)s'
256
class TextConflict(PathConflict):
257
typestring = 'text conflict'
258
format = 'Text conflict in %(path)s'
261
class HandledConflict(Conflict):
262
def __init__(self, action, path, file_id=None):
263
Conflict.__init__(self, path, file_id)
267
s = Conflict.as_stanza(self)
268
s.add('action', self.action)
272
class HandledPathConflict(HandledConflict):
273
def __init__(self, action, path, conflict_path, file_id=None,
274
conflict_file_id=None):
275
HandledConflict.__init__(self, action, path, file_id)
276
self.conflict_path = conflict_path
277
self.conflict_file_id = conflict_file_id
280
s = HandledConflict.as_stanza(self)
281
s.add('conflict_path', self.conflict_path)
282
if self.conflict_file_id is not None:
283
s.add('conflict_file_id', self.conflict_file_id)
288
class DuplicateID(HandledPathConflict):
289
typestring = 'duplicate id'
290
format = 'Conflict adding id to %(conflict_path)s. %(action)s %(path)s.'
293
class DuplicateEntry(HandledPathConflict):
294
typestring = 'duplicate'
295
format = 'Conflict adding file %(conflict_path)s. %(action)s %(path)s.'
298
class ParentLoop(HandledPathConflict):
299
typestring = 'parent loop'
300
format = 'Conflict moving %(conflict_path)s into %(path)s. %(action)s.'
303
class UnversionedParent(HandledConflict):
304
typestring = 'unversioned parent'
305
format = 'Conflict adding versioned files to %(path)s. %(action)s.'
308
class MissingParent(HandledConflict):
309
typestring = 'missing parent'
310
format = 'Conflict adding files to %(path)s. %(action)s.'
317
def register_types(*conflict_types):
318
"""Register a Conflict subclass for serialization purposes"""
320
for conflict_type in conflict_types:
321
ctype[conflict_type.typestring] = conflict_type
324
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
325
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)