13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""MutableTree object.
68
70
entirely in memory.
70
72
For now, we are not treating MutableTree as an interface to provide
71
conformance tests for - rather we are testing MemoryTree specifically, and
73
conformance tests for - rather we are testing MemoryTree specifically, and
72
74
interface testing implementations of WorkingTree.
74
76
A mutable tree always has an associated Branch and BzrDir object - the
75
77
branch and bzrdir attributes.
79
def __init__(self, *args, **kw):
80
super(MutableTree, self).__init__(*args, **kw)
81
# Is this tree on a case-insensitive or case-preserving file-system?
82
# Sub-classes may initialize to False if they detect they are being
83
# used on media which doesn't differentiate the case of names.
84
self.case_sensitive = True
78
86
@needs_tree_write_lock
79
87
def add(self, files, ids=None, kinds=None):
95
103
TODO: Perhaps callback with the ids and paths as they're added.
97
105
if isinstance(files, basestring):
98
assert(ids is None or isinstance(ids, basestring))
99
assert(kinds is None or isinstance(kinds, basestring))
106
# XXX: Passing a single string is inconsistent and should be
108
if not (ids is None or isinstance(ids, basestring)):
109
raise AssertionError()
110
if not (kinds is None or isinstance(kinds, basestring)):
111
raise AssertionError()
101
113
if ids is not None:
109
121
ids = [None] * len(files)
111
assert(len(ids) == len(files))
123
if not (len(ids) == len(files)):
124
raise AssertionError()
112
125
if kinds is None:
113
126
kinds = [None] * len(files)
115
assert(len(kinds) == len(files))
127
elif not len(kinds) == len(files):
128
raise AssertionError()
117
130
# generic constraint checks:
118
131
if self.is_control_filename(f):
119
132
raise errors.ForbiddenControlFileError(filename=f)
120
133
fp = splitpath(f)
121
# fill out file kinds for all files [not needed when we stop
134
# fill out file kinds for all files [not needed when we stop
122
135
# caring about the instantaneous file kind within a uncommmitted tree
124
137
self._gather_kinds(files, kinds)
175
188
from bzrlib import commit
176
189
if revprops is None:
191
possible_master_transports=[]
178
192
if not 'branch-nick' in revprops:
179
revprops['branch-nick'] = self.branch.nick
193
revprops['branch-nick'] = self.branch._get_nick(
194
kwargs.get('local', False),
195
possible_master_transports)
196
authors = kwargs.pop('authors', None)
180
197
author = kwargs.pop('author', None)
198
if authors is not None:
199
if author is not None:
200
raise AssertionError('Specifying both author and authors '
201
'is not allowed. Specify just authors instead')
202
if 'author' in revprops or 'authors' in revprops:
203
# XXX: maybe we should just accept one of them?
204
raise AssertionError('author property given twice')
206
for individual in authors:
207
if '\n' in individual:
208
raise AssertionError('\\n is not a valid character '
209
'in an author identity')
210
revprops['authors'] = '\n'.join(authors)
181
211
if author is not None:
182
assert 'author' not in revprops
183
revprops['author'] = author
212
symbol_versioning.warn('The parameter author was deprecated'
213
' in version 1.13. Use authors instead',
215
if 'author' in revprops or 'authors' in revprops:
216
# XXX: maybe we should just accept one of them?
217
raise AssertionError('author property given twice')
219
raise AssertionError('\\n is not a valid character '
220
'in an author identity')
221
revprops['authors'] = author
184
222
# args for wt.commit start at message from the Commit.commit method,
185
223
args = (message, ) + args
224
for hook in MutableTree.hooks['start_commit']:
186
226
committed_id = commit.Commit().commit(working_tree=self,
187
revprops=revprops, *args, **kwargs)
228
possible_master_transports=possible_master_transports,
230
post_hook_params = PostCommitHookParams(self)
231
for hook in MutableTree.hooks['post_commit']:
232
hook(post_hook_params)
188
233
return committed_id
190
235
def _gather_kinds(self, files, kinds):
192
237
raise NotImplementedError(self._gather_kinds)
240
def has_changes(self, from_tree):
241
"""Quickly check that the tree contains at least one change.
243
:return: True if a change is found. False otherwise
245
changes = self.iter_changes(from_tree)
247
change = changes.next()
248
# Exclude root (talk about black magic... --vila 20090629)
249
if change[4] == (None, None):
250
change = changes.next()
252
except StopIteration:
195
257
def last_revision(self):
196
258
"""Return the revision id of the last commit performed in this tree.
198
260
In early tree formats the result of last_revision is the same as the
199
261
branch last_revision, but that is no longer the case for modern tree
202
264
last_revision returns the left most parent id, or None if there are no
221
283
def lock_write(self):
222
284
"""Lock the tree and its branch. This allows mutating calls to be made.
224
Some mutating methods will take out implicit write locks, but in
286
Some mutating methods will take out implicit write locks, but in
225
287
general you should always obtain a write lock before calling mutating
226
288
methods on a tree.
238
300
raise NotImplementedError(self.mkdir)
302
def _observed_sha1(self, file_id, path, (sha1, stat_value)):
303
"""Tell the tree we have observed a paths sha1.
305
The intent of this function is to allow trees that have a hashcache to
306
update the hashcache during commit. If the observed file is too new
307
(based on the stat_value) to be safely hash-cached the tree will ignore
310
The default implementation does nothing.
312
:param file_id: The file id
313
:param path: The file path
314
:param sha1: The sha 1 that was observed.
315
:param stat_value: A stat result for the file the sha1 was read from.
319
def _fix_case_of_inventory_path(self, path):
320
"""If our tree isn't case sensitive, return the canonical path"""
321
if not self.case_sensitive:
322
path = self.get_canonical_inventory_path(path)
326
def put_file_bytes_non_atomic(self, file_id, bytes):
327
"""Update the content of a file in the tree.
329
Note that the file is written in-place rather than being
330
written to a temporary location and renamed. As a consequence,
331
readers can potentially see the file half-written.
333
:param file_id: file-id of the file
334
:param bytes: the new file contents
336
raise NotImplementedError(self.put_file_bytes_non_atomic)
240
338
def set_parent_ids(self, revision_ids, allow_leftmost_as_ghost=False):
241
339
"""Set the parents ids of the working tree.
247
345
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
248
346
"""Set the parents of the working tree.
250
:param parents_list: A list of (revision_id, tree) tuples.
348
:param parents_list: A list of (revision_id, tree) tuples.
251
349
If tree is None, then that element is treated as an unreachable
252
350
parent tree - i.e. a ghost.
261
359
For the specific behaviour see the help for cmd_add().
263
361
:param action: A reporter to be called with the inventory, parent_ie,
264
path and kind of the path being added. It may return a file_id if
362
path and kind of the path being added. It may return a file_id if
265
363
a specific one should be used.
266
364
:param save: Save the inventory after completing the adds. If False
267
365
this provides dry-run functionality by doing the add and not saving
273
371
# not in an inner loop; and we want to remove direct use of this,
274
372
# so here as a reminder for now. RBC 20070703
275
373
from bzrlib.inventory import InventoryEntry
276
assert isinstance(recurse, bool)
277
374
if action is None:
278
375
action = add.AddAction()
280
377
if not file_list:
281
378
# no paths supplied: add the entire tree.
282
379
file_list = [u'.']
288
385
user_dirs = set()
290
# validate user file paths and convert all paths to tree
387
# validate user file paths and convert all paths to tree
291
388
# relative : it's cheaper to make a tree relative path an abspath
292
# than to convert an abspath to tree relative.
293
for filepath in file_list:
294
rf = _FastPath(self.relpath(filepath))
389
# than to convert an abspath to tree relative, and it's cheaper to
390
# perform the canonicalization in bulk.
391
for filepath in osutils.canonical_relpaths(self.basedir, file_list):
392
rf = _FastPath(filepath)
295
393
# validate user parameters. Our recursive code avoids adding new files
296
# that need such validation
394
# that need such validation
297
395
if self.is_control_filename(rf.raw_path):
298
396
raise errors.ForbiddenControlFileError(filename=rf.raw_path)
300
398
abspath = self.abspath(rf.raw_path)
301
399
kind = osutils.file_kind(abspath)
302
400
if kind == 'directory':
307
405
raise errors.BadFileKindError(filename=abspath, kind=kind)
308
406
# ensure the named path is added, so that ignore rules in the later directory
309
407
# walk dont skip it.
310
# we dont have a parent ie known yet.: use the relatively slower inventory
408
# we dont have a parent ie known yet.: use the relatively slower inventory
312
410
versioned = inv.has_filename(rf.raw_path)
330
428
dirs_to_add.append((path, None))
331
429
prev_dir = path.raw_path
431
illegalpath_re = re.compile(r'[\r\n]')
333
432
# dirs_to_add is initialised to a list of directories, but as we scan
334
433
# directories we append files to it.
335
434
# XXX: We should determine kind of files when we scan them rather than
346
445
if not InventoryEntry.versionable_kind(kind):
347
446
warning("skipping %s (can't add file of kind '%s')", abspath, kind)
448
if illegalpath_re.search(directory.raw_path):
449
warning("skipping %r (contains \\n or \\r)" % abspath)
350
452
if parent_ie is not None:
351
453
versioned = directory.base_path in parent_ie.children
353
# without the parent ie, use the relatively slower inventory
455
# without the parent ie, use the relatively slower inventory
355
versioned = inv.has_filename(directory.raw_path)
457
versioned = inv.has_filename(
458
self._fix_case_of_inventory_path(directory.raw_path))
357
460
if kind == 'directory':
373
476
# mutter("%r is already versioned", abspath)
375
478
# XXX: This is wrong; people *might* reasonably be trying to add
376
# subtrees as subtrees. This should probably only be done in formats
479
# subtrees as subtrees. This should probably only be done in formats
377
480
# which can represent subtrees, and even then perhaps only when
378
481
# the user asked to add subtrees. At the moment you can add them
379
482
# specially through 'join --reference', which is perhaps
389
492
# must be present:
390
493
this_ie = parent_ie.children[directory.base_path]
392
# without the parent ie, use the relatively slower inventory
495
# without the parent ie, use the relatively slower inventory
394
this_id = inv.path2id(directory.raw_path)
497
this_id = inv.path2id(
498
self._fix_case_of_inventory_path(directory.raw_path))
395
499
if this_id is None:
398
502
this_ie = inv[this_id]
400
504
for subf in sorted(os.listdir(abspath)):
401
# here we could use TreeDirectory rather than
505
# here we could use TreeDirectory rather than
402
506
# string concatenation.
403
507
subp = osutils.pathjoin(directory.raw_path, subf)
404
# TODO: is_control_filename is very slow. Make it faster.
405
# TreeDirectory.is_control_filename could also make this
406
# faster - its impossible for a non root dir to have a
508
# TODO: is_control_filename is very slow. Make it faster.
509
# TreeDirectory.is_control_filename could also make this
510
# faster - its impossible for a non root dir to have a
408
512
if self.is_control_filename(subp):
409
513
mutter("skip control directory %r", subp)
437
541
inventory for the parent new_revid, and all other parent trees are
544
All the changes in the delta should be changes synchronising the basis
545
tree with some or all of the working tree, with a change to a directory
546
requiring that its contents have been recursively included. That is,
547
this is not a general purpose tree modification routine, but a helper
548
for commit which is not required to handle situations that do not arise
551
See the inventory developers documentation for the theory behind
440
554
:param new_revid: The new revision id for the trees parent.
441
555
:param delta: An inventory delta (see apply_inventory_delta) describing
442
556
the changes from the current left most parent revision to new_revid.
452
566
# WorkingTree classes for optimised versions for specific format trees.
453
567
basis = self.basis_tree()
454
568
basis.lock_read()
455
inventory = basis.inventory
569
# TODO: Consider re-evaluating the need for this with CHKInventory
570
# we don't strictly need to mutate an inventory for this
571
# it only makes sense when apply_delta is cheaper than get_inventory()
572
inventory = basis.inventory._get_mutable_inventory()
457
574
inventory.apply_delta(delta)
458
575
rev_tree = RevisionTree(self.branch.repository, inventory, new_revid)
459
576
self.set_parent_trees([(new_revid, rev_tree)])
579
class MutableTreeHooks(hooks.Hooks):
580
"""A dictionary mapping a hook name to a list of callables for mutabletree
585
"""Create the default hooks.
588
hooks.Hooks.__init__(self)
589
self.create_hook(hooks.HookPoint('start_commit',
590
"Called before a commit is performed on a tree. The start commit "
591
"hook is able to change the tree before the commit takes place. "
592
"start_commit is called with the bzrlib.mutabletree.MutableTree "
593
"that the commit is being performed on.", (1, 4), None))
594
self.create_hook(hooks.HookPoint('post_commit',
595
"Called after a commit is performed on a tree. The hook is "
596
"called with a bzrlib.mutabletree.PostCommitHookParams object. "
597
"The mutable tree the commit was performed on is available via "
598
"the mutable_tree attribute of that object.", (2, 0), None))
601
# install the default hooks into the MutableTree class.
602
MutableTree.hooks = MutableTreeHooks()
605
class PostCommitHookParams(object):
606
"""Parameters for the post_commit hook.
608
To access the parameters, use the following attributes:
610
* mutable_tree - the MutableTree object
613
def __init__(self, mutable_tree):
614
"""Create the parameters for the post_commit hook."""
615
self.mutable_tree = mutable_tree
462
618
class _FastPath(object):
463
619
"""A path object with fast accessors for things like basename."""
500
656
# slower but does not need parent_ie
501
if inv.has_filename(path.raw_path):
657
if inv.has_filename(tree._fix_case_of_inventory_path(path.raw_path)):
503
659
# its really not there : add the parent
504
660
# note that the dirname use leads to some extra str copying etc but as