90
91
IGNORE_FILENAME = ".gitignore"
93
def ensure_normalized_path(path):
94
"""Check whether path is normalized.
96
:raises InvalidNormalization: When path is not normalized, and cannot be
97
accessed on this platform by the normalized path.
98
:return: The NFC normalised version of path.
100
norm_path, can_access = osutils.normalized_filename(path)
101
if norm_path != path:
105
raise errors.InvalidNormalization(path)
109
class GitWorkingTree(workingtree.WorkingTree):
94
class GitWorkingTree(MutableGitIndexTree,workingtree.WorkingTree):
110
95
"""A Git working tree."""
112
97
def __init__(self, controldir, repo, branch, index):
98
MutableGitIndexTree.__init__(self)
113
99
basedir = controldir.root_transport.local_abspath('.')
114
100
self.basedir = osutils.realpath(basedir)
115
101
self.controldir = controldir
284
270
yield self.path2id(path)
286
def _index_add_entry(self, path, kind, flags=0):
287
assert self._lock_mode is not None
288
assert isinstance(path, basestring)
289
if kind == "directory":
290
# Git indexes don't contain directories
295
file, stat_val = self.get_file_with_stat(path)
296
except (errors.NoSuchFile, IOError):
297
# TODO: Rather than come up with something here, use the old index
299
stat_val = os.stat_result(
300
(stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
301
blob.set_raw_string(file.read())
302
elif kind == "symlink":
305
stat_val = os.lstat(self.abspath(path))
306
except (errors.NoSuchFile, OSError):
307
# TODO: Rather than come up with something here, use the
309
stat_val = os.stat_result(
310
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
312
self.get_symlink_target(path).encode("utf-8"))
314
raise AssertionError("unknown kind '%s'" % kind)
315
# Add object to the repository if it didn't exist yet
316
if not blob.id in self.store:
317
self.store.add_object(blob)
318
# Add an entry to the index or update the existing entry
319
ensure_normalized_path(path)
320
encoded_path = path.encode("utf-8")
321
if b'\r' in encoded_path or b'\n' in encoded_path:
322
# TODO(jelmer): Why do we need to do this?
323
trace.mutter('ignoring path with invalid newline in it: %r', path)
325
self.index[encoded_path] = index_entry_from_stat(
326
stat_val, blob.id, flags)
327
if self._versioned_dirs is not None:
328
self._ensure_versioned_dir(encoded_path)
330
def _ensure_versioned_dir(self, dirname):
331
if dirname in self._versioned_dirs:
334
self._ensure_versioned_dir(posixpath.dirname(dirname))
335
self._versioned_dirs.add(dirname)
337
def _load_dirs(self):
338
assert self._lock_mode is not None
339
self._versioned_dirs = set()
341
self._ensure_versioned_dir(posixpath.dirname(p))
343
def _unversion_path(self, path):
344
assert self._lock_mode is not None
345
encoded_path = path.encode("utf-8")
348
del self.index[encoded_path]
350
# A directory, perhaps?
351
for p in list(self.index):
352
if p.startswith(encoded_path+b"/"):
357
self._versioned_dirs = None
360
def unversion(self, paths, file_ids=None):
361
with self.lock_tree_write():
363
if self._unversion_path(path) == 0:
364
raise errors.NoSuchFile(path)
365
self._versioned_dirs = None
368
def update_basis_by_delta(self, revid, delta):
369
# TODO(jelmer): This shouldn't be called, it's inventory specific.
370
for (old_path, new_path, file_id, ie) in delta:
371
if old_path is not None and old_path.encode('utf-8') in self.index:
372
del self.index[old_path.encode('utf-8')]
373
self._versioned_dirs = None
374
if new_path is not None and ie.kind != 'directory':
375
self._index_add_entry(new_path, ie.kind)
377
self._set_merges_from_parent_ids([])
379
272
def check_state(self):
380
273
"""Check that the working state is/isn't valid."""
734
def has_id(self, file_id):
736
self.id2path(file_id)
737
except errors.NoSuchId:
742
def id2path(self, file_id):
743
assert type(file_id) is str, "file id not a string: %r" % file_id
744
file_id = osutils.safe_utf8(file_id)
745
with self.lock_read():
747
path = self._fileid_map.lookup_path(file_id)
749
raise errors.NoSuchId(self, file_id)
750
path = path.decode('utf-8')
751
if self.is_versioned(path):
753
raise errors.NoSuchId(self, file_id)
755
603
def get_file_mtime(self, path, file_id=None):
756
604
"""See Tree.get_file_mtime."""
758
return os.lstat(self.abspath(path)).st_mtime
606
return self._lstat(path).st_mtime
759
607
except OSError, (num, msg):
760
608
if num == errno.ENOENT:
761
609
raise errors.NoSuchFile(path)
844
692
def revision_tree(self, revid):
845
693
return self.repository.revision_tree(revid)
847
def is_versioned(self, path):
848
with self.lock_read():
849
path = path.rstrip('/').encode('utf-8')
850
return (path in self.index or self._has_dir(path))
852
695
def filter_unversioned_files(self, files):
853
696
return set([p for p in files if not self.is_versioned(p)])
855
def _get_dir_ie(self, path, parent_id):
856
file_id = self.path2id(path)
857
return inventory.InventoryDirectory(file_id,
858
posixpath.basename(path).strip("/"), parent_id)
860
def _add_missing_parent_ids(self, path, dir_ids):
863
parent = posixpath.dirname(path).strip("/")
864
ret = self._add_missing_parent_ids(parent, dir_ids)
865
parent_id = dir_ids[parent]
866
ie = self._get_dir_ie(path, parent_id)
867
dir_ids[path] = ie.file_id
868
ret.append((path, ie))
871
def _get_file_ie(self, name, path, value, parent_id):
872
assert isinstance(name, unicode)
873
assert isinstance(path, unicode)
874
assert isinstance(value, tuple) and len(value) == 10
875
(ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
876
file_id = self.path2id(path)
877
if type(file_id) != str:
879
kind = mode_kind(mode)
880
ie = inventory.entry_factory[kind](file_id, name, parent_id)
881
if kind == 'symlink':
882
ie.symlink_target = self.get_symlink_target(path, file_id)
885
data = self.get_file_text(path, file_id)
886
except errors.NoSuchFile:
889
if e.errno != errno.ENOENT:
893
data = self.repository._git.object_store[sha].data
894
ie.text_sha1 = osutils.sha_string(data)
895
ie.text_size = len(data)
896
ie.executable = bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
900
698
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
901
699
mode = stat_result.st_mode
902
700
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
1046
847
if not found_any:
1047
848
raise errors.NoSuchFile(path)
1049
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
1051
raise NotImplementedError(self.iter_entries_by_dir)
1052
with self.lock_read():
1053
if specific_file_ids is not None:
1055
for file_id in specific_file_ids:
1059
specific_paths.append(self.id2path(file_id))
1060
except errors.NoSuchId:
1062
if specific_paths in ([u""], []):
1063
specific_paths = None
1065
specific_paths = set(specific_paths)
1067
specific_paths = None
1068
root_ie = self._get_dir_ie(u"", None)
1070
if specific_paths is None:
1071
ret[(None, u"")] = root_ie
1072
dir_ids = {u"": root_ie.file_id}
1073
for path, value in self.index.iteritems():
1074
if self.mapping.is_special_file(path):
1076
path = path.decode("utf-8")
1077
if specific_paths is not None and not path in specific_paths:
1079
(parent, name) = posixpath.split(path)
1081
file_ie = self._get_file_ie(name, path, value, None)
1082
except errors.NoSuchFile:
1084
if yield_parents or specific_file_ids is None:
1085
for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
1087
ret[(posixpath.dirname(dir_path), dir_path)] = dir_ie
1088
file_ie.parent_id = self.path2id(parent)
1089
ret[(posixpath.dirname(path), path)] = file_ie
1090
return ((path, ie) for ((_, path), ie) in sorted(ret.items()))
1092
850
def conflicts(self):
1093
851
with self.lock_read():