15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
21
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
22
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
25
23
sha_file, appendpath, file_kind
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
DivergedBranches, NotBranchError
29
from bzrlib.textui import show_status
30
from bzrlib.revision import Revision
31
from bzrlib.delta import compare_trees
32
from bzrlib.tree import EmptyTree, RevisionTree
24
from bzrlib.errors import BzrError
38
26
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
39
27
## TODO: Maybe include checks for common corruption of newlines, etc?
42
# TODO: Some operations like log might retrieve the same revisions
43
# repeatedly to calculate deltas. We could perhaps have a weakref
44
# cache in memory to make this faster.
46
# TODO: please move the revision-string syntax stuff out of the branch
47
# object; it's clutter
50
31
def find_branch(f, **args):
51
32
if f and (f.startswith('http://') or f.startswith('https://')):
52
from bzrlib.remotebranch import RemoteBranch
53
return RemoteBranch(f, **args)
34
return remotebranch.RemoteBranch(f, **args)
55
return LocalBranch(f, **args)
36
return Branch(f, **args)
58
39
def find_cached_branch(f, cache_root, **args):
59
from bzrlib.remotebranch import RemoteBranch
40
from remotebranch import RemoteBranch
60
41
br = find_branch(f, **args)
61
42
def cacheify(br, store_name):
62
from bzrlib.meta_store import CachedStore
43
from meta_store import CachedStore
63
44
cache_path = os.path.join(cache_root, store_name)
64
45
os.mkdir(cache_path)
65
46
new_store = CachedStore(getattr(br, store_name), cache_path)
127
108
head, tail = os.path.split(f)
129
110
# reached the root, whatever that may be
130
raise NotBranchError('%s is not in a branch' % orig_f)
111
raise BzrError('%r is not in a branch' % orig_f)
114
class DivergedBranches(Exception):
115
def __init__(self, branch1, branch2):
116
self.branch1 = branch1
117
self.branch2 = branch2
118
Exception.__init__(self, "These branches have diverged.")
121
class NoSuchRevision(BzrError):
122
def __init__(self, branch, revision):
124
self.revision = revision
125
msg = "Branch %s has no revision %d" % (branch, revision)
126
BzrError.__init__(self, msg)
136
129
######################################################################
140
133
"""Branch holding a history of revisions.
143
Base directory/url of the branch.
147
def __new__(cls, *a, **kw):
148
"""this is temporary, till we get rid of all code that does
151
# XXX: AAARGH! MY EYES! UUUUGLY!!!
154
b = object.__new__(cls)
158
class LocalBranch(Branch):
159
"""A branch stored in the actual filesystem.
161
Note that it's "local" in the context of the filesystem; it doesn't
162
really matter if it's on an nfs/smb/afs/coda/... share, as long as
163
it's writable, and can be accessed via the normal filesystem API.
136
Base directory of the branch.
166
139
None, or 'r' or 'w'
267
242
self._lock = None
268
243
self._lock_mode = self._lock_count = None
270
246
def abspath(self, name):
271
247
"""Return absolute filename for something in the branch"""
272
248
return os.path.join(self.base, name)
274
251
def relpath(self, path):
275
252
"""Return path relative to this branch of something inside it.
277
254
Raises an error if path is not in this branch."""
278
255
return _relpath(self.base, path)
280
258
def controlfilename(self, file_or_path):
281
259
"""Return location relative to branch."""
282
260
if isinstance(file_or_path, basestring):
345
322
# on Windows from Linux and so on. I think it might be better
346
323
# to always make all internal files in unix format.
347
324
fmt = self.controlfile('branch-format', 'r').read()
348
fmt = fmt.replace('\r\n', '\n')
325
fmt.replace('\r\n', '')
349
326
if fmt != BZR_BRANCH_FORMAT:
350
327
raise BzrError('sorry, branch format %r not supported' % fmt,
351
328
['use a different bzr version',
352
329
'or remove the .bzr directory and "bzr init" again'])
354
def get_root_id(self):
355
"""Return the id of this branches root"""
356
inv = self.read_working_inventory()
357
return inv.root.file_id
359
def set_root_id(self, file_id):
360
inv = self.read_working_inventory()
361
orig_root_id = inv.root.file_id
362
del inv._byid[inv.root.file_id]
363
inv.root.file_id = file_id
364
inv._byid[inv.root.file_id] = inv.root
367
if entry.parent_id in (None, orig_root_id):
368
entry.parent_id = inv.root.file_id
369
self._write_inventory(inv)
371
333
def read_working_inventory(self):
372
334
"""Read the working inventory."""
373
335
from bzrlib.inventory import Inventory
336
from bzrlib.xml import unpack_xml
337
from time import time
376
341
# ElementTree does its own conversion from UTF-8, so open in
378
f = self.controlfile('inventory', 'rb')
379
return bzrlib.xml.serializer_v4.read_inventory(f)
343
inv = unpack_xml(Inventory,
344
self.controlfile('inventory', 'rb'))
345
mutter("loaded inventory of %d items in %f"
346
% (len(inv), time() - before))
407
376
"""Inventory for the working copy.""")
410
def add(self, files, ids=None):
379
def add(self, files, verbose=False, ids=None):
411
380
"""Make files versioned.
413
Note that the command line normally calls smart_add instead,
414
which can automatically recurse.
382
Note that the command line normally calls smart_add instead.
416
384
This puts the files in the Added state, so that they will be
417
385
recorded by the next commit.
427
395
TODO: Perhaps have an option to add the ids even if the files do
430
TODO: Perhaps yield the ids and paths as they're added.
398
TODO: Perhaps return the ids of the files? But then again it
399
is easy to retrieve them if they're needed.
401
TODO: Adding a directory should optionally recurse down and
402
add all non-ignored children. Perhaps do that in a
405
from bzrlib.textui import show_status
432
406
# TODO: Re-adding a file that is removed in the working copy
433
407
# should probably put it back with the previous ID.
434
408
if isinstance(files, basestring):
566
544
return self.working_tree().unknowns()
569
def append_revision(self, *revision_ids):
547
def append_revision(self, revision_id):
570
548
from bzrlib.atomicfile import AtomicFile
572
for revision_id in revision_ids:
573
mutter("add {%s} to revision-history" % revision_id)
575
rev_history = self.revision_history()
576
rev_history.extend(revision_ids)
550
mutter("add {%s} to revision-history" % revision_id)
551
rev_history = self.revision_history() + [revision_id]
578
553
f = AtomicFile(self.controlfilename('revision-history'))
587
def get_revision_xml_file(self, revision_id):
588
"""Return XML file object for revision object."""
589
if not revision_id or not isinstance(revision_id, basestring):
590
raise InvalidRevisionId(revision_id)
595
return self.revision_store[revision_id]
597
raise bzrlib.errors.NoSuchRevision(self, revision_id)
603
get_revision_xml = get_revision_xml_file
606
562
def get_revision(self, revision_id):
607
563
"""Return the Revision object for a named revision"""
608
xml_file = self.get_revision_xml_file(revision_id)
564
from bzrlib.revision import Revision
565
from bzrlib.xml import unpack_xml
611
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
612
except SyntaxError, e:
613
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
569
if not revision_id or not isinstance(revision_id, basestring):
570
raise ValueError('invalid revision-id: %r' % revision_id)
571
r = unpack_xml(Revision, self.revision_store[revision_id])
617
575
assert r.revision_id == revision_id
621
def get_revision_delta(self, revno):
622
"""Return the delta for one revision.
624
The delta is relative to its mainline predecessor, or the
625
empty tree for revision 1.
627
assert isinstance(revno, int)
628
rh = self.revision_history()
629
if not (1 <= revno <= len(rh)):
630
raise InvalidRevisionNumber(revno)
632
# revno is 1-based; list is 0-based
634
new_tree = self.revision_tree(rh[revno-1])
636
old_tree = EmptyTree()
638
old_tree = self.revision_tree(rh[revno-2])
640
return compare_trees(old_tree, new_tree)
644
579
def get_revision_sha1(self, revision_id):
659
594
parameter which can be either an integer revno or a
661
596
from bzrlib.inventory import Inventory
663
f = self.get_inventory_xml_file(inventory_id)
664
return bzrlib.xml.serializer_v4.read_inventory(f)
667
def get_inventory_xml(self, inventory_id):
668
"""Get inventory XML as a file object."""
669
return self.inventory_store[inventory_id]
671
get_inventory_xml_file = get_inventory_xml
597
from bzrlib.xml import unpack_xml
599
return unpack_xml(Inventory, self.inventory_store[inventory_id])
674
602
def get_inventory_sha1(self, inventory_id):
675
603
"""Return the sha1 hash of the inventory entry
677
return sha_file(self.get_inventory_xml(inventory_id))
605
return sha_file(self.inventory_store[inventory_id])
680
608
def get_revision_inventory(self, revision_id):
681
609
"""Return inventory of a past revision."""
682
# bzr 0.0.6 imposes the constraint that the inventory_id
683
# must be the same as its revision, so this is trivial.
684
610
if revision_id == None:
685
611
from bzrlib.inventory import Inventory
686
return Inventory(self.get_root_id())
688
return self.get_inventory(revision_id)
614
return self.get_inventory(self.get_revision(revision_id).inventory_id)
691
617
def revision_history(self):
705
631
def common_ancestor(self, other, self_revno=None, other_revno=None):
707
>>> from bzrlib.commit import commit
708
634
>>> sb = ScratchBranch(files=['foo', 'foo~'])
709
635
>>> sb.common_ancestor(sb) == (None, None)
711
>>> commit(sb, "Committing first revision", verbose=False)
637
>>> commit.commit(sb, "Committing first revision", verbose=False)
712
638
>>> sb.common_ancestor(sb)[0]
714
640
>>> clone = sb.clone()
715
>>> commit(sb, "Committing second revision", verbose=False)
641
>>> commit.commit(sb, "Committing second revision", verbose=False)
716
642
>>> sb.common_ancestor(sb)[0]
718
644
>>> sb.common_ancestor(clone)[0]
720
>>> commit(clone, "Committing divergent second revision",
646
>>> commit.commit(clone, "Committing divergent second revision",
721
647
... verbose=False)
722
648
>>> sb.common_ancestor(clone)[0]
746
672
return r+1, my_history[r]
747
673
return None, None
675
def enum_history(self, direction):
676
"""Return (revno, revision_id) for history of branch.
679
'forward' is from earliest to latest
680
'reverse' is from latest to earliest
682
rh = self.revision_history()
683
if direction == 'forward':
688
elif direction == 'reverse':
694
raise ValueError('invalid history direction', direction)
751
698
"""Return current revision number for this branch.
805
752
if stop_revision is None:
806
753
stop_revision = other_len
807
754
elif stop_revision > other_len:
808
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
755
raise NoSuchRevision(self, stop_revision)
810
757
return other_history[self_len:stop_revision]
813
760
def update_revisions(self, other, stop_revision=None):
814
761
"""Pull in all new revisions from other branch.
763
>>> from bzrlib.commit import commit
764
>>> bzrlib.trace.silent = True
765
>>> br1 = ScratchBranch(files=['foo', 'bar'])
768
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
769
>>> br2 = ScratchBranch()
770
>>> br2.update_revisions(br1)
774
>>> br2.revision_history()
776
>>> br2.update_revisions(br1)
780
>>> br1.text_store.total_size() == br2.text_store.total_size()
816
from bzrlib.fetch import greedy_fetch
818
pb = bzrlib.ui.ui_factory.progress_bar()
783
from bzrlib.progress import ProgressBar
787
from sets import Set as set
819
791
pb.update('comparing histories')
821
792
revision_ids = self.missing_revisions(other, stop_revision)
823
if len(revision_ids) > 0:
824
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
827
self.append_revision(*revision_ids)
828
## note("Added %d revisions." % count)
831
def install_revisions(self, other, revision_ids, pb):
832
794
if hasattr(other.revision_store, "prefetch"):
833
795
other.revision_store.prefetch(revision_ids)
834
796
if hasattr(other.inventory_store, "prefetch"):
835
797
inventory_ids = [other.get_revision(r).inventory_id
836
798
for r in revision_ids]
837
799
other.inventory_store.prefetch(inventory_ids)
840
pb = bzrlib.ui.ui_factory.progress_bar()
843
802
needed_texts = set()
847
for i, rev_id in enumerate(revision_ids):
848
pb.update('fetching revision', i+1, len(revision_ids))
850
rev = other.get_revision(rev_id)
851
except bzrlib.errors.NoSuchRevision:
804
for rev_id in revision_ids:
806
pb.update('fetching revision', i, len(revision_ids))
807
rev = other.get_revision(rev_id)
855
808
revisions.append(rev)
856
809
inv = other.get_inventory(str(rev.inventory_id))
857
810
for key, entry in inv.iter_entries():
865
count, cp_fail = self.text_store.copy_multi(other.text_store,
867
#print "Added %d texts." % count
818
count = self.text_store.copy_multi(other.text_store, needed_texts)
819
print "Added %d texts." % count
868
820
inventory_ids = [ f.inventory_id for f in revisions ]
869
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
871
#print "Added %d inventories." % count
821
count = self.inventory_store.copy_multi(other.inventory_store,
823
print "Added %d inventories." % count
872
824
revision_ids = [ f.revision_id for f in revisions]
874
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
877
assert len(cp_fail) == 0
878
return count, failures
825
count = self.revision_store.copy_multi(other.revision_store,
827
for revision_id in revision_ids:
828
self.append_revision(revision_id)
829
print "Added %d revisions." % count
881
832
def commit(self, *args, **kw):
882
833
from bzrlib.commit import commit
883
834
commit(self, *args, **kw)
886
def lookup_revision(self, revision):
887
"""Return the revision identifier for a given revision specifier."""
888
# XXX: I'm not sure this method belongs here; I'd rather have the
889
# revision spec stuff be an UI thing, and branch blissfully unaware
891
# Also, I'm not entirely happy with this method returning None
892
# when the revision doesn't exist.
893
# But I'm keeping the contract I found, because this seems to be
894
# used in a lot of places - and when I do change these, I'd rather
895
# figure out case-by-case which ones actually want to care about
896
# revision specs (eg, they are UI-level) and which ones should trust
897
# that they have a revno/revid.
898
# -- lalo@exoweb.net, 2005-09-07
899
from bzrlib.errors import NoSuchRevision
900
from bzrlib.revisionspec import RevisionSpec
902
spec = RevisionSpec(self, revision)
903
except NoSuchRevision:
908
def revision_id_to_revno(self, revision_id):
909
"""Given a revision id, return its revno"""
910
history = self.revision_history()
912
return history.index(revision_id) + 1
914
raise bzrlib.errors.NoSuchRevision(self, revision_id)
917
def get_rev_id(self, revno, history=None):
918
"""Find the revision id of the specified revno."""
837
def lookup_revision(self, revno):
838
"""Return revision hash for revision number."""
922
history = self.revision_history()
923
elif revno <= 0 or revno > len(history):
924
raise bzrlib.errors.NoSuchRevision(self, revno)
925
return history[revno - 1]
843
# list is 0-based; revisions are 1-based
844
return self.revision_history()[revno-1]
846
raise BzrError("no such revision %s" % revno)
927
849
def revision_tree(self, revision_id):
928
850
"""Return Tree for a revision on this branch.
930
852
`revision_id` may be None for the null revision, in which case
931
853
an `EmptyTree` is returned."""
854
from bzrlib.tree import EmptyTree, RevisionTree
932
855
# TODO: refactor this to use an existing revision object
933
856
# so we don't need to read it in twice.
934
857
if revision_id == None:
1165
def get_parent(self):
1166
"""Return the parent location of the branch.
1168
This is the default location for push/pull/missing. The usual
1169
pattern is that the user can override it by specifying a
1173
_locs = ['parent', 'pull', 'x-pull']
1176
return self.controlfile(l, 'r').read().strip('\n')
1178
if e.errno != errno.ENOENT:
1183
def set_parent(self, url):
1184
# TODO: Maybe delete old location files?
1185
from bzrlib.atomicfile import AtomicFile
1188
f = AtomicFile(self.controlfilename('parent'))
1197
def check_revno(self, revno):
1199
Check whether a revno corresponds to any revision.
1200
Zero (the NULL revision) is considered valid.
1203
self.check_real_revno(revno)
1205
def check_real_revno(self, revno):
1207
Check whether a revno corresponds to a real revision.
1208
Zero (the NULL revision) is considered invalid
1210
if revno < 1 or revno > self.revno():
1211
raise InvalidRevisionNumber(revno)
1216
class ScratchBranch(LocalBranch):
1086
class ScratchBranch(Branch):
1217
1087
"""Special test class: a branch that cleans up after itself.
1219
1089
>>> b = ScratchBranch()
1331
1199
s = hexlify(rand_bytes(8))
1332
1200
return '-'.join((name, compact_date(time()), s))
1336
"""Return a new tree-root file id."""
1337
return gen_file_id('TREE_ROOT')
1340
def copy_branch(branch_from, to_location, revision=None):
1341
"""Copy branch_from into the existing directory to_location.
1344
If not None, only revisions up to this point will be copied.
1345
The head of the new branch will be that revision.
1348
The name of a local directory that exists but is empty.
1350
from bzrlib.merge import merge
1351
from bzrlib.revisionspec import RevisionSpec
1353
assert isinstance(branch_from, Branch)
1354
assert isinstance(to_location, basestring)
1356
br_to = Branch(to_location, init=True)
1357
br_to.set_root_id(branch_from.get_root_id())
1358
if revision is None:
1359
revno = branch_from.revno()
1361
revno, rev_id = RevisionSpec(branch_from, revision)
1362
br_to.update_revisions(branch_from, stop_revision=revno)
1363
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1364
check_clean=False, ignore_zero=True)
1366
from_location = branch_from.base
1367
br_to.set_parent(branch_from.base)