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
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
# TODO: Move this into builtins
19
# TODO: 'bzr resolve' should accept a directory name and work from that
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(), """
26
from bzrlib.commands import register_command
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
28
37
from bzrlib.option import Option
29
from bzrlib.osutils import rename, delete_any
30
from bzrlib.rio import Stanza
33
40
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
36
class cmd_conflicts(bzrlib.commands.Command):
43
class cmd_conflicts(commands.Command):
37
44
"""List files with conflicts.
39
46
Merge will do its best to combine the changes in two branches, but there
41
48
it will mark a conflict. A conflict means that you need to fix something,
42
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.)
44
55
Use bzr resolve when you have fixed a problem.
46
(conflicts are determined by the presence of .BASE .TREE, and .OTHER
59
help='List paths of files with text conflicts.'),
61
_see_also = ['resolve', 'conflict-types']
63
def run(self, text=False):
52
64
from bzrlib.workingtree import WorkingTree
53
65
wt = WorkingTree.open_containing(u'.')[0]
54
66
for conflict in wt.conflicts():
58
class cmd_resolve(bzrlib.commands.Command):
68
if conflict.typestring != 'text conflict':
70
self.outf.write(conflict.path + '\n')
72
self.outf.write(str(conflict) + '\n')
75
class cmd_resolve(commands.Command):
59
76
"""Mark a conflict as resolved.
61
78
Merge will do its best to combine the changes in two branches, but there
63
80
it will mark a conflict. A conflict means that you need to fix something,
64
81
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.
83
Once you have fixed a problem, use "bzr resolve" to automatically mark
84
text conflicts as fixed, "bzr resolve FILE" to mark a specific conflict as
85
resolved, or "bzr resolve --all" to mark all conflicts as resolved.
72
87
aliases = ['resolved']
73
88
takes_args = ['file*']
74
takes_options = [Option('all', help='Resolve all conflicts in this tree')]
90
Option('all', help='Resolve all conflicts in this tree.'),
92
_see_also = ['conflicts']
75
93
def run(self, file_list=None, all=False):
76
94
from bzrlib.workingtree import WorkingTree
79
raise BzrCommandError("If --all is specified, no FILE may be provided")
97
raise errors.BzrCommandError("If --all is specified,"
98
" no FILE may be provided")
80
99
tree = WorkingTree.open_containing('.')[0]
102
tree, file_list = builtins.tree_files(file_list)
83
103
if file_list is None:
84
raise BzrCommandError("command 'resolve' needs one or more FILE, or --all")
85
tree = WorkingTree.open_containing(file_list[0])[0]
86
to_resolve = [tree.relpath(p) for p in file_list]
87
resolve(tree, to_resolve)
90
def resolve(tree, paths=None, ignore_misses=False):
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, recursive=False):
119
"""Resolve some or all of the conflicts in a working tree.
121
:param paths: If None, resolve all conflicts. Otherwise, select only
123
:param recursive: If True, then elements of paths which are directories
124
have all their children resolved, etc. When invoked as part of
125
recursive commands like revert, this should be True. For commands
126
or applications wishing finer-grained control, like the resolve
127
command, this should be False.
128
:ignore_misses: If False, warnings will be printed if the supplied paths
129
do not have conflicts.
131
tree.lock_tree_write()
93
133
tree_conflicts = tree.conflicts()
96
136
selected_conflicts = tree_conflicts
98
138
new_conflicts, selected_conflicts = \
99
tree_conflicts.select_conflicts(tree, paths, ignore_misses)
139
tree_conflicts.select_conflicts(tree, paths, ignore_misses,
101
142
tree.set_conflicts(new_conflicts)
102
except UnsupportedOperation:
143
except errors.UnsupportedOperation:
104
145
selected_conflicts.remove_files(tree)
109
150
def restore(filename):
111
Restore a conflicted file to the state it was in before merging.
112
Only text restoration supported at present.
151
"""Restore a conflicted file to the state it was in before merging.
153
Only text restoration is supported at present.
114
155
conflicted = False
116
rename(filename + ".THIS", filename)
157
osutils.rename(filename + ".THIS", filename)
117
158
conflicted = True
118
159
except OSError, e:
119
160
if e.errno != errno.ENOENT:
199
240
for suffix in CONFLICT_SUFFIXES:
201
delete_any(tree.abspath(conflict.path+suffix))
242
osutils.delete_any(tree.abspath(conflict.path+suffix))
202
243
except OSError, e:
203
244
if e.errno != errno.ENOENT:
206
def select_conflicts(self, tree, paths, ignore_misses=False):
247
def select_conflicts(self, tree, paths, ignore_misses=False,
207
249
"""Select the conflicts associated with paths in a tree.
209
251
File-ids are also used for this.
210
252
:return: a pair of ConflictLists: (not_selected, selected)
259
306
def __init__(self, path, file_id=None):
261
self.file_id = file_id
308
# warn turned off, because the factory blindly transfers the Stanza
309
# values to __init__ and Stanza is purely a Unicode api.
310
self.file_id = osutils.safe_file_id(file_id, warn=False)
263
312
def as_stanza(self):
264
s = Stanza(type=self.typestring, path=self.path)
313
s = rio.Stanza(type=self.typestring, path=self.path)
265
314
if self.file_id is not None:
266
s.add('file_id', self.file_id)
315
# Stanza requires Unicode apis
316
s.add('file_id', self.file_id.decode('utf8'))
269
319
def _cmp_list(self):
376
426
def __init__(self, action, path, conflict_path, file_id=None,
377
427
conflict_file_id=None):
378
428
HandledConflict.__init__(self, action, path, file_id)
379
self.conflict_path = conflict_path
380
self.conflict_file_id = conflict_file_id
429
self.conflict_path = conflict_path
430
# warn turned off, because the factory blindly transfers the Stanza
431
# values to __init__.
432
self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
382
435
def _cmp_list(self):
383
return HandledConflict._cmp_list(self) + [self.conflict_path,
436
return HandledConflict._cmp_list(self) + [self.conflict_path,
384
437
self.conflict_file_id]
386
439
def as_stanza(self):
387
440
s = HandledConflict.as_stanza(self)
388
441
s.add('conflict_path', self.conflict_path)
389
442
if self.conflict_file_id is not None:
390
s.add('conflict_file_id', self.conflict_file_id)
443
s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
427
480
class UnversionedParent(HandledConflict):
428
"""An attempt to version an file whose parent directory is not versioned.
481
"""An attempt to version a file whose parent directory is not versioned.
429
482
Typically, the result of a merge where one tree unversioned the directory
430
483
and the other added a versioned file to it.
433
486
typestring = 'unversioned parent'
435
format = 'Conflict adding versioned files to %(path)s. %(action)s.'
488
format = 'Conflict because %(path)s is not versioned, but has versioned'\
489
' children. %(action)s.'
438
492
class MissingParent(HandledConflict):
439
493
"""An attempt to add files to a directory that is not present.
440
Typically, the result of a merge where one tree deleted the directory and
441
the other added a file to it.
494
Typically, the result of a merge where THIS deleted the directory and
495
the OTHER added a file to it.
496
See also: DeletingParent (same situation, reversed THIS and OTHER)
444
499
typestring = 'missing parent'
446
501
format = 'Conflict adding files to %(path)s. %(action)s.'
504
class DeletingParent(HandledConflict):
505
"""An attempt to add files to a directory that is not present.
506
Typically, the result of a merge where one OTHER deleted the directory and
507
the THIS added a file to it.
510
typestring = 'deleting parent'
512
format = "Conflict: can't delete %(path)s because it is not empty. "\
516
class NonDirectoryParent(HandledConflict):
517
"""An attempt to add files to a directory that is not a director or
518
an attempt to change the kind of a directory with files.
521
typestring = 'non-directory parent'
523
format = "Conflict: %(path)s is not a directory, but has files in it."\
460
536
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
461
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)
537
DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
538
DeletingParent, NonDirectoryParent)