49
def _relpath(base, path):
50
"""Return path relative to base, or raise exception.
52
The path may be either an absolute path or a path relative to the
53
current working directory.
55
Lifted out of Branch.relpath for ease of testing.
57
os.path.commonprefix (python2.4) has a bad bug that it works just
58
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
59
avoids that problem."""
60
rp = os.path.abspath(path)
64
while len(head) >= len(base):
67
head, tail = os.path.split(head)
71
from errors import NotBranchError
72
raise NotBranchError("path %r is not within branch %r" % (rp, base))
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)
77
71
def find_branch_root(f=None):
178
168
def __del__(self):
179
if self._lock_mode or self._lock:
180
170
from warnings import warn
181
171
warn("branch %r was not explicitly unlocked" % self)
186
def lock_write(self):
188
if self._lock_mode != 'w':
189
from errors import LockError
190
raise LockError("can't upgrade to a write lock from %r" %
192
self._lock_count += 1
194
from bzrlib.lock import WriteLock
196
self._lock = WriteLock(self.controlfilename('branch-lock'))
197
self._lock_mode = 'w'
204
assert self._lock_mode in ('r', 'w'), \
205
"invalid lock mode %r" % self._lock_mode
206
self._lock_count += 1
208
from bzrlib.lock import ReadLock
210
self._lock = ReadLock(self.controlfilename('branch-lock'))
211
self._lock_mode = 'r'
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
189
raise ValueError('invalid lock mode %r' % mode)
191
lock(self._lockfile, m)
192
self._lock_mode = mode
216
196
def unlock(self):
217
197
if not self._lock_mode:
218
from errors import LockError
219
raise LockError('branch %r is not locked' % (self))
198
raise BzrError('branch %r is not locked' % (self))
221
200
if self._lock_count > 1:
222
201
self._lock_count -= 1
203
assert self._lock_count == 1
204
from bzrlib.lock import unlock
205
unlock(self._lockfile)
226
206
self._lock_mode = self._lock_count = None
311
299
def read_working_inventory(self):
312
300
"""Read the working inventory."""
313
301
before = time.time()
314
302
# ElementTree does its own conversion from UTF-8, so open in
318
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
319
mutter("loaded inventory of %d items in %f"
320
% (len(inv), time.time() - before))
304
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
305
mutter("loaded inventory of %d items in %f"
306
% (len(inv), time.time() - before))
326
310
def _write_inventory(self, inv):
354
339
This puts the files in the Added state, so that they will be
355
340
recorded by the next commit.
358
List of paths to add, relative to the base of the tree.
361
If set, use these instead of automatically generated ids.
362
Must be the same length as the list of files, but may
363
contain None for ids that are to be autogenerated.
365
342
TODO: Perhaps have an option to add the ids even if the files do
368
345
TODO: Perhaps return the ids of the files? But then again it
369
is easy to retrieve them if they're needed.
346
is easy to retrieve them if they're needed.
348
TODO: Option to specify file id.
371
350
TODO: Adding a directory should optionally recurse down and
372
add all non-ignored children. Perhaps do that in a
351
add all non-ignored children. Perhaps do that in a
375
354
# TODO: Re-adding a file that is removed in the working copy
376
355
# should probably put it back with the previous ID.
386
365
assert(len(ids) == len(files))
390
inv = self.read_working_inventory()
391
for f,file_id in zip(files, ids):
392
if is_control_file(f):
393
raise BzrError("cannot add control file %s" % quotefn(f))
398
raise BzrError("cannot add top-level %r" % f)
400
fullpath = os.path.normpath(self.abspath(f))
403
kind = file_kind(fullpath)
405
# maybe something better?
406
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
408
if kind != 'file' and kind != 'directory':
409
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
412
file_id = gen_file_id(f)
413
inv.add_path(f, kind=kind, file_id=file_id)
416
show_status('A', kind, quotefn(f))
418
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
420
self._write_inventory(inv)
367
inv = self.read_working_inventory()
368
for f,file_id in zip(files, ids):
369
if is_control_file(f):
370
raise BzrError("cannot add control file %s" % quotefn(f))
375
raise BzrError("cannot add top-level %r" % f)
377
fullpath = os.path.normpath(self.abspath(f))
380
kind = file_kind(fullpath)
382
# maybe something better?
383
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
385
if kind != 'file' and kind != 'directory':
386
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
389
file_id = gen_file_id(f)
390
inv.add_path(f, kind=kind, file_id=file_id)
393
show_status('A', kind, quotefn(f))
395
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
397
self._write_inventory(inv)
425
400
def print_file(self, file, revno):
426
401
"""Print `file` to stdout."""
429
tree = self.revision_tree(self.lookup_revision(revno))
430
# use inventory as it was in that revision
431
file_id = tree.inventory.path2id(file)
433
raise BzrError("%r is not present in revision %d" % (file, revno))
434
tree.print_file(file_id)
402
tree = self.revision_tree(self.lookup_revision(revno))
403
# use inventory as it was in that revision
404
file_id = tree.inventory.path2id(file)
406
raise BzrError("%r is not present in revision %d" % (file, revno))
407
tree.print_file(file_id)
439
411
def remove(self, files, verbose=False):
440
412
"""Mark nominated files for removal from the inventory.
455
427
if isinstance(files, types.StringTypes):
461
tree = self.working_tree()
464
# do this before any modifications
468
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
469
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
471
# having remove it, it must be either ignored or unknown
472
if tree.is_ignored(f):
476
show_status(new_status, inv[fid].kind, quotefn(f))
479
self._write_inventory(inv)
484
# FIXME: this doesn't need to be a branch method
430
tree = self.working_tree()
433
# do this before any modifications
437
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
438
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
440
# having remove it, it must be either ignored or unknown
441
if tree.is_ignored(f):
445
show_status(new_status, inv[fid].kind, quotefn(f))
448
self._write_inventory(inv)
485
451
def set_inventory(self, new_inventory_list):
486
452
inv = Inventory()
487
453
for path, file_id, parent, kind in new_inventory_list:
555
521
return self.get_inventory(self.get_revision(revision_id).inventory_id)
558
525
def revision_history(self):
559
526
"""Return sequence of revision hashes on to this branch.
561
528
>>> ScratchBranch().revision_history()
566
return [l.rstrip('\r\n') for l in
567
self.controlfile('revision-history', 'r').readlines()]
572
def common_ancestor(self, other, self_revno=None, other_revno=None):
575
>>> sb = ScratchBranch(files=['foo', 'foo~'])
576
>>> sb.common_ancestor(sb) == (None, None)
578
>>> commit.commit(sb, "Committing first revision", verbose=False)
579
>>> sb.common_ancestor(sb)[0]
581
>>> clone = sb.clone()
582
>>> commit.commit(sb, "Committing second revision", verbose=False)
583
>>> sb.common_ancestor(sb)[0]
585
>>> sb.common_ancestor(clone)[0]
587
>>> commit.commit(clone, "Committing divergent second revision",
589
>>> sb.common_ancestor(clone)[0]
591
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
593
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
595
>>> clone2 = sb.clone()
596
>>> sb.common_ancestor(clone2)[0]
598
>>> sb.common_ancestor(clone2, self_revno=1)[0]
600
>>> sb.common_ancestor(clone2, other_revno=1)[0]
603
my_history = self.revision_history()
604
other_history = other.revision_history()
605
if self_revno is None:
606
self_revno = len(my_history)
607
if other_revno is None:
608
other_revno = len(other_history)
609
indices = range(min((self_revno, other_revno)))
612
if my_history[r] == other_history[r]:
613
return r+1, my_history[r]
531
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
616
534
def enum_history(self, direction):
617
535
"""Return (revno, revision_id) for history of branch.
657
def missing_revisions(self, other):
659
If self and other have not diverged, return a list of the revisions
660
present in other, but missing from self.
662
>>> from bzrlib.commit import commit
663
>>> bzrlib.trace.silent = True
664
>>> br1 = ScratchBranch()
665
>>> br2 = ScratchBranch()
666
>>> br1.missing_revisions(br2)
668
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
669
>>> br1.missing_revisions(br2)
671
>>> br2.missing_revisions(br1)
673
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
674
>>> br1.missing_revisions(br2)
676
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
677
>>> br1.missing_revisions(br2)
679
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
680
>>> br1.missing_revisions(br2)
681
Traceback (most recent call last):
682
DivergedBranches: These branches have diverged.
684
self_history = self.revision_history()
685
self_len = len(self_history)
686
other_history = other.revision_history()
687
other_len = len(other_history)
688
common_index = min(self_len, other_len) -1
689
if common_index >= 0 and \
690
self_history[common_index] != other_history[common_index]:
691
raise DivergedBranches(self, other)
692
if self_len < other_len:
693
return other_history[self_len:]
697
def update_revisions(self, other):
698
"""If self and other have not diverged, ensure self has all the
701
>>> from bzrlib.commit import commit
702
>>> bzrlib.trace.silent = True
703
>>> br1 = ScratchBranch(files=['foo', 'bar'])
706
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
707
>>> br2 = ScratchBranch()
708
>>> br2.update_revisions(br1)
712
>>> br2.revision_history()
714
>>> br2.update_revisions(br1)
718
>>> br1.text_store.total_size() == br2.text_store.total_size()
721
revision_ids = self.missing_revisions(other)
722
revisions = [other.get_revision(f) for f in revision_ids]
723
needed_texts = sets.Set()
724
for rev in revisions:
725
inv = other.get_inventory(str(rev.inventory_id))
726
for key, entry in inv.iter_entries():
727
if entry.text_id is None:
729
if entry.text_id not in self.text_store:
730
needed_texts.add(entry.text_id)
731
count = self.text_store.copy_multi(other.text_store, needed_texts)
732
print "Added %d texts." % count
733
inventory_ids = [ f.inventory_id for f in revisions ]
734
count = self.inventory_store.copy_multi(other.inventory_store,
736
print "Added %d inventories." % count
737
revision_ids = [ f.revision_id for f in revisions]
738
count = self.revision_store.copy_multi(other.revision_store,
740
for revision_id in revision_ids:
741
self.append_revision(revision_id)
742
print "Added %d revisions." % count
745
575
def commit(self, *args, **kw):
747
577
from bzrlib.commit import commit
796
627
def rename_one(self, from_rel, to_rel):
797
628
"""Rename one file.
799
630
This can change the directory or the filename or both.
632
tree = self.working_tree()
634
if not tree.has_filename(from_rel):
635
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
636
if tree.has_filename(to_rel):
637
raise BzrError("can't rename: new working file %r already exists" % to_rel)
639
file_id = inv.path2id(from_rel)
641
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
643
if inv.path2id(to_rel):
644
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
646
to_dir, to_tail = os.path.split(to_rel)
647
to_dir_id = inv.path2id(to_dir)
648
if to_dir_id == None and to_dir != '':
649
raise BzrError("can't determine destination directory id for %r" % to_dir)
651
mutter("rename_one:")
652
mutter(" file_id {%s}" % file_id)
653
mutter(" from_rel %r" % from_rel)
654
mutter(" to_rel %r" % to_rel)
655
mutter(" to_dir %r" % to_dir)
656
mutter(" to_dir_id {%s}" % to_dir_id)
658
inv.rename(file_id, to_dir_id, to_tail)
660
print "%s => %s" % (from_rel, to_rel)
662
from_abs = self.abspath(from_rel)
663
to_abs = self.abspath(to_rel)
803
tree = self.working_tree()
805
if not tree.has_filename(from_rel):
806
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
807
if tree.has_filename(to_rel):
808
raise BzrError("can't rename: new working file %r already exists" % to_rel)
810
file_id = inv.path2id(from_rel)
812
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
814
if inv.path2id(to_rel):
815
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
817
to_dir, to_tail = os.path.split(to_rel)
818
to_dir_id = inv.path2id(to_dir)
819
if to_dir_id == None and to_dir != '':
820
raise BzrError("can't determine destination directory id for %r" % to_dir)
822
mutter("rename_one:")
823
mutter(" file_id {%s}" % file_id)
824
mutter(" from_rel %r" % from_rel)
825
mutter(" to_rel %r" % to_rel)
826
mutter(" to_dir %r" % to_dir)
827
mutter(" to_dir_id {%s}" % to_dir_id)
829
inv.rename(file_id, to_dir_id, to_tail)
831
print "%s => %s" % (from_rel, to_rel)
833
from_abs = self.abspath(from_rel)
834
to_abs = self.abspath(to_rel)
836
os.rename(from_abs, to_abs)
838
raise BzrError("failed to rename %r to %r: %s"
839
% (from_abs, to_abs, e[1]),
840
["rename rolled back"])
842
self._write_inventory(inv)
665
os.rename(from_abs, to_abs)
667
raise BzrError("failed to rename %r to %r: %s"
668
% (from_abs, to_abs, e[1]),
669
["rename rolled back"])
671
self._write_inventory(inv)
847
676
def move(self, from_paths, to_name):
855
684
Note that to_name is only the last component of the new name;
856
685
this doesn't change the directory.
860
## TODO: Option to move IDs only
861
assert not isinstance(from_paths, basestring)
862
tree = self.working_tree()
864
to_abs = self.abspath(to_name)
865
if not isdir(to_abs):
866
raise BzrError("destination %r is not a directory" % to_abs)
867
if not tree.has_filename(to_name):
868
raise BzrError("destination %r not in working directory" % to_abs)
869
to_dir_id = inv.path2id(to_name)
870
if to_dir_id == None and to_name != '':
871
raise BzrError("destination %r is not a versioned directory" % to_name)
872
to_dir_ie = inv[to_dir_id]
873
if to_dir_ie.kind not in ('directory', 'root_directory'):
874
raise BzrError("destination %r is not a directory" % to_abs)
876
to_idpath = inv.get_idpath(to_dir_id)
879
if not tree.has_filename(f):
880
raise BzrError("%r does not exist in working tree" % f)
881
f_id = inv.path2id(f)
883
raise BzrError("%r is not versioned" % f)
884
name_tail = splitpath(f)[-1]
885
dest_path = appendpath(to_name, name_tail)
886
if tree.has_filename(dest_path):
887
raise BzrError("destination %r already exists" % dest_path)
888
if f_id in to_idpath:
889
raise BzrError("can't move %r to a subdirectory of itself" % f)
891
# OK, so there's a race here, it's possible that someone will
892
# create a file in this interval and then the rename might be
893
# left half-done. But we should have caught most problems.
896
name_tail = splitpath(f)[-1]
897
dest_path = appendpath(to_name, name_tail)
898
print "%s => %s" % (f, dest_path)
899
inv.rename(inv.path2id(f), to_dir_id, name_tail)
901
os.rename(self.abspath(f), self.abspath(dest_path))
903
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
904
["rename rolled back"])
906
self._write_inventory(inv)
687
## TODO: Option to move IDs only
688
assert not isinstance(from_paths, basestring)
689
tree = self.working_tree()
691
to_abs = self.abspath(to_name)
692
if not isdir(to_abs):
693
raise BzrError("destination %r is not a directory" % to_abs)
694
if not tree.has_filename(to_name):
695
raise BzrError("destination %r not in working directory" % to_abs)
696
to_dir_id = inv.path2id(to_name)
697
if to_dir_id == None and to_name != '':
698
raise BzrError("destination %r is not a versioned directory" % to_name)
699
to_dir_ie = inv[to_dir_id]
700
if to_dir_ie.kind not in ('directory', 'root_directory'):
701
raise BzrError("destination %r is not a directory" % to_abs)
703
to_idpath = inv.get_idpath(to_dir_id)
706
if not tree.has_filename(f):
707
raise BzrError("%r does not exist in working tree" % f)
708
f_id = inv.path2id(f)
710
raise BzrError("%r is not versioned" % f)
711
name_tail = splitpath(f)[-1]
712
dest_path = appendpath(to_name, name_tail)
713
if tree.has_filename(dest_path):
714
raise BzrError("destination %r already exists" % dest_path)
715
if f_id in to_idpath:
716
raise BzrError("can't move %r to a subdirectory of itself" % f)
718
# OK, so there's a race here, it's possible that someone will
719
# create a file in this interval and then the rename might be
720
# left half-done. But we should have caught most problems.
723
name_tail = splitpath(f)[-1]
724
dest_path = appendpath(to_name, name_tail)
725
print "%s => %s" % (f, dest_path)
726
inv.rename(inv.path2id(f), to_dir_id, name_tail)
728
os.rename(self.abspath(f), self.abspath(dest_path))
730
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
731
["rename rolled back"])
733
self._write_inventory(inv)
939
761
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
944
>>> orig = ScratchBranch(files=["file1", "file2"])
945
>>> clone = orig.clone()
946
>>> os.path.samefile(orig.base, clone.base)
948
>>> os.path.isfile(os.path.join(clone.base, "file1"))
951
base = tempfile.mkdtemp()
953
shutil.copytree(self.base, base, symlinks=True)
954
return ScratchBranch(base=base)
956
764
def __del__(self):
959
767
def destroy(self):
960
768
"""Destroy the test branch, removing the scratch directory."""
963
mutter("delete ScratchBranch %s" % self.base)
964
shutil.rmtree(self.base)
770
mutter("delete ScratchBranch %s" % self.base)
771
shutil.rmtree(self.base)
965
772
except OSError, e:
966
773
# Work around for shutil.rmtree failing on Windows when
967
774
# readonly files are encountered