79
80
MutableGitIndexTree,
81
82
from .mapping import (
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__
177
return self.describe()
180
return "%s(%r)" % (type(self).__name__, self.path)
183
87
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
184
88
"""A Git working tree."""
426
334
def recurse_directory_to_add_files(directory):
427
335
# Recurse directory and add all files
428
336
# so we can check if they have changed.
429
for parent_path, file_infos in self.walkdirs(directory):
430
for relpath, basename, kind, lstat, kind in file_infos:
337
for parent_info, file_infos in self.walkdirs(directory):
338
for relpath, basename, kind, lstat, fileid, kind in file_infos:
431
339
# Is it versioned or ignored?
432
340
if self.is_versioned(relpath):
433
341
# Add nested content for deletion.
963
870
conflicts = _mod_conflicts.ConflictList()
964
871
for item_path, value in self.index.iteritems():
965
872
if value.flags & FLAG_STAGEMASK:
966
conflicts.append(TextConflict(decode_git_path(item_path)))
873
conflicts.append(_mod_conflicts.TextConflict(
874
item_path.decode('utf-8')))
969
877
def set_conflicts(self, conflicts):
971
879
for conflict in conflicts:
972
880
if conflict.typestring in ('text conflict', 'contents conflict'):
973
by_path.add(encode_git_path(conflict.path))
881
by_path.add(conflict.path.encode('utf-8'))
975
883
raise errors.UnsupportedOperation(self.set_conflicts, self)
976
884
with self.lock_tree_write():
1068
977
- (current_inv[0][0] < cur_disk_dir_relpath))
1069
978
if direction > 0:
1070
979
# disk is before inventory - unknown
1071
dirblock = [(relpath, basename, kind, stat, None) for
980
dirblock = [(relpath, basename, kind, stat, None, None) for
1072
981
relpath, basename, kind, stat, top_path in
1073
982
cur_disk_dir_content]
1074
yield cur_disk_dir_relpath, dirblock
983
yield (cur_disk_dir_relpath, None), dirblock
1076
985
current_disk = next(disk_iterator)
1077
986
except StopIteration:
1078
987
disk_finished = True
1079
988
elif direction < 0:
1080
989
# inventory is before disk - missing.
1081
dirblock = [(relpath, basename, 'unknown', None, kind)
990
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1082
991
for relpath, basename, dkind, stat, fileid, kind in
1084
yield current_inv[0][0], dirblock
993
yield (current_inv[0][0], current_inv[0][1]), dirblock
1086
995
current_inv = next(inventory_iterator)
1087
996
except StopIteration:
1099
1008
# versioned, present file
1100
1009
dirblock.append((inv_row[0],
1101
1010
inv_row[1], disk_row[2],
1102
disk_row[3], inv_row[5]))
1011
disk_row[3], inv_row[4],
1103
1013
elif len(path_elements[0]) == 5:
1104
1014
# unknown disk file
1105
1015
dirblock.append(
1106
1016
(path_elements[0][0], path_elements[0][1],
1107
1017
path_elements[0][2], path_elements[0][3],
1109
1019
elif len(path_elements[0]) == 6:
1110
1020
# versioned, absent file.
1111
1021
dirblock.append(
1112
1022
(path_elements[0][0], path_elements[0][1],
1023
'unknown', None, path_elements[0][4],
1114
1024
path_elements[0][5]))
1116
1026
raise NotImplementedError('unreachable code')
1117
yield current_inv[0][0], dirblock
1027
yield current_inv[0], dirblock
1119
1029
current_inv = next(inventory_iterator)
1120
1030
except StopIteration:
1138
1048
(dirname, child_name) = posixpath.split(path)
1139
1049
add_entry(dirname, 'directory')
1140
dirname = decode_git_path(dirname)
1050
dirname = dirname.decode("utf-8")
1141
1051
dir_file_id = self.path2id(dirname)
1142
1052
if not isinstance(value, tuple) or len(value) != 10:
1143
1053
raise ValueError(value)
1144
1054
per_dir[(dirname, dir_file_id)].add(
1145
(decode_git_path(path), decode_git_path(child_name),
1055
(path.decode("utf-8"), child_name.decode("utf-8"),
1147
self.path2id(decode_git_path(path)),
1057
self.path2id(path.decode("utf-8")),
1149
1059
with self.lock_read():
1150
1060
for path, value in self.index.iteritems():
1161
1071
def store_uncommitted(self):
1162
1072
raise errors.StoringUncommittedNotSupported(self)
1074
def apply_inventory_delta(self, changes):
1075
for (old_path, new_path, file_id, ie) in changes:
1076
if old_path is not None:
1077
(index, old_subpath) = self._lookup_index(
1078
old_path.encode('utf-8'))
1080
self._index_del_entry(index, old_subpath)
1084
self._versioned_dirs = None
1085
if new_path is not None and ie.kind != 'directory':
1086
if ie.kind == 'tree-reference':
1087
self._index_add_entry(
1089
reference_revision=ie.reference_revision)
1091
self._index_add_entry(new_path, ie.kind)
1164
1094
def annotate_iter(self, path,
1165
1095
default_revision=_mod_revision.CURRENT_REVISION):
1166
1096
"""See Tree.annotate_iter
1268
1198
(index, subpath) = self._lookup_index(entry.path)
1269
1199
index[subpath] = index_entry_from_stat(st, entry.sha, 0)
1271
def _update_git_tree(
1272
self, old_revision, new_revision, change_reporter=None,
1201
def _update_git_tree(self, old_revision, new_revision, change_reporter=None,
1274
1203
basis_tree = self.revision_tree(old_revision)
1275
1204
if new_revision != old_revision:
1276
from .. import merge
1277
1205
with basis_tree.lock_read():
1278
1206
new_basis_tree = self.branch.basis_tree()
1279
1207
merge.merge_inner(