86
86
IGNORE_FILENAME = ".gitignore"
89
class GitWorkingTree(MutableGitIndexTree,workingtree.WorkingTree):
89
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
90
90
"""A Git working tree."""
92
92
def __init__(self, controldir, repo, branch):
136
136
self._lock_mode = 'w'
137
137
self._lock_count = 1
139
self._index_file = GitFile(self.control_transport.local_abspath('index'), 'wb')
139
self._index_file = GitFile(
140
self.control_transport.local_abspath('index'), 'wb')
140
141
except FileLocked:
141
142
raise errors.LockContention('index')
142
143
self._read_index()
143
144
elif self._lock_mode == 'r':
144
145
raise errors.ReadOnlyError(self)
147
self._lock_count += 1
148
149
def lock_tree_write(self):
149
150
self.branch.lock_read()
222
223
def _set_merges_from_parent_ids(self, rhs_parent_ids):
224
merges = [self.branch.lookup_bzr_revision_id(revid)[0] for revid in rhs_parent_ids]
225
merges = [self.branch.lookup_bzr_revision_id(
226
revid)[0] for revid in rhs_parent_ids]
225
227
except errors.NoSuchRevision as e:
226
228
raise errors.GhostRevisionUnusableHere(e.revision)
228
230
self.control_transport.put_bytes('MERGE_HEAD', b'\n'.join(merges),
229
mode=self.controldir._get_file_mode())
231
mode=self.controldir._get_file_mode())
232
234
self.control_transport.delete('MERGE_HEAD')
248
250
with self.lock_tree_write():
249
251
self._check_parents_for_ghosts(revision_ids,
250
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
252
allow_leftmost_as_ghost=allow_leftmost_as_ghost)
251
253
for revision_id in revision_ids:
252
254
_mod_revision.check_not_reserved_id(revision_id)
288
291
def remove(self, files, verbose=False, to_file=None, keep_files=True,
290
293
"""Remove nominated files from the working tree metadata.
292
295
:param files: File paths relative to the basedir.
303
306
def backup(file_to_backup):
304
307
abs_path = self.abspath(file_to_backup)
305
backup_name = self.controldir._available_backup_name(file_to_backup)
308
backup_name = self.controldir._available_backup_name(
306
310
osutils.rename(abs_path, self.abspath(backup_name))
307
311
return "removed %s (but kept a copy: %s)" % (
308
312
file_to_backup, backup_name)
348
352
if not keep_files and not force:
349
353
for (file_id, path, content_change, versioned, parent_id, name,
350
354
kind, executable) in self.iter_changes(self.basis_tree(),
351
include_unchanged=True, require_versioned=False,
352
want_unversioned=True, specific_files=files):
355
include_unchanged=True, require_versioned=False,
356
want_unversioned=True, specific_files=files):
353
357
if versioned[0] == False:
354
358
# The record is unknown or newly added
355
359
files_to_backup.append(path[1])
356
files_to_backup.extend(osutils.parent_directories(path[1]))
357
elif (content_change and (kind[1] is not None) and
358
osutils.is_inside_any(files, path[1])):
360
files_to_backup.extend(
361
osutils.parent_directories(path[1]))
362
elif (content_change and (kind[1] is not None)
363
and osutils.is_inside_any(files, path[1])):
359
364
# Versioned and changed, but not deleted, and still
360
365
# in one of the dirs to be deleted.
361
366
files_to_backup.append(path[1])
362
files_to_backup.extend(osutils.parent_directories(path[1]))
367
files_to_backup.extend(
368
osutils.parent_directories(path[1]))
435
442
abspath = self.abspath(filepath)
436
443
kind = osutils.file_kind(abspath)
437
444
if kind in ("file", "symlink"):
438
(index, subpath) = self._lookup_index(filepath.encode('utf-8'))
445
(index, subpath) = self._lookup_index(
446
filepath.encode('utf-8'))
439
447
if subpath in index:
440
448
# Already present
444
452
self._index_add_entry(filepath, kind)
445
453
added.append(filepath)
446
454
elif kind == "directory":
447
(index, subpath) = self._lookup_index(filepath.encode('utf-8'))
455
(index, subpath) = self._lookup_index(
456
filepath.encode('utf-8'))
448
457
if subpath not in index:
449
458
call_action(filepath, kind)
455
464
abs_user_dir = self.abspath(user_dir)
456
465
if user_dir != '':
458
transport = _mod_transport.get_transport_from_path(abs_user_dir)
467
transport = _mod_transport.get_transport_from_path(
459
469
_mod_controldir.ControlDirFormat.find_format(transport)
461
471
except errors.NotBranchError:
529
540
"""Yield all unversioned files in this WorkingTree.
531
542
with self.lock_read():
532
index_paths = set([p.decode('utf-8') for p, i in self._recurse_index_entries()])
543
index_paths = set([p.decode('utf-8')
544
for p, i in self._recurse_index_entries()])
533
545
all_paths = set(self._iter_files_recursive(include_dirs=True))
534
546
for p in (all_paths - index_paths):
535
547
if not self._has_dir(p.encode('utf-8')):
542
554
if kinds[pos] is None:
543
555
fullpath = osutils.normpath(self.abspath(f))
545
kind = osutils.file_kind(fullpath)
557
kind = osutils.file_kind(fullpath)
546
558
except OSError as e:
547
559
if e.errno == errno.ENOENT:
548
560
raise errors.NoSuchFile(fullpath)
591
tree_lookup_path(self.store.__getitem__, root_tree, path.encode('utf-8'))
603
tree_lookup_path(self.store.__getitem__,
604
root_tree, path.encode('utf-8'))
613
626
ignore_globs = set()
614
627
ignore_globs.update(ignores.get_runtime_ignores())
615
628
ignore_globs.update(ignores.get_user_ignores())
616
self._global_ignoreglobster = globbing.ExceptionGlobster(ignore_globs)
629
self._global_ignoreglobster = globbing.ExceptionGlobster(
617
631
match = self._global_ignoreglobster.match(filename)
618
632
if match is not None:
745
759
yield "", "V", root_ie.kind, root_ie.file_id, root_ie
746
760
dir_ids[u""] = root_ie.file_id
748
path_iterator = sorted(self._iter_files_recursive(from_dir, include_dirs=True))
762
path_iterator = sorted(
763
self._iter_files_recursive(from_dir, include_dirs=True))
750
765
path_iterator = sorted([os.path.join(from_dir, name.decode(osutils._fs_enc)) for name in
751
os.listdir(self.abspath(from_dir).encode(osutils._fs_enc))
752
if not self.controldir.is_control_filename(name.decode(osutils._fs_enc))
753
and not self.mapping.is_special_file(name.decode(osutils._fs_enc))])
766
os.listdir(self.abspath(
767
from_dir).encode(osutils._fs_enc))
768
if not self.controldir.is_control_filename(name.decode(osutils._fs_enc)) and
769
not self.mapping.is_special_file(name.decode(osutils._fs_enc))])
754
770
for path in path_iterator:
756
772
encoded_path = path.encode("utf-8")
833
849
subpath = posixpath.relpath(decoded_item_path, path)
834
850
if '/' in subpath:
835
851
dirname = subpath.split('/', 1)[0]
836
file_ie = self._get_dir_ie(posixpath.join(path, dirname), parent_id)
852
file_ie = self._get_dir_ie(
853
posixpath.join(path, dirname), parent_id)
838
855
(unused_parent, name) = posixpath.split(decoded_item_path)
839
856
file_ie = self._get_file_ie(
847
864
conflicts = _mod_conflicts.ConflictList()
848
865
for item_path, value in self.index.iteritems():
849
866
if value.flags & FLAG_STAGEMASK:
850
conflicts.append(_mod_conflicts.TextConflict(item_path.decode('utf-8')))
867
conflicts.append(_mod_conflicts.TextConflict(
868
item_path.decode('utf-8')))
853
871
def set_conflicts(self, conflicts):
869
887
self.index[path] = (value[:9] + (value[9] | FLAG_STAGEMASK, ))
871
self.index[path] = (value[:9] + (value[9] &~ FLAG_STAGEMASK, ))
889
self.index[path] = (value[:9] + (value[9] & ~ FLAG_STAGEMASK, ))
873
891
def add_conflicts(self, new_conflicts):
874
892
with self.lock_tree_write():
875
893
for conflict in new_conflicts:
876
894
if conflict.typestring in ('text conflict', 'contents conflict'):
878
self._set_conflicted(conflict.path.encode('utf-8'), True)
896
self._set_conflicted(
897
conflict.path.encode('utf-8'), True)
880
raise errors.UnsupportedOperation(self.add_conflicts, self)
899
raise errors.UnsupportedOperation(
900
self.add_conflicts, self)
882
902
raise errors.UnsupportedOperation(self.add_conflicts, self)
907
927
current_disk = next(disk_iterator)
908
928
disk_finished = False
909
929
except OSError as e:
910
if not (e.errno == errno.ENOENT or
911
(sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
930
if not (e.errno == errno.ENOENT
931
or (sys.platform == 'win32' and e.errno == ERROR_PATH_NOT_FOUND)):
913
933
current_disk = None
914
934
disk_finished = True
927
947
cur_disk_dir_content) = ((None, None), None)
928
948
if not disk_finished:
929
949
# strip out .bzr dirs
930
if (cur_disk_dir_path_from_top[top_strip_len:] == '' and
931
len(cur_disk_dir_content) > 0):
950
if (cur_disk_dir_path_from_top[top_strip_len:] == ''
951
and len(cur_disk_dir_content) > 0):
932
952
# osutils.walkdirs can be made nicer -
933
953
# yield the path-from-prefix rather than the pathjoined
935
955
bzrdir_loc = bisect_left(cur_disk_dir_content,
937
if (bzrdir_loc < len(cur_disk_dir_content)
938
and self.controldir.is_control_filename(
957
if (bzrdir_loc < len(cur_disk_dir_content) and
958
self.controldir.is_control_filename(
939
959
cur_disk_dir_content[bzrdir_loc][0])):
940
960
# we dont yield the contents of, or, .bzr itself.
941
961
del cur_disk_dir_content[bzrdir_loc]
946
966
# everything is missing
949
direction = ((current_inv[0][0] > cur_disk_dir_relpath) -
950
(current_inv[0][0] < cur_disk_dir_relpath))
969
direction = ((current_inv[0][0] > cur_disk_dir_relpath)
970
- (current_inv[0][0] < cur_disk_dir_relpath))
951
971
if direction > 0:
952
972
# disk is before inventory - unknown
953
973
dirblock = [(relpath, basename, kind, stat, None, None) for
954
relpath, basename, kind, stat, top_path in
955
cur_disk_dir_content]
974
relpath, basename, kind, stat, top_path in
975
cur_disk_dir_content]
956
976
yield (cur_disk_dir_relpath, None), dirblock
958
978
current_disk = next(disk_iterator)
961
981
elif direction < 0:
962
982
# inventory is before disk - missing.
963
983
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
964
for relpath, basename, dkind, stat, fileid, kind in
984
for relpath, basename, dkind, stat, fileid, kind in
966
986
yield (current_inv[0][0], current_inv[0][1]), dirblock
968
988
current_inv = next(inventory_iterator)
973
993
# merge the inventory and disk data together
975
995
for relpath, subiterator in itertools.groupby(sorted(
976
current_inv[1] + cur_disk_dir_content,
977
key=operator.itemgetter(0)), operator.itemgetter(1)):
996
current_inv[1] + cur_disk_dir_content,
997
key=operator.itemgetter(0)), operator.itemgetter(1)):
978
998
path_elements = list(subiterator)
979
999
if len(path_elements) == 2:
980
1000
inv_row, disk_row = path_elements
981
1001
# versioned, present file
982
1002
dirblock.append((inv_row[0],
983
inv_row[1], disk_row[2],
984
disk_row[3], inv_row[4],
1003
inv_row[1], disk_row[2],
1004
disk_row[3], inv_row[4],
986
1006
elif len(path_elements[0]) == 5:
987
1007
# unknown disk file
988
1008
dirblock.append((path_elements[0][0],
989
path_elements[0][1], path_elements[0][2],
990
path_elements[0][3], None, None))
1009
path_elements[0][1], path_elements[0][2],
1010
path_elements[0][3], None, None))
991
1011
elif len(path_elements[0]) == 6:
992
1012
# versioned, absent file.
993
1013
dirblock.append((path_elements[0][0],
994
path_elements[0][1], 'unknown', None,
995
path_elements[0][4], path_elements[0][5]))
1014
path_elements[0][1], 'unknown', None,
1015
path_elements[0][4], path_elements[0][5]))
997
1017
raise NotImplementedError('unreachable code')
998
1018
yield current_inv[0], dirblock
1012
1032
per_dir = defaultdict(set)
1013
1033
if prefix == b"":
1014
1034
per_dir[(u'', self.get_root_id())] = set()
1015
1036
def add_entry(path, kind):
1016
1037
if path == b'' or not path.startswith(prefix):
1023
1044
raise ValueError(value)
1024
1045
per_dir[(dirname, dir_file_id)].add(
1025
1046
(path.decode("utf-8"), child_name.decode("utf-8"),
1027
self.path2id(path.decode("utf-8")),
1048
self.path2id(path.decode("utf-8")),
1029
1050
with self.lock_read():
1030
1051
for path, value in self.index.iteritems():
1031
1052
if self.mapping.is_special_file(path):
1044
1065
def apply_inventory_delta(self, changes):
1045
1066
for (old_path, new_path, file_id, ie) in changes:
1046
1067
if old_path is not None:
1047
(index, old_subpath) = self._lookup_index(old_path.encode('utf-8'))
1068
(index, old_subpath) = self._lookup_index(
1069
old_path.encode('utf-8'))
1049
1071
self._index_del_entry(index, old_subpath)
1050
1072
except KeyError:
1054
1076
if new_path is not None and ie.kind != 'directory':
1055
1077
if ie.kind == 'tree-reference':
1056
1078
self._index_add_entry(
1058
reference_revision=ie.reference_revision)
1080
reference_revision=ie.reference_revision)
1060
1082
self._index_add_entry(new_path, ie.kind)
1078
1100
parent_tree = self.revision_tree(parent_id)
1079
1101
except errors.NoSuchRevisionInTree:
1080
1102
parent_tree = self.branch.repository.revision_tree(
1082
1104
with parent_tree.lock_read():
1083
1105
# TODO(jelmer): Use rename/copy tracker to find path name in parent
1084
1106
parent_path = path
1147
1169
if S_ISGITLINK(entry.mode):
1148
pass # TODO(jelmer): record and return submodule paths
1170
pass # TODO(jelmer): record and return submodule paths
1150
1172
# Let's at least try to use the working tree file:
1152
st = self._lstat(self.abspath(entry.path.decode('utf-8')))
1174
st = self._lstat(self.abspath(
1175
entry.path.decode('utf-8')))
1153
1176
except OSError:
1154
1177
# But if it doesn't exist, we'll make something up.
1155
1178
obj = self.store[entry.sha]
1156
1179
st = os.stat_result((entry.mode, 0, 0, 0,
1157
0, 0, len(obj.as_raw_string()), 0,
1181
obj.as_raw_string()), 0,
1159
1183
(index, subpath) = self._lookup_index(entry.path)
1160
1184
index[subpath] = index_entry_from_stat(st, entry.sha, 0)
1173
1197
with basis_tree.lock_read():
1174
1198
new_basis_tree = self.branch.basis_tree()
1175
1199
merge.merge_inner(
1180
change_reporter=change_reporter,
1181
show_base=show_base)
1204
change_reporter=change_reporter,
1205
show_base=show_base)
1184
1208
def add_reference(self, sub_tree):
1191
1215
sub_tree_path = self.relpath(sub_tree.basedir)
1192
1216
except errors.PathNotChild:
1193
1217
raise BadReferenceTarget(
1194
self, sub_tree, 'Target not inside tree.')
1218
self, sub_tree, 'Target not inside tree.')
1196
1220
self._add([sub_tree_path], [None], ['tree-reference'])
1274
1298
other_tree = self.revision_tree(revision_id)
1275
1299
except errors.NoSuchRevision:
1276
1300
other_tree = self.branch.repository.revision_tree(
1279
1303
merge.transform_tree(tree, other_tree)
1280
1304
if revision_id == _mod_revision.NULL_REVISION:
1319
1343
if revision_id is not None:
1320
1344
branch.set_last_revision(revision_id)
1321
1345
wt = GitWorkingTree(
1322
a_controldir, a_controldir.open_repository(), branch)
1346
a_controldir, a_controldir.open_repository(), branch)
1323
1347
for hook in MutableTree.hooks['post_build_tree']: