79
79
MutableGitIndexTree,
81
81
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."""
211
115
self.index = Index(self.control_transport.local_abspath('index'))
212
116
self._index_dirty = False
214
def _get_submodule_index(self, relpath):
215
if not isinstance(relpath, bytes):
216
raise TypeError(relpath)
218
info = self._submodule_info()[relpath]
220
index_path = os.path.join(self.basedir, decode_git_path(relpath), '.git', 'index')
222
index_path = self.control_transport.local_abspath(
223
posixpath.join('modules', decode_git_path(info[1]), 'index'))
224
return Index(index_path)
226
118
def lock_read(self):
227
119
"""Lock the repository for read operations.
426
318
def recurse_directory_to_add_files(directory):
427
319
# Recurse directory and add all files
428
320
# 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:
321
for parent_info, file_infos in self.walkdirs(directory):
322
for relpath, basename, kind, lstat, fileid, kind in file_infos:
431
323
# Is it versioned or ignored?
432
324
if self.is_versioned(relpath):
433
325
# Add nested content for deletion.
459
351
# Bail out if we are going to delete files we shouldn't
460
352
if not keep_files and not force:
461
for change in self.iter_changes(
462
self.basis_tree(), include_unchanged=True,
463
require_versioned=False, want_unversioned=True,
464
specific_files=files):
465
if change.versioned[0] is False:
353
for (file_id, path, content_change, versioned, parent_id, name,
354
kind, executable) in self.iter_changes(
355
self.basis_tree(), include_unchanged=True,
356
require_versioned=False, want_unversioned=True,
357
specific_files=files):
358
if versioned[0] is False:
466
359
# The record is unknown or newly added
467
files_to_backup.append(change.path[1])
360
files_to_backup.append(path[1])
468
361
files_to_backup.extend(
469
osutils.parent_directories(change.path[1]))
470
elif (change.changed_content and (change.kind[1] is not None)
471
and osutils.is_inside_any(files, change.path[1])):
362
osutils.parent_directories(path[1]))
363
elif (content_change and (kind[1] is not None)
364
and osutils.is_inside_any(files, path[1])):
472
365
# Versioned and changed, but not deleted, and still
473
366
# in one of the dirs to be deleted.
474
files_to_backup.append(change.path[1])
367
files_to_backup.append(path[1])
475
368
files_to_backup.extend(
476
osutils.parent_directories(change.path[1]))
369
osutils.parent_directories(path[1]))
619
512
def has_filename(self, filename):
620
513
return osutils.lexists(self.abspath(filename))
622
def _iter_files_recursive(self, from_dir=None, include_dirs=False,
623
recurse_nested=False):
515
def _iter_files_recursive(self, from_dir=None, include_dirs=False):
624
516
if from_dir is None:
626
if not isinstance(from_dir, str):
627
raise TypeError(from_dir)
628
518
encoded_from_dir = self.abspath(from_dir).encode(osutils._fs_enc)
629
519
for (dirpath, dirnames, filenames) in os.walk(encoded_from_dir):
630
520
dir_relpath = dirpath[len(self.basedir):].strip(b"/")
705
594
self._index_dirty = False
596
def has_or_had_id(self, file_id):
597
if self.has_id(file_id):
599
if self.had_id(file_id):
603
def had_id(self, file_id):
604
path = self._basis_fileid_map.lookup_path(file_id)
606
head = self.repository._git.head()
608
# Assume no if basis is not accessible
611
root_tree = self.store[head].tree
615
tree_lookup_path(self.store.__getitem__,
616
root_tree, path.encode('utf-8'))
707
622
def get_file_mtime(self, path):
708
623
"""See Tree.get_file_mtime."""
766
680
raise errors.GhostRevisionUnusableHere(revid)
768
682
def _reset_data(self):
684
head = self.repository._git.head()
686
self._basis_fileid_map = GitFileIdMap({}, self.mapping)
688
self._basis_fileid_map = self.mapping.get_fileid_map(
689
self.store.__getitem__, self.store[head].tree)
690
self._fileid_map = self._basis_fileid_map.copy()
771
692
def get_file_verifier(self, path, stat_value=None):
772
693
with self.lock_read():
773
(index, subpath) = self._lookup_index(encode_git_path(path))
694
(index, subpath) = self._lookup_index(path.encode('utf-8'))
775
696
return ("GIT", index[subpath].sha)
816
737
return os.lstat(self.abspath(path))
818
739
def _live_entry(self, path):
819
encoded_path = self.abspath(decode_git_path(path)).encode(
740
encoded_path = self.abspath(path.decode('utf-8')).encode(
821
742
return index_entry_from_path(encoded_path)
823
744
def is_executable(self, path):
824
745
with self.lock_read():
825
if self._supports_executable():
746
if getattr(self, "_supports_executable",
747
osutils.supports_executable)():
826
748
mode = self._lstat(path).st_mode
828
(index, subpath) = self._lookup_index(encode_git_path(path))
750
(index, subpath) = self._lookup_index(path.encode('utf-8'))
830
752
mode = index[subpath].mode
833
755
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
835
757
def _is_executable_from_path_and_stat(self, path, stat_result):
836
if self._supports_executable():
837
return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
758
if getattr(self, "_supports_executable",
759
osutils.supports_executable)():
760
return self._is_executable_from_path_and_stat_from_stat(
839
763
return self._is_executable_from_path_and_stat_from_basis(
840
764
path, stat_result)
842
def list_files(self, include_root=False, from_dir=None, recursive=True,
843
recurse_nested=False):
766
def list_files(self, include_root=False, from_dir=None, recursive=True):
844
767
if from_dir is None or from_dir == '.':
963
875
conflicts = _mod_conflicts.ConflictList()
964
876
for item_path, value in self.index.iteritems():
965
877
if value.flags & FLAG_STAGEMASK:
966
conflicts.append(TextConflict(decode_git_path(item_path)))
878
conflicts.append(_mod_conflicts.TextConflict(
879
item_path.decode('utf-8')))
969
882
def set_conflicts(self, conflicts):
971
884
for conflict in conflicts:
972
885
if conflict.typestring in ('text conflict', 'contents conflict'):
973
by_path.add(encode_git_path(conflict.path))
886
by_path.add(conflict.path.encode('utf-8'))
975
888
raise errors.UnsupportedOperation(self.set_conflicts, self)
976
889
with self.lock_tree_write():
1068
982
- (current_inv[0][0] < cur_disk_dir_relpath))
1069
983
if direction > 0:
1070
984
# disk is before inventory - unknown
1071
dirblock = [(relpath, basename, kind, stat, None) for
985
dirblock = [(relpath, basename, kind, stat, None, None) for
1072
986
relpath, basename, kind, stat, top_path in
1073
987
cur_disk_dir_content]
1074
yield cur_disk_dir_relpath, dirblock
988
yield (cur_disk_dir_relpath, None), dirblock
1076
990
current_disk = next(disk_iterator)
1077
991
except StopIteration:
1078
992
disk_finished = True
1079
993
elif direction < 0:
1080
994
# inventory is before disk - missing.
1081
dirblock = [(relpath, basename, 'unknown', None, kind)
995
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1082
996
for relpath, basename, dkind, stat, fileid, kind in
1084
yield current_inv[0][0], dirblock
998
yield (current_inv[0][0], current_inv[0][1]), dirblock
1086
1000
current_inv = next(inventory_iterator)
1087
1001
except StopIteration:
1099
1013
# versioned, present file
1100
1014
dirblock.append((inv_row[0],
1101
1015
inv_row[1], disk_row[2],
1102
disk_row[3], inv_row[5]))
1016
disk_row[3], inv_row[4],
1103
1018
elif len(path_elements[0]) == 5:
1104
1019
# unknown disk file
1105
1020
dirblock.append(
1106
1021
(path_elements[0][0], path_elements[0][1],
1107
1022
path_elements[0][2], path_elements[0][3],
1109
1024
elif len(path_elements[0]) == 6:
1110
1025
# versioned, absent file.
1111
1026
dirblock.append(
1112
1027
(path_elements[0][0], path_elements[0][1],
1028
'unknown', None, path_elements[0][4],
1114
1029
path_elements[0][5]))
1116
1031
raise NotImplementedError('unreachable code')
1117
yield current_inv[0][0], dirblock
1032
yield current_inv[0], dirblock
1119
1034
current_inv = next(inventory_iterator)
1120
1035
except StopIteration:
1127
1042
def _walkdirs(self, prefix=u""):
1128
1043
if prefix != u"":
1130
prefix = encode_git_path(prefix)
1045
prefix = prefix.encode('utf-8')
1131
1046
per_dir = defaultdict(set)
1132
1047
if prefix == b"":
1133
per_dir[(u'', self.path2id(''))] = set()
1048
per_dir[(u'', self.get_root_id())] = set()
1135
1050
def add_entry(path, kind):
1136
1051
if path == b'' or not path.startswith(prefix):
1138
1053
(dirname, child_name) = posixpath.split(path)
1139
1054
add_entry(dirname, 'directory')
1140
dirname = decode_git_path(dirname)
1055
dirname = dirname.decode("utf-8")
1141
1056
dir_file_id = self.path2id(dirname)
1142
1057
if not isinstance(value, tuple) or len(value) != 10:
1143
1058
raise ValueError(value)
1144
1059
per_dir[(dirname, dir_file_id)].add(
1145
(decode_git_path(path), decode_git_path(child_name),
1060
(path.decode("utf-8"), child_name.decode("utf-8"),
1147
self.path2id(decode_git_path(path)),
1062
self.path2id(path.decode("utf-8")),
1149
1064
with self.lock_read():
1150
1065
for path, value in self.index.iteritems():
1161
1076
def store_uncommitted(self):
1162
1077
raise errors.StoringUncommittedNotSupported(self)
1079
def apply_inventory_delta(self, changes):
1080
for (old_path, new_path, file_id, ie) in changes:
1081
if old_path is not None:
1082
(index, old_subpath) = self._lookup_index(
1083
old_path.encode('utf-8'))
1085
self._index_del_entry(index, old_subpath)
1089
self._versioned_dirs = None
1090
if new_path is not None and ie.kind != 'directory':
1091
if ie.kind == 'tree-reference':
1092
self._index_add_entry(
1094
reference_revision=ie.reference_revision)
1096
self._index_add_entry(new_path, ie.kind)
1164
1099
def annotate_iter(self, path,
1165
1100
default_revision=_mod_revision.CURRENT_REVISION):
1166
1101
"""See Tree.annotate_iter
1268
1202
(index, subpath) = self._lookup_index(entry.path)
1269
1203
index[subpath] = index_entry_from_stat(st, entry.sha, 0)
1271
def _update_git_tree(
1272
self, old_revision, new_revision, change_reporter=None,
1274
basis_tree = self.revision_tree(old_revision)
1275
if new_revision != old_revision:
1276
from .. import merge
1277
with basis_tree.lock_read():
1278
new_basis_tree = self.branch.basis_tree()
1284
change_reporter=change_reporter,
1285
show_base=show_base)
1287
1205
def pull(self, source, overwrite=False, stop_revision=None,
1288
1206
change_reporter=None, possible_transports=None, local=False,
1289
show_base=False, tag_selector=None):
1290
1208
with self.lock_write(), source.lock_read():
1291
1209
old_revision = self.branch.last_revision()
1210
basis_tree = self.basis_tree()
1292
1211
count = self.branch.pull(source, overwrite, stop_revision,
1293
1212
possible_transports=possible_transports,
1294
local=local, tag_selector=tag_selector)
1295
self._update_git_tree(
1296
old_revision=old_revision,
1297
new_revision=self.branch.last_revision(),
1298
change_reporter=change_reporter,
1299
show_base=show_base)
1214
new_revision = self.branch.last_revision()
1215
if new_revision != old_revision:
1216
with basis_tree.lock_read():
1217
new_basis_tree = self.branch.basis_tree()
1223
change_reporter=change_reporter,
1224
show_base=show_base)
1302
1227
def add_reference(self, sub_tree):
1316
1241
def _read_submodule_head(self, path):
1317
1242
return read_submodule_head(self.abspath(path))
1319
def get_reference_revision(self, path, branch=None):
1244
def get_reference_revision(self, path):
1320
1245
hexsha = self._read_submodule_head(path)
1321
1246
if hexsha is None:
1322
(index, subpath) = self._lookup_index(
1323
encode_git_path(path))
1325
raise errors.NoSuchFile(path)
1326
hexsha = index[subpath].sha
1247
return _mod_revision.NULL_REVISION
1327
1248
return self.branch.lookup_foreign_revision_id(hexsha)
1329
1250
def get_nested_tree(self, path):
1406
1326
new_parents = [revision_id]
1407
1327
tree.set_parent_ids(new_parents)
1409
def reference_parent(self, path, possible_transports=None):
1410
remote_url = self.get_reference_info(path)
1411
if remote_url is None:
1412
trace.warning("Unable to find submodule info for %s", path)
1414
return _mod_branch.Branch.open(remote_url, possible_transports=possible_transports)
1416
def get_reference_info(self, path):
1417
submodule_info = self._submodule_info()
1418
info = submodule_info.get(encode_git_path(path))
1421
return decode_git_path(info[0])
1423
def set_reference_info(self, tree_path, branch_location):
1424
path = self.abspath('.gitmodules')
1426
config = GitConfigFile.from_path(path)
1427
except EnvironmentError as e:
1428
if e.errno == errno.ENOENT:
1429
config = GitConfigFile()
1432
section = (b'submodule', encode_git_path(tree_path))
1433
if branch_location is None:
1439
branch_location = urlutils.join(
1440
urlutils.strip_segment_parameters(self.branch.user_url),
1444
b'path', encode_git_path(tree_path))
1447
b'url', branch_location.encode('utf-8'))
1448
config.write_to_path(path)
1449
self.add('.gitmodules')
1452
1330
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):