1
# Copyright (C) 2006-2011 Canonical Ltd
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""MutableTree object.
19
See MutableTree for more details.
23
from bzrlib.lazy_import import lazy_import
24
lazy_import(globals(), """
34
inventory as _mod_inventory,
38
transport as _mod_transport,
43
from bzrlib.decorators import needs_read_lock, needs_write_lock
46
def needs_tree_write_lock(unbound):
47
"""Decorate unbound to take out and release a tree_write lock."""
48
def tree_write_locked(self, *args, **kwargs):
49
self.lock_tree_write()
51
return unbound(self, *args, **kwargs)
54
tree_write_locked.__doc__ = unbound.__doc__
55
tree_write_locked.__name__ = unbound.__name__
56
return tree_write_locked
59
class MutableTree(tree.Tree):
60
"""A MutableTree is a specialisation of Tree which is able to be mutated.
62
Generally speaking these mutations are only possible within a lock_write
63
context, and will revert if the lock is broken abnormally - but this cannot
64
be guaranteed - depending on the exact implementation of the mutable state.
66
The most common form of Mutable Tree is WorkingTree, see bzrlib.workingtree.
67
For tests we also have MemoryTree which is a MutableTree whose contents are
70
For now, we are not treating MutableTree as an interface to provide
71
conformance tests for - rather we are testing MemoryTree specifically, and
72
interface testing implementations of WorkingTree.
74
A mutable tree always has an associated Branch and BzrDir object - the
75
branch and bzrdir attributes.
77
def __init__(self, *args, **kw):
78
super(MutableTree, self).__init__(*args, **kw)
79
# Is this tree on a case-insensitive or case-preserving file-system?
80
# Sub-classes may initialize to False if they detect they are being
81
# used on media which doesn't differentiate the case of names.
82
self.case_sensitive = True
84
def is_control_filename(self, filename):
85
"""True if filename is the name of a control file in this tree.
87
:param filename: A filename within the tree. This is a relative path
88
from the root of this tree.
90
This is true IF and ONLY IF the filename is part of the meta data
91
that bzr controls in this tree. I.E. a random .bzr directory placed
92
on disk will not be a control file for this tree.
94
raise NotImplementedError(self.is_control_filename)
96
@needs_tree_write_lock
97
def add(self, files, ids=None, kinds=None):
98
"""Add paths to the set of versioned paths.
100
Note that the command line normally calls smart_add instead,
101
which can automatically recurse.
103
This adds the files to the inventory, so that they will be
104
recorded by the next commit.
106
:param files: List of paths to add, relative to the base of the tree.
107
:param ids: If set, use these instead of automatically generated ids.
108
Must be the same length as the list of files, but may
109
contain None for ids that are to be autogenerated.
110
:param kinds: Optional parameter to specify the kinds to be used for
113
TODO: Perhaps callback with the ids and paths as they're added.
115
if isinstance(files, basestring):
116
# XXX: Passing a single string is inconsistent and should be
118
if not (ids is None or isinstance(ids, basestring)):
119
raise AssertionError()
120
if not (kinds is None or isinstance(kinds, basestring)):
121
raise AssertionError()
125
if kinds is not None:
128
files = [path.strip('/') for path in files]
131
ids = [None] * len(files)
133
if not (len(ids) == len(files)):
134
raise AssertionError()
136
kinds = [None] * len(files)
137
elif not len(kinds) == len(files):
138
raise AssertionError()
140
# generic constraint checks:
141
if self.is_control_filename(f):
142
raise errors.ForbiddenControlFileError(filename=f)
143
fp = osutils.splitpath(f)
144
# fill out file kinds for all files [not needed when we stop
145
# caring about the instantaneous file kind within a uncommmitted tree
147
self._gather_kinds(files, kinds)
148
self._add(files, ids, kinds)
150
def add_reference(self, sub_tree):
151
"""Add a TreeReference to the tree, pointing at sub_tree"""
152
raise errors.UnsupportedOperation(self.add_reference, self)
154
def _add_reference(self, sub_tree):
155
"""Standard add_reference implementation, for use by subclasses"""
157
sub_tree_path = self.relpath(sub_tree.basedir)
158
except errors.PathNotChild:
159
raise errors.BadReferenceTarget(self, sub_tree,
160
'Target not inside tree.')
161
sub_tree_id = sub_tree.get_root_id()
162
if sub_tree_id == self.get_root_id():
163
raise errors.BadReferenceTarget(self, sub_tree,
164
'Trees have the same root id.')
165
if self.has_id(sub_tree_id):
166
raise errors.BadReferenceTarget(self, sub_tree,
167
'Root id already present in tree')
168
self._add([sub_tree_path], [sub_tree_id], ['tree-reference'])
170
def _add(self, files, ids, kinds):
171
"""Helper function for add - updates the inventory.
173
:param files: sequence of pathnames, relative to the tree root
174
:param ids: sequence of suggested ids for the files (may be None)
175
:param kinds: sequence of inventory kinds of the files (i.e. may
176
contain "tree-reference")
178
raise NotImplementedError(self._add)
180
def apply_inventory_delta(self, changes):
181
"""Apply changes to the inventory as an atomic operation.
183
:param changes: An inventory delta to apply to the working tree's
186
:seealso Inventory.apply_delta: For details on the changes parameter.
188
raise NotImplementedError(self.apply_inventory_delta)
191
def commit(self, message=None, revprops=None, *args,
193
# avoid circular imports
194
from bzrlib import commit
195
possible_master_transports=[]
196
revprops = commit.Commit.update_revprops(
199
kwargs.pop('authors', None),
200
kwargs.pop('author', None),
201
kwargs.get('local', False),
202
possible_master_transports)
203
# args for wt.commit start at message from the Commit.commit method,
204
args = (message, ) + args
205
for hook in MutableTree.hooks['start_commit']:
207
committed_id = commit.Commit().commit(working_tree=self,
209
possible_master_transports=possible_master_transports,
211
post_hook_params = PostCommitHookParams(self)
212
for hook in MutableTree.hooks['post_commit']:
213
hook(post_hook_params)
216
def _gather_kinds(self, files, kinds):
217
"""Helper function for add - sets the entries of kinds."""
218
raise NotImplementedError(self._gather_kinds)
221
def has_changes(self, _from_tree=None):
222
"""Quickly check that the tree contains at least one commitable change.
224
:param _from_tree: tree to compare against to find changes (default to
225
the basis tree and is intended to be used by tests).
227
:return: True if a change is found. False otherwise
229
# Check pending merges
230
if len(self.get_parent_ids()) > 1:
232
if _from_tree is None:
233
_from_tree = self.basis_tree()
234
changes = self.iter_changes(_from_tree)
236
change = changes.next()
237
# Exclude root (talk about black magic... --vila 20090629)
238
if change[4] == (None, None):
239
change = changes.next()
241
except StopIteration:
246
def check_changed_or_out_of_date(self, strict, opt_name,
247
more_error, more_warning):
248
"""Check the tree for uncommitted changes and branch synchronization.
250
If strict is None and not set in the config files, a warning is issued.
251
If strict is True, an error is raised.
252
If strict is False, no checks are done and no warning is issued.
254
:param strict: True, False or None, searched in branch config if None.
256
:param opt_name: strict option name to search in config file.
258
:param more_error: Details about how to avoid the check.
260
:param more_warning: Details about what is happening.
263
strict = self.branch.get_config().get_user_option_as_bool(opt_name)
264
if strict is not False:
266
if (self.has_changes()):
267
err_class = errors.UncommittedChanges
268
elif self.last_revision() != self.branch.last_revision():
269
# The tree has lost sync with its branch, there is little
270
# chance that the user is aware of it but he can still force
271
# the action with --no-strict
272
err_class = errors.OutOfDateTree
273
if err_class is not None:
275
err = err_class(self, more=more_warning)
276
# We don't want to interrupt the user if he expressed no
277
# preference about strict.
278
trace.warning('%s', err._format())
280
err = err_class(self, more=more_error)
284
def last_revision(self):
285
"""Return the revision id of the last commit performed in this tree.
287
In early tree formats the result of last_revision is the same as the
288
branch last_revision, but that is no longer the case for modern tree
291
last_revision returns the left most parent id, or None if there are no
294
last_revision was deprecated as of 0.11. Please use get_parent_ids
297
raise NotImplementedError(self.last_revision)
299
def lock_tree_write(self):
300
"""Lock the working tree for write, and the branch for read.
302
This is useful for operations which only need to mutate the working
303
tree. Taking out branch write locks is a relatively expensive process
304
and may fail if the branch is on read only media. So branch write locks
305
should only be taken out when we are modifying branch data - such as in
306
operations like commit, pull, uncommit and update.
308
raise NotImplementedError(self.lock_tree_write)
310
def lock_write(self):
311
"""Lock the tree and its branch. This allows mutating calls to be made.
313
Some mutating methods will take out implicit write locks, but in
314
general you should always obtain a write lock before calling mutating
317
raise NotImplementedError(self.lock_write)
320
def mkdir(self, path, file_id=None):
321
"""Create a directory in the tree. if file_id is None, one is assigned.
323
:param path: A unicode file path.
324
:param file_id: An optional file-id.
325
:return: the file id of the new directory.
327
raise NotImplementedError(self.mkdir)
329
def _observed_sha1(self, file_id, path, (sha1, stat_value)):
330
"""Tell the tree we have observed a paths sha1.
332
The intent of this function is to allow trees that have a hashcache to
333
update the hashcache during commit. If the observed file is too new
334
(based on the stat_value) to be safely hash-cached the tree will ignore
337
The default implementation does nothing.
339
:param file_id: The file id
340
:param path: The file path
341
:param sha1: The sha 1 that was observed.
342
:param stat_value: A stat result for the file the sha1 was read from.
347
def put_file_bytes_non_atomic(self, file_id, bytes):
348
"""Update the content of a file in the tree.
350
Note that the file is written in-place rather than being
351
written to a temporary location and renamed. As a consequence,
352
readers can potentially see the file half-written.
354
:param file_id: file-id of the file
355
:param bytes: the new file contents
357
raise NotImplementedError(self.put_file_bytes_non_atomic)
359
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
360
"""Set the parents ids of the working tree.
362
:param revision_ids: A list of revision_ids.
364
raise NotImplementedError(self.set_parent_ids)
366
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
367
"""Set the parents of the working tree.
369
:param parents_list: A list of (revision_id, tree) tuples.
370
If tree is None, then that element is treated as an unreachable
371
parent tree - i.e. a ghost.
373
raise NotImplementedError(self.set_parent_trees)
375
def smart_add(self, file_list, recurse=True, action=None, save=True):
376
"""Version file_list, optionally recursing into directories.
378
This is designed more towards DWIM for humans than API clarity.
379
For the specific behaviour see the help for cmd_add().
381
:param file_list: List of zero or more paths. *NB: these are
382
interpreted relative to the process cwd, not relative to the
383
tree.* (Add and most other tree methods use tree-relative
385
:param action: A reporter to be called with the inventory, parent_ie,
386
path and kind of the path being added. It may return a file_id if
387
a specific one should be used.
388
:param save: Save the inventory after completing the adds. If False
389
this provides dry-run functionality by doing the add and not saving
391
:return: A tuple - files_added, ignored_files. files_added is the count
392
of added files, and ignored_files is a dict mapping files that were
393
ignored to the rule that caused them to be ignored.
395
raise NotImplementedError(self.smart_add)
398
class MutableInventoryTree(MutableTree, tree.InventoryTree):
400
@needs_tree_write_lock
401
def apply_inventory_delta(self, changes):
402
"""Apply changes to the inventory as an atomic operation.
404
:param changes: An inventory delta to apply to the working tree's
407
:seealso Inventory.apply_delta: For details on the changes parameter.
411
inv.apply_delta(changes)
412
self._write_inventory(inv)
414
def _fix_case_of_inventory_path(self, path):
415
"""If our tree isn't case sensitive, return the canonical path"""
416
if not self.case_sensitive:
417
path = self.get_canonical_inventory_path(path)
420
@needs_tree_write_lock
421
def smart_add(self, file_list, recurse=True, action=None, save=True):
422
"""Version file_list, optionally recursing into directories.
424
This is designed more towards DWIM for humans than API clarity.
425
For the specific behaviour see the help for cmd_add().
427
:param file_list: List of zero or more paths. *NB: these are
428
interpreted relative to the process cwd, not relative to the
429
tree.* (Add and most other tree methods use tree-relative
431
:param action: A reporter to be called with the inventory, parent_ie,
432
path and kind of the path being added. It may return a file_id if
433
a specific one should be used.
434
:param save: Save the inventory after completing the adds. If False
435
this provides dry-run functionality by doing the add and not saving
437
:return: A tuple - files_added, ignored_files. files_added is the count
438
of added files, and ignored_files is a dict mapping files that were
439
ignored to the rule that caused them to be ignored.
441
# Not all mutable trees can have conflicts
442
if getattr(self, 'conflicts', None) is not None:
443
# Collect all related files without checking whether they exist or
444
# are versioned. It's cheaper to do that once for all conflicts
445
# than trying to find the relevant conflict for each added file.
446
conflicts_related = set()
447
for c in self.conflicts():
448
conflicts_related.update(c.associated_filenames())
450
conflicts_related = None
451
adder = _SmartAddHelper(self, action, conflicts_related)
452
adder.add(file_list, recurse=recurse)
454
invdelta = adder.get_inventory_delta()
455
self.apply_inventory_delta(invdelta)
456
return adder.added, adder.ignored
458
def update_basis_by_delta(self, new_revid, delta):
459
"""Update the parents of this tree after a commit.
461
This gives the tree one parent, with revision id new_revid. The
462
inventory delta is applied to the current basis tree to generate the
463
inventory for the parent new_revid, and all other parent trees are
466
All the changes in the delta should be changes synchronising the basis
467
tree with some or all of the working tree, with a change to a directory
468
requiring that its contents have been recursively included. That is,
469
this is not a general purpose tree modification routine, but a helper
470
for commit which is not required to handle situations that do not arise
473
See the inventory developers documentation for the theory behind
476
:param new_revid: The new revision id for the trees parent.
477
:param delta: An inventory delta (see apply_inventory_delta) describing
478
the changes from the current left most parent revision to new_revid.
480
# if the tree is updated by a pull to the branch, as happens in
481
# WorkingTree2, when there was no separation between branch and tree,
482
# then just clear merges, efficiency is not a concern for now as this
483
# is legacy environments only, and they are slow regardless.
484
if self.last_revision() == new_revid:
485
self.set_parent_ids([new_revid])
487
# generic implementation based on Inventory manipulation. See
488
# WorkingTree classes for optimised versions for specific format trees.
489
basis = self.basis_tree()
491
# TODO: Consider re-evaluating the need for this with CHKInventory
492
# we don't strictly need to mutate an inventory for this
493
# it only makes sense when apply_delta is cheaper than get_inventory()
494
inventory = _mod_inventory.mutable_inventory_from_tree(basis)
496
inventory.apply_delta(delta)
497
rev_tree = revisiontree.InventoryRevisionTree(self.branch.repository,
498
inventory, new_revid)
499
self.set_parent_trees([(new_revid, rev_tree)])
502
class MutableTreeHooks(hooks.Hooks):
503
"""A dictionary mapping a hook name to a list of callables for mutabletree
508
"""Create the default hooks.
511
hooks.Hooks.__init__(self, "bzrlib.mutabletree", "MutableTree.hooks")
512
self.add_hook('start_commit',
513
"Called before a commit is performed on a tree. The start commit "
514
"hook is able to change the tree before the commit takes place. "
515
"start_commit is called with the bzrlib.mutabletree.MutableTree "
516
"that the commit is being performed on.", (1, 4))
517
self.add_hook('post_commit',
518
"Called after a commit is performed on a tree. The hook is "
519
"called with a bzrlib.mutabletree.PostCommitHookParams object. "
520
"The mutable tree the commit was performed on is available via "
521
"the mutable_tree attribute of that object.", (2, 0))
524
# install the default hooks into the MutableTree class.
525
MutableTree.hooks = MutableTreeHooks()
528
class PostCommitHookParams(object):
529
"""Parameters for the post_commit hook.
531
To access the parameters, use the following attributes:
533
* mutable_tree - the MutableTree object
536
def __init__(self, mutable_tree):
537
"""Create the parameters for the post_commit hook."""
538
self.mutable_tree = mutable_tree
541
class _SmartAddHelper(object):
542
"""Helper for MutableTree.smart_add."""
544
def get_inventory_delta(self):
545
return self._invdelta.values()
547
def _get_ie(self, inv_path):
548
"""Retrieve the most up to date inventory entry for a path.
550
:param inv_path: Normalized inventory path
551
:return: Inventory entry (with possibly invalid .children for
554
entry = self._invdelta.get(inv_path)
555
if entry is not None:
557
# Find a 'best fit' match if the filesystem is case-insensitive
558
inv_path = self.tree._fix_case_of_inventory_path(inv_path)
559
file_id = self.tree.path2id(inv_path)
560
if file_id is not None:
561
return self.tree.iter_entries_by_dir([file_id]).next()[1]
564
def _convert_to_directory(self, this_ie, inv_path):
565
"""Convert an entry to a directory.
567
:param this_ie: Inventory entry
568
:param inv_path: Normalized path for the inventory entry
569
:return: The new inventory entry
571
# Same as in _add_one below, if the inventory doesn't
572
# think this is a directory, update the inventory
573
this_ie = _mod_inventory.InventoryDirectory(
574
this_ie.file_id, this_ie.name, this_ie.parent_id)
575
self._invdelta[inv_path] = (inv_path, inv_path, this_ie.file_id,
579
def _add_one_and_parent(self, parent_ie, path, kind, inv_path):
580
"""Add a new entry to the inventory and automatically add unversioned parents.
582
:param parent_ie: Parent inventory entry if known, or None. If
583
None, the parent is looked up by name and used if present, otherwise it
584
is recursively added.
586
:param kind: Kind of new entry (file, directory, etc)
588
:return: Inventory entry for path and a list of paths which have been added.
590
# Nothing to do if path is already versioned.
591
# This is safe from infinite recursion because the tree root is
593
inv_dirname = osutils.dirname(inv_path)
594
dirname, basename = osutils.split(path)
595
if parent_ie is None:
596
# slower but does not need parent_ie
597
this_ie = self._get_ie(inv_path)
598
if this_ie is not None:
600
# its really not there : add the parent
601
# note that the dirname use leads to some extra str copying etc but as
602
# there are a limited number of dirs we can be nested under, it should
603
# generally find it very fast and not recurse after that.
604
parent_ie = self._add_one_and_parent(None,
605
dirname, 'directory',
607
# if the parent exists, but isn't a directory, we have to do the
608
# kind change now -- really the inventory shouldn't pretend to know
609
# the kind of wt files, but it does.
610
if parent_ie.kind != 'directory':
611
# nb: this relies on someone else checking that the path we're using
612
# doesn't contain symlinks.
613
parent_ie = self._convert_to_directory(parent_ie, inv_dirname)
614
file_id = self.action(self.tree.inventory, parent_ie, path, kind)
615
entry = _mod_inventory.make_entry(kind, basename, parent_ie.file_id,
617
self._invdelta[inv_path] = (None, inv_path, entry.file_id, entry)
618
self.added.append(inv_path)
621
def _gather_dirs_to_add(self, user_dirs):
622
# only walk the minimal parents needed: we have user_dirs to override
626
is_inside = osutils.is_inside_or_parent_of_any
627
for path, (inv_path, this_ie) in sorted(
628
user_dirs.iteritems(), key=operator.itemgetter(0)):
629
if (prev_dir is None or not is_inside([prev_dir], path)):
630
yield (path, inv_path, this_ie, None)
633
def __init__(self, tree, action, conflicts_related=None):
636
self.action = add.AddAction()
642
if conflicts_related is None:
643
self.conflicts_related = frozenset()
645
self.conflicts_related = conflicts_related
647
def add(self, file_list, recurse=True):
648
from bzrlib.inventory import InventoryEntry
650
# no paths supplied: add the entire tree.
651
# FIXME: this assumes we are running in a working tree subdir :-/
655
# expand any symlinks in the directory part, while leaving the
657
# only expanding if symlinks are supported avoids windows path bugs
658
if osutils.has_symlinks():
659
file_list = map(osutils.normalizepath, file_list)
662
# validate user file paths and convert all paths to tree
663
# relative : it's cheaper to make a tree relative path an abspath
664
# than to convert an abspath to tree relative, and it's cheaper to
665
# perform the canonicalization in bulk.
666
for filepath in osutils.canonical_relpaths(self.tree.basedir, file_list):
667
# validate user parameters. Our recursive code avoids adding new
668
# files that need such validation
669
if self.tree.is_control_filename(filepath):
670
raise errors.ForbiddenControlFileError(filename=filepath)
672
abspath = self.tree.abspath(filepath)
673
kind = osutils.file_kind(abspath)
674
# ensure the named path is added, so that ignore rules in the later
675
# directory walk dont skip it.
676
# we dont have a parent ie known yet.: use the relatively slower
677
# inventory probing method
678
inv_path, _ = osutils.normalized_filename(filepath)
679
this_ie = self._get_ie(inv_path)
681
this_ie = self._add_one_and_parent(None, filepath, kind, inv_path)
682
if kind == 'directory':
683
# schedule the dir for scanning
684
user_dirs[filepath] = (inv_path, this_ie)
687
# no need to walk any directories at all.
690
things_to_add = list(self._gather_dirs_to_add(user_dirs))
692
illegalpath_re = re.compile(r'[\r\n]')
693
for directory, inv_path, this_ie, parent_ie in things_to_add:
694
# directory is tree-relative
695
abspath = self.tree.abspath(directory)
697
# get the contents of this directory.
699
# find the kind of the path being added, and save stat_value
703
stat_value = osutils.file_stat(abspath)
704
kind = osutils.file_kind_from_stat_mode(stat_value.st_mode)
708
# allow AddAction to skip this file
709
if self.action.skip_file(self.tree, abspath, kind, stat_value):
711
if not InventoryEntry.versionable_kind(kind):
712
trace.warning("skipping %s (can't add file of kind '%s')",
715
if illegalpath_re.search(directory):
716
trace.warning("skipping %r (contains \\n or \\r)" % abspath)
718
if directory in self.conflicts_related:
719
# If the file looks like one generated for a conflict, don't
722
'skipping %s (generated to help resolve conflicts)',
726
if kind == 'directory' and directory != '':
728
transport = _mod_transport.get_transport_from_path(abspath)
729
controldir.ControlDirFormat.find_format(transport)
731
except errors.NotBranchError:
733
except errors.UnsupportedFormatError:
738
if this_ie is not None:
741
# XXX: This is wrong; people *might* reasonably be trying to
742
# add subtrees as subtrees. This should probably only be done
743
# in formats which can represent subtrees, and even then
744
# perhaps only when the user asked to add subtrees. At the
745
# moment you can add them specially through 'join --reference',
746
# which is perhaps reasonable: adding a new reference is a
747
# special operation and can have a special behaviour. mbp
749
trace.warning("skipping nested tree %r", abspath)
751
this_ie = self._add_one_and_parent(parent_ie, directory, kind,
754
if kind == 'directory' and not sub_tree:
755
if this_ie.kind != 'directory':
756
this_ie = self._convert_to_directory(this_ie, inv_path)
758
for subf in sorted(os.listdir(abspath)):
759
inv_f, _ = osutils.normalized_filename(subf)
760
# here we could use TreeDirectory rather than
761
# string concatenation.
762
subp = osutils.pathjoin(directory, subf)
763
# TODO: is_control_filename is very slow. Make it faster.
764
# TreeDirectory.is_control_filename could also make this
765
# faster - its impossible for a non root dir to have a
767
if self.tree.is_control_filename(subp):
768
trace.mutter("skip control directory %r", subp)
770
sub_invp = osutils.pathjoin(inv_path, inv_f)
771
entry = self._invdelta.get(sub_invp)
772
if entry is not None:
775
sub_ie = this_ie.children.get(inv_f)
776
if sub_ie is not None:
777
# recurse into this already versioned subdir.
778
things_to_add.append((subp, sub_invp, sub_ie, this_ie))
780
# user selection overrides ignores
781
# ignore while selecting files - if we globbed in the
782
# outer loop we would ignore user files.
783
ignore_glob = self.tree.is_ignored(subp)
784
if ignore_glob is not None:
785
self.ignored.setdefault(ignore_glob, []).append(subp)
787
things_to_add.append((subp, sub_invp, None, this_ie))