1
# Copyright (C) 2008-2011 Jelmer Vernooij <jelmer@samba.org>
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
"""An adapter between a Git index and a Bazaar Working Tree"""
20
from __future__ import absolute_import
22
from cStringIO import (
25
from collections import defaultdict
27
from dulwich.errors import NotGitRepository
28
from dulwich.ignore import (
31
from dulwich.index import (
35
index_entry_from_stat,
37
from dulwich.object_store import (
40
from dulwich.objects import (
45
from dulwich.repo import Repo
54
conflicts as _mod_conflicts,
66
from ...decorators import (
69
from ...mutabletree import needs_tree_write_lock
76
changes_from_git_changes,
77
tree_delta_from_git_changes,
79
from .mapping import (
84
IGNORE_FILENAME = ".gitignore"
87
class GitWorkingTree(workingtree.WorkingTree):
88
"""A Git working tree."""
90
def __init__(self, controldir, repo, branch, index):
91
self.basedir = controldir.root_transport.local_abspath('.').encode(osutils._fs_enc)
92
self.controldir = controldir
93
self.repository = repo
94
self.store = self.repository._git.object_store
95
self.mapping = self.repository.get_mapping()
97
self._transport = controldir.transport
98
self._format = GitWorkingTreeFormat()
100
self._versioned_dirs = None
101
self.views = self._make_views()
102
self._rules_searcher = None
103
self._detect_case_handling()
105
self._fileid_map = self._basis_fileid_map.copy()
106
self._lock_mode = None
109
def supports_tree_reference(self):
113
"""Lock the repository for read operations.
115
:return: A breezy.lock.LogicalLockResult.
117
if not self._lock_mode:
118
self._lock_mode = 'r'
122
self._lock_count += 1
123
self.branch.lock_read()
124
return lock.LogicalLockResult(self.unlock)
126
def lock_tree_write(self):
127
if not self._lock_mode:
128
self._lock_mode = 'w'
131
elif self._lock_mode == 'r':
132
raise errors.ReadOnlyError(self)
135
self.branch.lock_read()
136
return lock.LogicalLockResult(self.unlock)
138
def lock_write(self, token=None):
139
if not self._lock_mode:
140
self._lock_mode = 'w'
143
elif self._lock_mode == 'r':
144
raise errors.ReadOnlyError(self)
147
self.branch.lock_write()
148
return lock.LogicalLockResult(self.unlock)
151
return self._lock_count >= 1
153
def get_physical_lock_status(self):
157
if not self._lock_count:
158
return lock.cant_unlock_not_held(self)
161
self._lock_count -= 1
162
if self._lock_count > 0:
164
self._lock_mode = None
169
def _detect_case_handling(self):
171
self._transport.stat(".git/cOnFiG")
172
except errors.NoSuchFile:
173
self.case_sensitive = True
175
self.case_sensitive = False
177
def merge_modified(self):
180
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
181
self.set_parent_ids([p for p, t in parents_list])
183
def iter_children(self, file_id):
184
dpath = self.id2path(file_id) + "/"
185
if dpath in self.index:
187
for path in self.index:
188
if not path.startswith(dpath):
190
if "/" in path[len(dpath):]:
191
# Not a direct child but something further down
193
yield self.path2id(path)
195
def _index_add_entry(self, path, kind):
196
assert self._lock_mode is not None
197
assert isinstance(path, basestring)
198
if kind == "directory":
199
# Git indexes don't contain directories
204
file, stat_val = self.get_file_with_stat(None, path)
205
except (errors.NoSuchFile, IOError):
206
# TODO: Rather than come up with something here, use the old index
208
stat_val = os.stat_result(
209
(stat.S_IFREG | 0644, 0, 0, 0, 0, 0, 0, 0, 0, 0))
210
blob.set_raw_string(file.read())
211
elif kind == "symlink":
214
stat_val = os.lstat(self.abspath(path))
215
except (errors.NoSuchFile, OSError):
216
# TODO: Rather than come up with something here, use the
218
stat_val = os.stat_result(
219
(stat.S_IFLNK, 0, 0, 0, 0, 0, 0, 0, 0, 0))
221
self.get_symlink_target(None, path).encode("utf-8"))
223
raise AssertionError("unknown kind '%s'" % kind)
224
# Add object to the repository if it didn't exist yet
225
if not blob.id in self.store:
226
self.store.add_object(blob)
227
# Add an entry to the index or update the existing entry
229
encoded_path = path.encode("utf-8")
230
self.index[encoded_path] = index_entry_from_stat(
231
stat_val, blob.id, flags)
232
if self._versioned_dirs is not None:
233
self._ensure_versioned_dir(encoded_path)
235
def _ensure_versioned_dir(self, dirname):
236
if dirname in self._versioned_dirs:
239
self._ensure_versioned_dir(posixpath.dirname(dirname))
240
self._versioned_dirs.add(dirname)
242
def _load_dirs(self):
243
assert self._lock_mode is not None
244
self._versioned_dirs = set()
246
self._ensure_versioned_dir(posixpath.dirname(p))
248
def _unversion_path(self, path):
249
assert self._lock_mode is not None
250
encoded_path = path.encode("utf-8")
252
del self.index[encoded_path]
254
# A directory, perhaps?
255
for p in list(self.index):
256
if p.startswith(encoded_path+"/"):
258
# FIXME: remove empty directories
260
@needs_tree_write_lock
261
def unversion(self, file_ids):
262
for file_id in file_ids:
263
path = self.id2path(file_id)
264
self._unversion_path(path)
267
def check_state(self):
268
"""Check that the working state is/isn't valid."""
271
@needs_tree_write_lock
272
def remove(self, files, verbose=False, to_file=None, keep_files=True,
274
"""Remove nominated files from the working tree metadata.
276
:param files: File paths relative to the basedir.
277
:param keep_files: If true, the files will also be kept.
278
:param force: Delete files and directories, even if they are changed
279
and even if the directories are not empty.
281
all_files = set() # specified and nested files
283
if isinstance(files, basestring):
289
files = list(all_files)
292
return # nothing to do
294
# Sort needed to first handle directory content before the directory
295
files.sort(reverse=True)
297
def backup(file_to_backup):
298
abs_path = self.abspath(file_to_backup)
299
backup_name = self.controldir._available_backup_name(file_to_backup)
300
osutils.rename(abs_path, self.abspath(backup_name))
301
return "removed %s (but kept a copy: %s)" % (
302
file_to_backup, backup_name)
305
fid = self.path2id(f)
307
message = "%s is not versioned." % (f,)
309
abs_path = self.abspath(f)
311
# having removed it, it must be either ignored or unknown
312
if self.is_ignored(f):
316
# XXX: Really should be a more abstract reporter interface
317
kind_ch = osutils.kind_marker(self.kind(fid))
318
to_file.write(new_status + ' ' + f + kind_ch + '\n')
320
# FIXME: _unversion_path() is O(size-of-index) for directories
321
self._unversion_path(f)
322
message = "removed %s" % (f,)
323
if osutils.lexists(abs_path):
324
if (osutils.isdir(abs_path) and
325
len(os.listdir(abs_path)) > 0):
327
osutils.rmtree(abs_path)
328
message = "deleted %s" % (f,)
333
osutils.delete_any(abs_path)
334
message = "deleted %s" % (f,)
336
# print only one message (if any) per file.
337
if message is not None:
341
def _add(self, files, ids, kinds):
342
for (path, file_id, kind) in zip(files, ids, kinds):
343
if file_id is not None:
344
raise workingtree.SettingFileIdUnsupported()
345
self._index_add_entry(path, kind)
347
@needs_tree_write_lock
348
def smart_add(self, file_list, recurse=True, action=None, save=True):
352
for filepath in osutils.canonical_relpaths(self.basedir, file_list):
353
abspath = self.abspath(filepath)
354
kind = osutils.file_kind(abspath)
355
if action is not None:
356
file_id = action(self, None, filepath, kind)
359
if kind in ("file", "symlink"):
361
self._index_add_entry(filepath, file_id, kind)
362
added.append(filepath)
363
elif kind == "directory":
365
user_dirs.append(filepath)
367
raise errors.BadFileKindError(filename=abspath, kind=kind)
368
for user_dir in user_dirs:
369
abs_user_dir = self.abspath(user_dir)
370
for name in os.listdir(abs_user_dir):
371
subp = os.path.join(user_dir, name)
372
if self.is_control_filename(subp) or self.mapping.is_special_file(subp):
374
ignore_glob = self.is_ignored(subp)
375
if ignore_glob is not None:
376
ignored.setdefault(ignore_glob, []).append(subp)
378
abspath = self.abspath(subp)
379
kind = osutils.file_kind(abspath)
380
if kind == "directory":
381
user_dirs.append(subp)
383
if action is not None:
384
file_id = action(self, None, filepath, kind)
388
self._index_add_entry(subp, file_id, kind)
391
return added, ignored
393
def _set_root_id(self, file_id):
394
self._fileid_map.set_file_id("", file_id)
396
@needs_tree_write_lock
397
def move(self, from_paths, to_dir=None, after=False):
399
to_abs = self.abspath(to_dir)
400
if not os.path.isdir(to_abs):
401
raise errors.BzrMoveFailedError('', to_dir,
402
errors.NotADirectory(to_abs))
404
for from_rel in from_paths:
405
from_tail = os.path.split(from_rel)[-1]
406
to_rel = os.path.join(to_dir, from_tail)
407
self.rename_one(from_rel, to_rel, after=after)
408
rename_tuples.append((from_rel, to_rel))
412
@needs_tree_write_lock
413
def rename_one(self, from_rel, to_rel, after=False):
414
from_path = from_rel.encode("utf-8")
415
to_path = to_rel.encode("utf-8")
416
if not self.has_filename(to_rel):
417
raise errors.BzrMoveFailedError(from_rel, to_rel,
418
errors.NoSuchFile(to_rel))
419
if not from_path in self.index:
420
raise errors.BzrMoveFailedError(from_rel, to_rel,
421
errors.NotVersionedError(path=from_rel))
423
os.rename(self.abspath(from_rel), self.abspath(to_rel))
424
self.index[to_path] = self.index[from_path]
425
del self.index[from_path]
428
def get_root_id(self):
429
return self.path2id("")
431
def _has_dir(self, path):
434
if self._versioned_dirs is None:
436
return path in self._versioned_dirs
439
def path2id(self, path):
440
if type(path) is list:
441
path = u"/".join(path)
442
encoded_path = path.encode("utf-8")
443
if self._is_versioned(encoded_path):
444
return self._fileid_map.lookup_file_id(encoded_path)
447
def _iter_files_recursive(self, from_dir=None):
450
for (dirpath, dirnames, filenames) in os.walk(self.abspath(from_dir)):
451
dir_relpath = dirpath[len(self.basedir):].strip("/")
452
if self.controldir.is_control_filename(dir_relpath):
454
for filename in filenames:
455
if not self.mapping.is_special_file(filename):
456
yield os.path.join(dir_relpath, filename)
460
"""Yield all unversioned files in this WorkingTree.
462
return set(self._iter_files_recursive()) - set(self.index)
464
@needs_tree_write_lock
466
# TODO: Maybe this should only write on dirty ?
467
if self._lock_mode != 'w':
468
raise errors.NotWriteLocked(self)
473
for path in self.index:
474
yield self.path2id(path)
476
for path in self._versioned_dirs:
477
yield self.path2id(path)
479
def has_or_had_id(self, file_id):
480
if self.has_id(file_id):
482
if self.had_id(file_id):
486
def had_id(self, file_id):
487
path = self._basis_fileid_map.lookup_file_id(file_id)
489
head = self.repository._git.head()
491
# Assume no if basis is not accessible
495
root_tree = self.store[head].tree
497
tree_lookup_path(self.store.__getitem__, root_tree, path)
503
def has_id(self, file_id):
505
self.id2path(file_id)
506
except errors.NoSuchId:
512
def id2path(self, file_id):
513
assert type(file_id) is str, "file id not a string: %r" % file_id
514
file_id = osutils.safe_utf8(file_id)
515
path = self._fileid_map.lookup_path(file_id)
516
# FIXME: What about directories?
517
if self._is_versioned(path):
518
return path.decode("utf-8")
519
raise errors.NoSuchId(self, file_id)
521
def get_file_mtime(self, file_id, path=None):
522
"""See Tree.get_file_mtime."""
524
path = self.id2path(file_id)
525
return os.lstat(self.abspath(path)).st_mtime
527
def is_ignored(self, filename):
528
r"""Check whether the filename matches an ignore pattern.
530
If the file is ignored, returns the pattern which caused it to
531
be ignored, otherwise None. So this can simply be used as a
532
boolean if desired."""
533
if getattr(self, '_global_ignoreglobster', None) is None:
535
ignore_globs.update(ignores.get_runtime_ignores())
536
ignore_globs.update(ignores.get_user_ignores())
537
self._global_ignoreglobster = globbing.ExceptionGlobster(ignore_globs)
538
match = self._global_ignoreglobster.match(filename)
539
if match is not None:
541
if osutils.file_kind(self.abspath(filename)) == 'directory':
543
ignore_manager = self._get_ignore_manager()
544
ps = list(ignore_manager.find_matching(filename))
547
if not ps[-1].is_exclude:
551
def _get_ignore_manager(self):
552
ignoremanager = getattr(self, '_ignoremanager', None)
553
if ignoremanager is not None:
556
ignore_manager = IgnoreFilterManager.from_repo(self.repository._git)
557
self._ignoremanager = ignore_manager
558
return ignore_manager
560
def set_last_revision(self, revid):
561
self._change_last_revision(revid)
563
def _reset_data(self):
565
head = self.repository._git.head()
566
except KeyError, name:
567
raise errors.NotBranchError("branch %s at %s" % (name,
568
self.repository.base))
570
self._basis_fileid_map = GitFileIdMap({}, self.mapping)
572
self._basis_fileid_map = self.mapping.get_fileid_map(
573
self.store.__getitem__, self.store[head].tree)
576
def get_file_verifier(self, file_id, path=None, stat_value=None):
578
path = self.id2path(file_id)
580
return ("GIT", self.index[path][-2])
582
if self._has_dir(path):
584
raise errors.NoSuchId(self, file_id)
587
def get_file_sha1(self, file_id, path=None, stat_value=None):
589
path = self.id2path(file_id)
590
abspath = self.abspath(path).encode(osutils._fs_enc)
592
return osutils.sha_file_by_name(abspath)
593
except OSError, (num, msg):
594
if num in (errno.EISDIR, errno.ENOENT):
598
def revision_tree(self, revid):
599
return self.repository.revision_tree(revid)
601
def _is_versioned(self, path):
602
assert self._lock_mode is not None
603
return (path in self.index or self._has_dir(path))
605
def filter_unversioned_files(self, files):
606
return set([p for p in files if not self._is_versioned(p.encode("utf-8"))])
608
def _get_dir_ie(self, path, parent_id):
609
file_id = self.path2id(path)
610
return inventory.InventoryDirectory(file_id,
611
posixpath.basename(path).strip("/"), parent_id)
613
def _add_missing_parent_ids(self, path, dir_ids):
616
parent = posixpath.dirname(path).strip("/")
617
ret = self._add_missing_parent_ids(parent, dir_ids)
618
parent_id = dir_ids[parent]
619
ie = self._get_dir_ie(path, parent_id)
620
dir_ids[path] = ie.file_id
621
ret.append((path, ie))
624
def _get_file_ie(self, name, path, value, parent_id):
625
assert isinstance(name, unicode)
626
assert isinstance(path, unicode)
627
assert isinstance(value, tuple) and len(value) == 10
628
(ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
629
file_id = self.path2id(path)
630
if type(file_id) != str:
632
kind = mode_kind(mode)
633
ie = inventory.entry_factory[kind](file_id, name, parent_id)
634
if kind == 'symlink':
635
ie.symlink_target = self.get_symlink_target(file_id)
637
data = self.get_file_text(file_id, path)
638
ie.text_sha1 = osutils.sha_string(data)
639
ie.text_size = len(data)
640
ie.executable = self.is_executable(file_id, path)
644
def _is_executable_from_path_and_stat_from_stat(self, path, stat_result):
645
mode = stat_result.st_mode
646
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
649
def stored_kind(self, file_id, path=None):
651
path = self.id2path(file_id)
653
return mode_kind(self.index[path.encode("utf-8")][4])
655
# Maybe it's a directory?
656
if self._has_dir(path):
658
raise errors.NoSuchId(self, file_id)
660
def is_executable(self, file_id, path=None):
661
if getattr(self, "_supports_executable", osutils.supports_executable)():
663
path = self.id2path(file_id)
664
mode = os.lstat(self.abspath(path)).st_mode
665
return bool(stat.S_ISREG(mode) and stat.S_IEXEC & mode)
667
basis_tree = self.basis_tree()
668
if file_id in basis_tree:
669
return basis_tree.is_executable(file_id)
670
# Default to not executable
673
def _is_executable_from_path_and_stat(self, path, stat_result):
674
if getattr(self, "_supports_executable", osutils.supports_executable)():
675
return self._is_executable_from_path_and_stat_from_stat(path, stat_result)
677
return self._is_executable_from_path_and_stat_from_basis(path, stat_result)
680
def list_files(self, include_root=False, from_dir=None, recursive=True):
684
fk_entries = {'directory': workingtree.TreeDirectory,
685
'file': workingtree.TreeFile,
686
'symlink': workingtree.TreeLink}
687
root_ie = self._get_dir_ie(u"", None)
688
if include_root and not from_dir:
689
yield "", "V", root_ie.kind, root_ie.file_id, root_ie
690
dir_ids[u""] = root_ie.file_id
692
path_iterator = self._iter_files_recursive(from_dir)
697
start = os.path.join(self.basedir, from_dir)
698
path_iterator = sorted([os.path.join(from_dir, name) for name in
699
os.listdir(start) if not self.controldir.is_control_filename(name)
700
and not self.mapping.is_special_file(name)])
701
for path in path_iterator:
703
value = self.index[path]
706
path = path.decode("utf-8")
707
parent, name = posixpath.split(path)
708
for dir_path, dir_ie in self._add_missing_parent_ids(parent, dir_ids):
709
yield dir_path, "V", dir_ie.kind, dir_ie.file_id, dir_ie
710
if value is not None:
711
ie = self._get_file_ie(name, path, value, dir_ids[parent])
712
yield path, "V", ie.kind, ie.file_id, ie
714
kind = osutils.file_kind(self.abspath(path))
715
ie = fk_entries[kind]()
716
yield path, ("I" if self.is_ignored(path) else "?"), kind, None, ie
719
def all_file_ids(self):
720
ids = {u"": self.path2id("")}
721
for path in self.index:
722
if self.mapping.is_special_file(path):
724
path = path.decode("utf-8")
725
parent = posixpath.dirname(path).strip("/")
726
for e in self._add_missing_parent_ids(parent, ids):
728
ids[path] = self.path2id(path)
729
return set(ids.values())
731
def _directory_is_tree_reference(self, path):
732
# FIXME: Check .gitsubmodules for path
736
def iter_entries_by_dir(self, specific_file_ids=None, yield_parents=False):
737
# FIXME: Is return order correct?
739
raise NotImplementedError(self.iter_entries_by_dir)
740
if specific_file_ids is not None:
741
specific_paths = [self.id2path(file_id) for file_id in specific_file_ids]
742
if specific_paths in ([u""], []):
743
specific_paths = None
745
specific_paths = set(specific_paths)
747
specific_paths = None
748
root_ie = self._get_dir_ie(u"", None)
749
if specific_paths is None:
751
dir_ids = {u"": root_ie.file_id}
752
for path, value in self.index.iteritems():
753
if self.mapping.is_special_file(path):
755
path = path.decode("utf-8")
756
if specific_paths is not None and not path in specific_paths:
758
(parent, name) = posixpath.split(path)
760
file_ie = self._get_file_ie(name, path, value, None)
763
for (dir_path, dir_ie) in self._add_missing_parent_ids(parent,
765
yield dir_path, dir_ie
766
file_ie.parent_id = self.path2id(parent)
772
return _mod_conflicts.ConflictList()
774
def update_basis_by_delta(self, new_revid, delta):
775
# The index just contains content, which won't have changed.
779
def get_canonical_inventory_path(self, path):
781
if p.lower() == path.lower():
787
def _walkdirs(self, prefix=""):
790
per_dir = defaultdict(list)
791
for path, value in self.index.iteritems():
792
if self.mapping.is_special_file(path):
794
if not path.startswith(prefix):
796
(dirname, child_name) = posixpath.split(path)
797
dirname = dirname.decode("utf-8")
798
dir_file_id = self.path2id(dirname)
799
assert isinstance(value, tuple) and len(value) == 10
800
(ctime, mtime, dev, ino, mode, uid, gid, size, sha, flags) = value
801
stat_result = os.stat_result((mode, ino,
802
dev, 1, uid, gid, size,
804
per_dir[(dirname, dir_file_id)].append(
805
(path.decode("utf-8"), child_name.decode("utf-8"),
806
mode_kind(mode), stat_result,
807
self.path2id(path.decode("utf-8")),
809
return per_dir.iteritems()
811
def _lookup_entry(self, path, update_index=False):
812
assert type(path) == str
813
entry = self.index[path]
814
index_mode = entry[-6]
815
index_sha = entry[-2]
816
disk_path = os.path.join(self.basedir, path)
818
disk_stat = os.lstat(disk_path)
819
except OSError, (num, msg):
820
if num in (errno.EISDIR, errno.ENOENT):
823
disk_mtime = disk_stat.st_mtime
824
if isinstance(entry[1], tuple):
825
index_mtime = entry[1][0]
827
index_mtime = int(entry[1])
828
mtime_delta = (disk_mtime - index_mtime)
829
disk_mode = cleanup_mode(disk_stat.st_mode)
830
if mtime_delta > 0 or disk_mode != index_mode:
831
if stat.S_ISDIR(disk_mode):
833
subrepo = Repo(disk_path)
834
except NotGitRepository:
837
disk_mode = S_IFGITLINK
838
git_id = subrepo.head()
839
elif stat.S_ISLNK(disk_mode):
840
blob = Blob.from_string(os.readlink(disk_path).encode('utf-8'))
842
elif stat.S_ISREG(disk_mode):
843
with open(disk_path, 'r') as f:
844
blob = Blob.from_string(f.read())
850
self.index[path] = index_entry_from_stat(disk_stat, git_id, flags, disk_mode)
851
return (git_id, disk_mode)
852
return (index_sha, index_mode)
855
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):
857
_tree_class = GitWorkingTree
859
supports_versioned_directories = False
861
supports_setting_file_ids = False
864
def _matchingbzrdir(self):
865
from .dir import LocalGitControlDirFormat
866
return LocalGitControlDirFormat()
868
def get_format_description(self):
869
return "Git Working Tree"
871
def initialize(self, a_controldir, revision_id=None, from_branch=None,
872
accelerator_tree=None, hardlink=False):
873
"""See WorkingTreeFormat.initialize()."""
874
if not isinstance(a_controldir, LocalGitDir):
875
raise errors.IncompatibleFormat(self, a_controldir)
876
index = Index(a_controldir.root_transport.local_abspath(".git/index"))
878
return GitWorkingTree(a_controldir, a_controldir.open_repository(),
879
a_controldir.open_branch(), index)
882
class InterIndexGitTree(tree.InterTree):
883
"""InterTree that works between a Git revision tree and an index."""
885
def __init__(self, source, target):
886
super(InterIndexGitTree, self).__init__(source, target)
887
self._index = target.index
890
def is_compatible(cls, source, target):
891
from .repository import GitRevisionTree
892
return (isinstance(source, GitRevisionTree) and
893
isinstance(target, GitWorkingTree))
896
def compare(self, want_unchanged=False, specific_files=None,
897
extra_trees=None, require_versioned=False, include_root=False,
898
want_unversioned=False):
899
# FIXME: Handle include_root
900
changes = changes_between_git_tree_and_index(
901
self.source.store, self.source.tree,
902
self.target, want_unchanged=want_unchanged,
903
want_unversioned=want_unversioned)
904
source_fileid_map = self.source._fileid_map
905
target_fileid_map = self.target._fileid_map
906
ret = tree_delta_from_git_changes(changes, self.target.mapping,
907
(source_fileid_map, target_fileid_map),
908
specific_file=specific_files, require_versioned=require_versioned)
910
for e in self.target.extras():
911
ret.unversioned.append((e, None,
912
osutils.file_kind(self.target.abspath(e))))
916
def iter_changes(self, include_unchanged=False, specific_files=None,
917
pb=None, extra_trees=[], require_versioned=True,
918
want_unversioned=False):
919
changes = changes_between_git_tree_and_index(
920
self.source.store, self.source.tree,
921
self.target, want_unchanged=include_unchanged,
922
want_unversioned=want_unversioned)
923
return changes_from_git_changes(changes, self.target.mapping,
924
specific_file=specific_files)
927
tree.InterTree.register_optimiser(InterIndexGitTree)
930
def changes_between_git_tree_and_index(object_store, tree, target,
931
want_unchanged=False, want_unversioned=False, update_index=False):
932
"""Determine the changes between a git tree and a working tree with index.
936
names = target.index._byname.keys()
937
for (name, mode, sha) in changes_from_tree(names, target._lookup_entry,
938
object_store, tree, want_unchanged=want_unchanged):
939
if name == (None, None):
941
yield (name, mode, sha)