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))
77
48
def find_branch_root(f=None):
104
75
raise BzrError('%r is not in a branch' % orig_f)
107
class DivergedBranches(Exception):
108
def __init__(self, branch1, branch2):
109
self.branch1 = branch1
110
self.branch2 = branch2
111
Exception.__init__(self, "These branches have diverged.")
113
80
######################################################################
116
class Branch(object):
117
84
"""Branch holding a history of revisions.
120
87
Base directory of the branch.
126
If _lock_mode is true, a positive count of the number of times the
130
Lock object from bzrlib.lock.
137
def __init__(self, base, init=False, find_root=True):
91
def __init__(self, base, init=False, find_root=True, lock_mode='w'):
138
92
"""Create new branch object at a particular location.
140
94
base -- Base directory for the branch.
158
112
self.base = os.path.realpath(base)
159
113
if not isdir(self.controlfilename('.')):
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'])
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'])
164
117
self._check_format()
166
120
self.text_store = ImmutableStore(self.controlfilename('text-store'))
167
121
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
175
129
__repr__ = __str__
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'
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)
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
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)
229
181
def abspath(self, name):
302
260
fmt = self.controlfile('branch-format', 'r').read()
303
261
fmt.replace('\r\n', '')
304
262
if fmt != BZR_BRANCH_FORMAT:
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'])
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'])
311
268
def read_working_inventory(self):
312
269
"""Read the working inventory."""
270
self._need_readlock()
313
271
before = time.time()
314
272
# 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))
274
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
275
mutter("loaded inventory of %d items in %f"
276
% (len(inv), time.time() - before))
326
280
def _write_inventory(self, inv):
327
281
"""Update the working inventory.
354
309
This puts the files in the Added state, so that they will be
355
310
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
312
TODO: Perhaps have an option to add the ids even if the files do
368
315
TODO: Perhaps return the ids of the files? But then again it
369
is easy to retrieve them if they're needed.
316
is easy to retrieve them if they're needed.
318
TODO: Option to specify file id.
371
320
TODO: Adding a directory should optionally recurse down and
372
add all non-ignored children. Perhaps do that in a
321
add all non-ignored children. Perhaps do that in a
324
self._need_writelock()
375
326
# TODO: Re-adding a file that is removed in the working copy
376
327
# should probably put it back with the previous ID.
377
328
if isinstance(files, types.StringTypes):
384
335
ids = [None] * len(files)
386
337
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)
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)
425
372
def print_file(self, file, revno):
426
373
"""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)
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)
439
383
def remove(self, files, verbose=False):
440
384
"""Mark nominated files for removal from the inventory.
453
397
## TODO: Normalize names
454
398
## TODO: Remove nested loops; better scalability
399
self._need_writelock()
455
401
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
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)
485
424
def set_inventory(self, new_inventory_list):
486
425
inv = Inventory()
487
426
for path, file_id, parent, kind in new_inventory_list:
561
503
>>> 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]
506
self._need_readlock()
507
return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
616
510
def enum_history(self, direction):
617
511
"""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
551
def commit(self, *args, **kw):
747
553
from bzrlib.commit import commit
799
606
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)
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)
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)
847
652
def move(self, from_paths, to_name):
855
660
Note that to_name is only the last component of the new name;
856
661
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)
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)
923
def __init__(self, files=[], dirs=[], base=None):
726
def __init__(self, files=[], dirs=[]):
924
727
"""Make a test branch.
926
729
This creates a temporary directory and runs init-tree in it.
928
731
If any files are listed, they are created in the working copy.
932
base = tempfile.mkdtemp()
934
Branch.__init__(self, base, init=init)
733
Branch.__init__(self, tempfile.mkdtemp(), init=True)
936
735
os.mkdir(self.abspath(d))
939
738
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
741
def __del__(self):
959
744
def destroy(self):
960
745
"""Destroy the test branch, removing the scratch directory."""
963
mutter("delete ScratchBranch %s" % self.base)
964
shutil.rmtree(self.base)
747
mutter("delete ScratchBranch %s" % self.base)
748
shutil.rmtree(self.base)
965
749
except OSError, e:
966
750
# Work around for shutil.rmtree failing on Windows when
967
751
# readonly files are encountered