88
CONFLICT_SUFFIXES = ['.BASE', '.OTHER', '.THIS']
91
# TODO: There should be a base revid attribute to better inform the user about
92
# how the conflicts were generated.
93
class TextConflict(_mod_conflicts.Conflict):
94
"""The merge algorithm could not resolve all differences encountered."""
98
typestring = 'text conflict'
100
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
102
def associated_filenames(self):
103
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
105
def _resolve(self, tt, winner_suffix):
106
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
108
:param tt: The TreeTransform where the conflict is resolved.
109
:param winner_suffix: Either 'THIS' or 'OTHER'
111
The resolution is symmetric, when taking THIS, item.THIS is renamed
112
into item and vice-versa. This takes one of the files as a whole
113
ignoring every difference that could have been merged cleanly.
115
# To avoid useless copies, we switch item and item.winner_suffix, only
116
# item will exist after the conflict has been resolved anyway.
117
item_tid = tt.trans_id_tree_path(self.path)
118
item_parent_tid = tt.get_tree_parent(item_tid)
119
winner_path = self.path + '.' + winner_suffix
120
winner_tid = tt.trans_id_tree_path(winner_path)
121
winner_parent_tid = tt.get_tree_parent(winner_tid)
122
# Switch the paths to preserve the content
123
tt.adjust_path(osutils.basename(self.path),
124
winner_parent_tid, winner_tid)
125
tt.adjust_path(osutils.basename(winner_path),
126
item_parent_tid, item_tid)
127
tt.unversion_file(item_tid)
128
tt.version_file(winner_tid)
131
def action_auto(self, tree):
132
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
133
# can't be auto resolved does not seem ideal.
135
kind = tree.kind(self.path)
136
except errors.NoSuchFile:
139
raise NotImplementedError("Conflict is not a file")
140
conflict_markers_in_line = self._conflict_re.search
141
with tree.get_file(self.path) as f:
143
if conflict_markers_in_line(line):
144
raise NotImplementedError("Conflict markers present")
146
def _resolve_with_cleanups(self, tree, *args, **kwargs):
147
with tree.transform() as tt:
148
self._resolve(tt, *args, **kwargs)
150
def action_take_this(self, tree):
151
self._resolve_with_cleanups(tree, 'THIS')
153
def action_take_other(self, tree):
154
self._resolve_with_cleanups(tree, 'OTHER')
156
def do(self, action, tree):
157
"""Apply the specified action to the conflict.
159
:param action: The method name to call.
161
:param tree: The tree passed as a parameter to the method.
163
meth = getattr(self, 'action_%s' % action, None)
165
raise NotImplementedError(self.__class__.__name__ + '.' + action)
168
def action_done(self, tree):
169
"""Mark the conflict as solved once it has been handled."""
170
# This method does nothing but simplifies the design of upper levels.
174
return 'Text conflict in %(path)s' % self.__dict__
87
177
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
88
178
"""A Git working tree."""
330
420
def recurse_directory_to_add_files(directory):
331
421
# Recurse directory and add all files
332
422
# so we can check if they have changed.
333
for parent_info, file_infos in self.walkdirs(directory):
334
for relpath, basename, kind, lstat, fileid, kind in file_infos:
423
for parent_path, file_infos in self.walkdirs(directory):
424
for relpath, basename, kind, lstat, kind in file_infos:
335
425
# Is it versioned or ignored?
336
426
if self.is_versioned(relpath):
337
427
# Add nested content for deletion.
909
998
"""Walk the directories of this tree.
911
1000
returns a generator which yields items in the form:
912
((curren_directory_path, fileid),
913
[(file1_path, file1_name, file1_kind, (lstat), file1_id,
1001
(current_directory_path,
1002
[(file1_path, file1_name, file1_kind, (lstat),
914
1003
file1_kind), ... ])
916
1005
This API returns a generator, which is only valid during the current
974
1063
- (current_inv[0][0] < cur_disk_dir_relpath))
975
1064
if direction > 0:
976
1065
# disk is before inventory - unknown
977
dirblock = [(relpath, basename, kind, stat, None, None) for
1066
dirblock = [(relpath, basename, kind, stat, None) for
978
1067
relpath, basename, kind, stat, top_path in
979
1068
cur_disk_dir_content]
980
yield (cur_disk_dir_relpath, None), dirblock
1069
yield cur_disk_dir_relpath, dirblock
982
1071
current_disk = next(disk_iterator)
983
1072
except StopIteration:
984
1073
disk_finished = True
985
1074
elif direction < 0:
986
1075
# inventory is before disk - missing.
987
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1076
dirblock = [(relpath, basename, 'unknown', None, kind)
988
1077
for relpath, basename, dkind, stat, fileid, kind in
990
yield (current_inv[0][0], current_inv[0][1]), dirblock
1079
yield current_inv[0][0], dirblock
992
1081
current_inv = next(inventory_iterator)
993
1082
except StopIteration:
1005
1094
# versioned, present file
1006
1095
dirblock.append((inv_row[0],
1007
1096
inv_row[1], disk_row[2],
1008
disk_row[3], inv_row[4],
1097
disk_row[3], inv_row[5]))
1010
1098
elif len(path_elements[0]) == 5:
1011
1099
# unknown disk file
1012
1100
dirblock.append(
1013
1101
(path_elements[0][0], path_elements[0][1],
1014
1102
path_elements[0][2], path_elements[0][3],
1016
1104
elif len(path_elements[0]) == 6:
1017
1105
# versioned, absent file.
1018
1106
dirblock.append(
1019
1107
(path_elements[0][0], path_elements[0][1],
1020
'unknown', None, path_elements[0][4],
1021
1109
path_elements[0][5]))
1023
1111
raise NotImplementedError('unreachable code')
1024
yield current_inv[0], dirblock
1112
yield current_inv[0][0], dirblock
1026
1114
current_inv = next(inventory_iterator)
1027
1115
except StopIteration: