43
43
return remotebranch.RemoteBranch(f, **args)
45
45
return Branch(f, **args)
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))
48
77
def find_branch_root(f=None):
112
158
self.base = os.path.realpath(base)
113
159
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'])
160
from errors import NotBranchError
161
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
162
['use "bzr init" to initialize a new working tree',
163
'current bzr can only operate from top-of-tree'])
117
164
self._check_format()
120
166
self.text_store = ImmutableStore(self.controlfilename('text-store'))
121
167
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
129
175
__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
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)
179
if self._lock_mode or self._lock:
180
from warnings import warn
181
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'
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)
217
if not self._lock_mode:
218
from errors import LockError
219
raise LockError('branch %r is not locked' % (self))
221
if self._lock_count > 1:
222
self._lock_count -= 1
226
self._lock_mode = self._lock_count = None
181
229
def abspath(self, name):
260
302
fmt = self.controlfile('branch-format', 'r').read()
261
303
fmt.replace('\r\n', '')
262
304
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'])
305
raise BzrError('sorry, branch format %r not supported' % fmt,
306
['use a different bzr version',
307
'or remove the .bzr directory and "bzr init" again'])
268
311
def read_working_inventory(self):
269
312
"""Read the working inventory."""
270
self._need_readlock()
271
313
before = time.time()
272
314
# ElementTree does its own conversion from UTF-8, so open in
274
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
275
mutter("loaded inventory of %d items in %f"
276
% (len(inv), time.time() - before))
318
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
319
mutter("loaded inventory of %d items in %f"
320
% (len(inv), time.time() - before))
280
326
def _write_inventory(self, inv):
281
327
"""Update the working inventory.
309
354
This puts the files in the Added state, so that they will be
310
355
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.
312
365
TODO: Perhaps have an option to add the ids even if the files do
315
368
TODO: Perhaps return the ids of the files? But then again it
316
is easy to retrieve them if they're needed.
318
TODO: Option to specify file id.
369
is easy to retrieve them if they're needed.
320
371
TODO: Adding a directory should optionally recurse down and
321
add all non-ignored children. Perhaps do that in a
372
add all non-ignored children. Perhaps do that in a
324
self._need_writelock()
326
375
# TODO: Re-adding a file that is removed in the working copy
327
376
# should probably put it back with the previous ID.
328
377
if isinstance(files, types.StringTypes):
335
384
ids = [None] * len(files)
337
386
assert(len(ids) == len(files))
339
inv = self.read_working_inventory()
340
for f,file_id in zip(files, ids):
341
if is_control_file(f):
342
bailout("cannot add control file %s" % quotefn(f))
347
bailout("cannot add top-level %r" % f)
349
fullpath = os.path.normpath(self.abspath(f))
352
kind = file_kind(fullpath)
354
# maybe something better?
355
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
357
if kind != 'file' and kind != 'directory':
358
bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
361
file_id = gen_file_id(f)
362
inv.add_path(f, kind=kind, file_id=file_id)
365
show_status('A', kind, quotefn(f))
367
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
369
self._write_inventory(inv)
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)
372
425
def print_file(self, file, revno):
373
426
"""Print `file` to stdout."""
374
self._need_readlock()
375
tree = self.revision_tree(self.lookup_revision(revno))
376
# use inventory as it was in that revision
377
file_id = tree.inventory.path2id(file)
379
bailout("%r is not present in revision %d" % (file, revno))
380
tree.print_file(file_id)
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)
383
439
def remove(self, files, verbose=False):
384
440
"""Mark nominated files for removal from the inventory.
397
453
## TODO: Normalize names
398
454
## TODO: Remove nested loops; better scalability
399
self._need_writelock()
401
455
if isinstance(files, types.StringTypes):
404
tree = self.working_tree()
407
# do this before any modifications
411
bailout("cannot remove unversioned file %s" % quotefn(f))
412
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
414
# having remove it, it must be either ignored or unknown
415
if tree.is_ignored(f):
419
show_status(new_status, inv[fid].kind, quotefn(f))
422
self._write_inventory(inv)
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
424
485
def set_inventory(self, new_inventory_list):
425
486
inv = Inventory()
426
487
for path, file_id, parent, kind in new_inventory_list:
503
561
>>> ScratchBranch().revision_history()
506
self._need_readlock()
507
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
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]
510
616
def enum_history(self, direction):
511
617
"""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
"""Pull in all new revisions from other branch.
700
>>> from bzrlib.commit import commit
701
>>> bzrlib.trace.silent = True
702
>>> br1 = ScratchBranch(files=['foo', 'bar'])
705
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
706
>>> br2 = ScratchBranch()
707
>>> br2.update_revisions(br1)
711
>>> br2.revision_history()
713
>>> br2.update_revisions(br1)
717
>>> br1.text_store.total_size() == br2.text_store.total_size()
720
revision_ids = self.missing_revisions(other)
721
revisions = [other.get_revision(f) for f in revision_ids]
722
needed_texts = sets.Set()
723
for rev in revisions:
724
inv = other.get_inventory(str(rev.inventory_id))
725
for key, entry in inv.iter_entries():
726
if entry.text_id is None:
728
if entry.text_id not in self.text_store:
729
needed_texts.add(entry.text_id)
730
count = self.text_store.copy_multi(other.text_store, needed_texts)
731
print "Added %d texts." % count
732
inventory_ids = [ f.inventory_id for f in revisions ]
733
count = self.inventory_store.copy_multi(other.inventory_store,
735
print "Added %d inventories." % count
736
revision_ids = [ f.revision_id for f in revisions]
737
count = self.revision_store.copy_multi(other.revision_store,
739
for revision_id in revision_ids:
740
self.append_revision(revision_id)
741
print "Added %d revisions." % count
551
744
def commit(self, *args, **kw):
553
746
from bzrlib.commit import commit
606
798
This can change the directory or the filename or both.
608
self._need_writelock()
609
tree = self.working_tree()
611
if not tree.has_filename(from_rel):
612
bailout("can't rename: old working file %r does not exist" % from_rel)
613
if tree.has_filename(to_rel):
614
bailout("can't rename: new working file %r already exists" % to_rel)
616
file_id = inv.path2id(from_rel)
618
bailout("can't rename: old name %r is not versioned" % from_rel)
620
if inv.path2id(to_rel):
621
bailout("can't rename: new name %r is already versioned" % to_rel)
623
to_dir, to_tail = os.path.split(to_rel)
624
to_dir_id = inv.path2id(to_dir)
625
if to_dir_id == None and to_dir != '':
626
bailout("can't determine destination directory id for %r" % to_dir)
628
mutter("rename_one:")
629
mutter(" file_id {%s}" % file_id)
630
mutter(" from_rel %r" % from_rel)
631
mutter(" to_rel %r" % to_rel)
632
mutter(" to_dir %r" % to_dir)
633
mutter(" to_dir_id {%s}" % to_dir_id)
635
inv.rename(file_id, to_dir_id, to_tail)
637
print "%s => %s" % (from_rel, to_rel)
639
from_abs = self.abspath(from_rel)
640
to_abs = self.abspath(to_rel)
642
os.rename(from_abs, to_abs)
644
bailout("failed to rename %r to %r: %s"
645
% (from_abs, to_abs, e[1]),
646
["rename rolled back"])
648
self._write_inventory(inv)
802
tree = self.working_tree()
804
if not tree.has_filename(from_rel):
805
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
806
if tree.has_filename(to_rel):
807
raise BzrError("can't rename: new working file %r already exists" % to_rel)
809
file_id = inv.path2id(from_rel)
811
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
813
if inv.path2id(to_rel):
814
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
816
to_dir, to_tail = os.path.split(to_rel)
817
to_dir_id = inv.path2id(to_dir)
818
if to_dir_id == None and to_dir != '':
819
raise BzrError("can't determine destination directory id for %r" % to_dir)
821
mutter("rename_one:")
822
mutter(" file_id {%s}" % file_id)
823
mutter(" from_rel %r" % from_rel)
824
mutter(" to_rel %r" % to_rel)
825
mutter(" to_dir %r" % to_dir)
826
mutter(" to_dir_id {%s}" % to_dir_id)
828
inv.rename(file_id, to_dir_id, to_tail)
830
print "%s => %s" % (from_rel, to_rel)
832
from_abs = self.abspath(from_rel)
833
to_abs = self.abspath(to_rel)
835
os.rename(from_abs, to_abs)
837
raise BzrError("failed to rename %r to %r: %s"
838
% (from_abs, to_abs, e[1]),
839
["rename rolled back"])
841
self._write_inventory(inv)
652
846
def move(self, from_paths, to_name):
660
854
Note that to_name is only the last component of the new name;
661
855
this doesn't change the directory.
663
self._need_writelock()
664
## TODO: Option to move IDs only
665
assert not isinstance(from_paths, basestring)
666
tree = self.working_tree()
668
to_abs = self.abspath(to_name)
669
if not isdir(to_abs):
670
bailout("destination %r is not a directory" % to_abs)
671
if not tree.has_filename(to_name):
672
bailout("destination %r not in working directory" % to_abs)
673
to_dir_id = inv.path2id(to_name)
674
if to_dir_id == None and to_name != '':
675
bailout("destination %r is not a versioned directory" % to_name)
676
to_dir_ie = inv[to_dir_id]
677
if to_dir_ie.kind not in ('directory', 'root_directory'):
678
bailout("destination %r is not a directory" % to_abs)
680
to_idpath = inv.get_idpath(to_dir_id)
683
if not tree.has_filename(f):
684
bailout("%r does not exist in working tree" % f)
685
f_id = inv.path2id(f)
687
bailout("%r is not versioned" % f)
688
name_tail = splitpath(f)[-1]
689
dest_path = appendpath(to_name, name_tail)
690
if tree.has_filename(dest_path):
691
bailout("destination %r already exists" % dest_path)
692
if f_id in to_idpath:
693
bailout("can't move %r to a subdirectory of itself" % f)
695
# OK, so there's a race here, it's possible that someone will
696
# create a file in this interval and then the rename might be
697
# left half-done. But we should have caught most problems.
700
name_tail = splitpath(f)[-1]
701
dest_path = appendpath(to_name, name_tail)
702
print "%s => %s" % (f, dest_path)
703
inv.rename(inv.path2id(f), to_dir_id, name_tail)
705
os.rename(self.abspath(f), self.abspath(dest_path))
707
bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
708
["rename rolled back"])
710
self._write_inventory(inv)
859
## TODO: Option to move IDs only
860
assert not isinstance(from_paths, basestring)
861
tree = self.working_tree()
863
to_abs = self.abspath(to_name)
864
if not isdir(to_abs):
865
raise BzrError("destination %r is not a directory" % to_abs)
866
if not tree.has_filename(to_name):
867
raise BzrError("destination %r not in working directory" % to_abs)
868
to_dir_id = inv.path2id(to_name)
869
if to_dir_id == None and to_name != '':
870
raise BzrError("destination %r is not a versioned directory" % to_name)
871
to_dir_ie = inv[to_dir_id]
872
if to_dir_ie.kind not in ('directory', 'root_directory'):
873
raise BzrError("destination %r is not a directory" % to_abs)
875
to_idpath = inv.get_idpath(to_dir_id)
878
if not tree.has_filename(f):
879
raise BzrError("%r does not exist in working tree" % f)
880
f_id = inv.path2id(f)
882
raise BzrError("%r is not versioned" % f)
883
name_tail = splitpath(f)[-1]
884
dest_path = appendpath(to_name, name_tail)
885
if tree.has_filename(dest_path):
886
raise BzrError("destination %r already exists" % dest_path)
887
if f_id in to_idpath:
888
raise BzrError("can't move %r to a subdirectory of itself" % f)
890
# OK, so there's a race here, it's possible that someone will
891
# create a file in this interval and then the rename might be
892
# left half-done. But we should have caught most problems.
895
name_tail = splitpath(f)[-1]
896
dest_path = appendpath(to_name, name_tail)
897
print "%s => %s" % (f, dest_path)
898
inv.rename(inv.path2id(f), to_dir_id, name_tail)
900
os.rename(self.abspath(f), self.abspath(dest_path))
902
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
903
["rename rolled back"])
905
self._write_inventory(inv)
726
def __init__(self, files=[], dirs=[]):
922
def __init__(self, files=[], dirs=[], base=None):
727
923
"""Make a test branch.
729
925
This creates a temporary directory and runs init-tree in it.
731
927
If any files are listed, they are created in the working copy.
733
Branch.__init__(self, tempfile.mkdtemp(), init=True)
931
base = tempfile.mkdtemp()
933
Branch.__init__(self, base, init=init)
735
935
os.mkdir(self.abspath(d))
738
938
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
943
>>> orig = ScratchBranch(files=["file1", "file2"])
944
>>> clone = orig.clone()
945
>>> os.path.samefile(orig.base, clone.base)
947
>>> os.path.isfile(os.path.join(clone.base, "file1"))
950
base = tempfile.mkdtemp()
952
shutil.copytree(self.base, base, symlinks=True)
953
return ScratchBranch(base=base)
741
955
def __del__(self):
744
958
def destroy(self):
745
959
"""Destroy the test branch, removing the scratch directory."""
747
mutter("delete ScratchBranch %s" % self.base)
748
shutil.rmtree(self.base)
962
mutter("delete ScratchBranch %s" % self.base)
963
shutil.rmtree(self.base)
749
964
except OSError, e:
750
965
# Work around for shutil.rmtree failing on Windows when
751
966
# readonly files are encountered