29
29
joinpath, sha_string, file_kind, local_time_offset, appendpath
30
30
from store import ImmutableStore
31
31
from revision import Revision
32
from errors import bailout, BzrError
32
from errors import BzrError
33
33
from textui import show_status
35
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
43
43
return remotebranch.RemoteBranch(f, **args)
45
45
return Branch(f, **args)
49
def with_writelock(method):
50
"""Method decorator for functions run with the branch locked."""
52
# called with self set to the branch
55
return method(self, *a, **k)
61
def with_readlock(method):
65
return method(self, *a, **k)
48
71
def find_branch_root(f=None):
87
110
Base directory of the branch.
116
If _lock_mode is true, a positive count of the number of times the
120
Open file used for locking.
91
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
126
def __init__(self, base, init=False, find_root=True):
92
127
"""Create new branch object at a particular location.
94
129
base -- Base directory for the branch.
112
147
self.base = os.path.realpath(base)
113
148
if not isdir(self.controlfilename('.')):
114
bailout("not a bzr branch: %s" % quotefn(base),
115
['use "bzr init" to initialize a new working tree',
116
'current bzr can only operate from top-of-tree'])
149
from errors import NotBranchError
150
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
151
['use "bzr init" to initialize a new working tree',
152
'current bzr can only operate from top-of-tree'])
117
153
self._check_format()
154
self._lockfile = self.controlfile('branch-lock', 'wb')
120
156
self.text_store = ImmutableStore(self.controlfilename('text-store'))
121
157
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
129
165
__repr__ = __str__
133
def lock(self, mode='w'):
134
"""Lock the on-disk branch, excluding other processes."""
140
om = os.O_WRONLY | os.O_CREAT
170
from warnings import warn
171
warn("branch %r was not explicitly unlocked" % self)
175
def lock(self, mode):
177
if mode == 'w' and cur_lm == 'r':
178
raise BzrError("can't upgrade to a write lock")
180
assert self._lock_count >= 1
181
self._lock_count += 1
183
from bzrlib.lock import lock, LOCK_SH, LOCK_EX
145
raise BzrError("invalid locking mode %r" % mode)
148
lockfile = os.open(self.controlfilename('branch-lock'), om)
150
if e.errno == errno.ENOENT:
151
# might not exist on branches from <0.0.4
152
self.controlfile('branch-lock', 'w').close()
153
lockfile = os.open(self.controlfilename('branch-lock'), om)
157
fcntl.lockf(lockfile, lm)
159
fcntl.lockf(lockfile, fcntl.LOCK_UN)
161
self._lockmode = None
163
self._lockmode = mode
165
warning("please write a locking method for platform %r" % sys.platform)
167
self._lockmode = None
169
self._lockmode = mode
172
def _need_readlock(self):
173
if self._lockmode not in ['r', 'w']:
174
raise BzrError('need read lock on branch, only have %r' % self._lockmode)
176
def _need_writelock(self):
177
if self._lockmode not in ['w']:
178
raise BzrError('need write lock on branch, only have %r' % self._lockmode)
189
raise ValueError('invalid lock mode %r' % mode)
191
lock(self._lockfile, m)
192
self._lock_mode = mode
197
if not self._lock_mode:
198
raise BzrError('branch %r is not locked' % (self))
200
if self._lock_count > 1:
201
self._lock_count -= 1
203
assert self._lock_count == 1
204
from bzrlib.lock import unlock
205
unlock(self._lockfile)
206
self._lock_mode = self._lock_count = None
181
209
def abspath(self, name):
190
218
rp = os.path.realpath(path)
192
220
if not rp.startswith(self.base):
193
bailout("path %r is not within branch %r" % (rp, self.base))
221
from errors import NotBranchError
222
raise NotBranchError("path %r is not within branch %r" % (rp, self.base))
194
223
rp = rp[len(self.base):]
195
224
rp = rp.lstrip(os.sep)
260
289
fmt = self.controlfile('branch-format', 'r').read()
261
290
fmt.replace('\r\n', '')
262
291
if fmt != BZR_BRANCH_FORMAT:
263
bailout('sorry, branch format %r not supported' % fmt,
264
['use a different bzr version',
265
'or remove the .bzr directory and "bzr init" again'])
292
raise BzrError('sorry, branch format %r not supported' % fmt,
293
['use a different bzr version',
294
'or remove the .bzr directory and "bzr init" again'])
268
299
def read_working_inventory(self):
269
300
"""Read the working inventory."""
270
self._need_readlock()
271
301
before = time.time()
272
302
# ElementTree does its own conversion from UTF-8, so open in
275
305
mutter("loaded inventory of %d items in %f"
276
306
% (len(inv), time.time() - before))
280
310
def _write_inventory(self, inv):
281
311
"""Update the working inventory.
283
313
That is to say, the inventory describing changes underway, that
284
314
will be committed to the next revision.
286
self._need_writelock()
287
316
## TODO: factor out to atomicfile? is rename safe on windows?
288
317
## TODO: Maybe some kind of clean/dirty marker on inventory?
289
318
tmpfname = self.controlfilename('inventory.tmp')
295
324
os.remove(inv_fname)
296
325
os.rename(tmpfname, inv_fname)
297
326
mutter('wrote working inventory')
300
329
inventory = property(read_working_inventory, _write_inventory, None,
301
330
"""Inventory for the working copy.""")
304
334
def add(self, files, verbose=False, ids=None):
305
335
"""Make files versioned.
321
351
add all non-ignored children. Perhaps do that in a
322
352
higher-level method.
324
self._need_writelock()
326
354
# TODO: Re-adding a file that is removed in the working copy
327
355
# should probably put it back with the previous ID.
328
356
if isinstance(files, types.StringTypes):
335
363
ids = [None] * len(files)
337
365
assert(len(ids) == len(files))
339
367
inv = self.read_working_inventory()
340
368
for f,file_id in zip(files, ids):
341
369
if is_control_file(f):
342
bailout("cannot add control file %s" % quotefn(f))
370
raise BzrError("cannot add control file %s" % quotefn(f))
344
372
fp = splitpath(f)
347
bailout("cannot add top-level %r" % f)
375
raise BzrError("cannot add top-level %r" % f)
349
377
fullpath = os.path.normpath(self.abspath(f))
352
380
kind = file_kind(fullpath)
354
382
# maybe something better?
355
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
383
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
357
385
if kind != 'file' and kind != 'directory':
358
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
386
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
360
388
if file_id is None:
361
389
file_id = gen_file_id(f)
365
393
show_status('A', kind, quotefn(f))
367
395
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
397
self._write_inventory(inv)
369
self._write_inventory(inv)
372
400
def print_file(self, file, revno):
373
401
"""Print `file` to stdout."""
374
self._need_readlock()
375
402
tree = self.revision_tree(self.lookup_revision(revno))
376
403
# use inventory as it was in that revision
377
404
file_id = tree.inventory.path2id(file)
379
bailout("%r is not present in revision %d" % (file, revno))
406
raise BzrError("%r is not present in revision %d" % (file, revno))
380
407
tree.print_file(file_id)
383
411
def remove(self, files, verbose=False):
384
412
"""Mark nominated files for removal from the inventory.
409
435
fid = inv.path2id(f)
411
bailout("cannot remove unversioned file %s" % quotefn(f))
437
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
412
438
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
414
440
# having remove it, it must be either ignored or unknown
422
448
self._write_inventory(inv)
424
451
def set_inventory(self, new_inventory_list):
425
452
inv = Inventory()
426
453
for path, file_id, parent, kind in new_inventory_list:
472
499
def get_revision(self, revision_id):
473
500
"""Return the Revision object for a named revision"""
474
self._need_readlock()
475
501
r = Revision.read_xml(self.revision_store[revision_id])
476
502
assert r.revision_id == revision_id
483
509
TODO: Perhaps for this and similar methods, take a revision
484
510
parameter which can be either an integer revno or a
486
self._need_readlock()
487
512
i = Inventory.read_xml(self.inventory_store[inventory_id])
491
516
def get_revision_inventory(self, revision_id):
492
517
"""Return inventory of a past revision."""
493
self._need_readlock()
494
518
if revision_id == None:
495
519
return Inventory()
497
521
return self.get_inventory(self.get_revision(revision_id).inventory_id)
500
525
def revision_history(self):
501
526
"""Return sequence of revision hashes on to this branch.
503
528
>>> ScratchBranch().revision_history()
506
self._need_readlock()
507
531
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
603
627
def rename_one(self, from_rel, to_rel):
604
628
"""Rename one file.
606
630
This can change the directory or the filename or both.
608
self._need_writelock()
609
632
tree = self.working_tree()
610
633
inv = tree.inventory
611
634
if not tree.has_filename(from_rel):
612
bailout("can't rename: old working file %r does not exist" % from_rel)
635
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
613
636
if tree.has_filename(to_rel):
614
bailout("can't rename: new working file %r already exists" % to_rel)
637
raise BzrError("can't rename: new working file %r already exists" % to_rel)
616
639
file_id = inv.path2id(from_rel)
617
640
if file_id == None:
618
bailout("can't rename: old name %r is not versioned" % from_rel)
641
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
620
643
if inv.path2id(to_rel):
621
bailout("can't rename: new name %r is already versioned" % to_rel)
644
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
623
646
to_dir, to_tail = os.path.split(to_rel)
624
647
to_dir_id = inv.path2id(to_dir)
625
648
if to_dir_id == None and to_dir != '':
626
bailout("can't determine destination directory id for %r" % to_dir)
649
raise BzrError("can't determine destination directory id for %r" % to_dir)
628
651
mutter("rename_one:")
629
652
mutter(" file_id {%s}" % file_id)
631
654
mutter(" to_rel %r" % to_rel)
632
655
mutter(" to_dir %r" % to_dir)
633
656
mutter(" to_dir_id {%s}" % to_dir_id)
635
658
inv.rename(file_id, to_dir_id, to_tail)
637
660
print "%s => %s" % (from_rel, to_rel)
639
662
from_abs = self.abspath(from_rel)
640
663
to_abs = self.abspath(to_rel)
642
665
os.rename(from_abs, to_abs)
643
666
except OSError, e:
644
bailout("failed to rename %r to %r: %s"
667
raise BzrError("failed to rename %r to %r: %s"
645
668
% (from_abs, to_abs, e[1]),
646
669
["rename rolled back"])
648
671
self._write_inventory(inv)
652
676
def move(self, from_paths, to_name):
660
684
Note that to_name is only the last component of the new name;
661
685
this doesn't change the directory.
663
self._need_writelock()
664
687
## TODO: Option to move IDs only
665
688
assert not isinstance(from_paths, basestring)
666
689
tree = self.working_tree()
667
690
inv = tree.inventory
668
691
to_abs = self.abspath(to_name)
669
692
if not isdir(to_abs):
670
bailout("destination %r is not a directory" % to_abs)
693
raise BzrError("destination %r is not a directory" % to_abs)
671
694
if not tree.has_filename(to_name):
672
bailout("destination %r not in working directory" % to_abs)
695
raise BzrError("destination %r not in working directory" % to_abs)
673
696
to_dir_id = inv.path2id(to_name)
674
697
if to_dir_id == None and to_name != '':
675
bailout("destination %r is not a versioned directory" % to_name)
698
raise BzrError("destination %r is not a versioned directory" % to_name)
676
699
to_dir_ie = inv[to_dir_id]
677
700
if to_dir_ie.kind not in ('directory', 'root_directory'):
678
bailout("destination %r is not a directory" % to_abs)
701
raise BzrError("destination %r is not a directory" % to_abs)
680
703
to_idpath = inv.get_idpath(to_dir_id)
682
705
for f in from_paths:
683
706
if not tree.has_filename(f):
684
bailout("%r does not exist in working tree" % f)
707
raise BzrError("%r does not exist in working tree" % f)
685
708
f_id = inv.path2id(f)
687
bailout("%r is not versioned" % f)
710
raise BzrError("%r is not versioned" % f)
688
711
name_tail = splitpath(f)[-1]
689
712
dest_path = appendpath(to_name, name_tail)
690
713
if tree.has_filename(dest_path):
691
bailout("destination %r already exists" % dest_path)
714
raise BzrError("destination %r already exists" % dest_path)
692
715
if f_id in to_idpath:
693
bailout("can't move %r to a subdirectory of itself" % f)
716
raise BzrError("can't move %r to a subdirectory of itself" % f)
695
718
# OK, so there's a race here, it's possible that someone will
696
719
# create a file in this interval and then the rename might be
705
728
os.rename(self.abspath(f), self.abspath(dest_path))
706
729
except OSError, e:
707
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
730
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
708
731
["rename rolled back"])
710
733
self._write_inventory(inv)