1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
from warnings import warn
23
from cStringIO import StringIO
27
from bzrlib.inventory import InventoryEntry
28
import bzrlib.inventory as inventory
29
from bzrlib.trace import mutter, note
30
from bzrlib.osutils import (isdir, quotefn, compact_date, rand_bytes,
31
rename, splitpath, sha_file, appendpath,
33
import bzrlib.errors as errors
34
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
35
NoSuchRevision, HistoryMissing, NotBranchError,
36
DivergedBranches, LockError, UnlistableStore,
37
UnlistableBranch, NoSuchFile, NotVersionedError,
39
from bzrlib.textui import show_status
40
from bzrlib.revision import (Revision, is_ancestor, get_intervening_revisions,
43
from bzrlib.delta import compare_trees
44
from bzrlib.tree import EmptyTree, RevisionTree
45
from bzrlib.inventory import Inventory
46
from bzrlib.store import copy_all
47
from bzrlib.store.compressed_text import CompressedTextStore
48
from bzrlib.store.text import TextStore
49
from bzrlib.store.weave import WeaveStore
50
from bzrlib.testament import Testament
51
import bzrlib.transactions as transactions
52
from bzrlib.transport import Transport, get_transport
55
from config import TreeConfig
58
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
59
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
60
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
61
## TODO: Maybe include checks for common corruption of newlines, etc?
64
# TODO: Some operations like log might retrieve the same revisions
65
# repeatedly to calculate deltas. We could perhaps have a weakref
66
# cache in memory to make this faster. In general anything can be
67
# cached in memory between lock and unlock operations.
69
def find_branch(*ignored, **ignored_too):
70
# XXX: leave this here for about one release, then remove it
71
raise NotImplementedError('find_branch() is not supported anymore, '
72
'please use one of the new branch constructors')
75
def needs_read_lock(unbound):
76
"""Decorate unbound to take out and release a read lock."""
77
def decorated(self, *args, **kwargs):
80
return unbound(self, *args, **kwargs)
86
def needs_write_lock(unbound):
87
"""Decorate unbound to take out and release a write lock."""
88
def decorated(self, *args, **kwargs):
91
return unbound(self, *args, **kwargs)
96
######################################################################
100
"""Branch holding a history of revisions.
103
Base directory/url of the branch.
107
def __init__(self, *ignored, **ignored_too):
108
raise NotImplementedError('The Branch class is abstract')
111
def open_downlevel(base):
112
"""Open a branch which may be of an old format.
114
Only local branches are supported."""
115
return _Branch(get_transport(base), relax_version_check=True)
119
"""Open an existing branch, rooted at 'base' (url)"""
120
t = get_transport(base)
121
mutter("trying to open %r with transport %r", base, t)
125
def open_containing(url):
126
"""Open an existing branch which contains url.
128
This probes for a branch at url, and searches upwards from there.
130
Basically we keep looking up until we find the control directory or
131
run into the root. If there isn't one, raises NotBranchError.
132
If there is one, it is returned, along with the unused portion of url.
134
t = get_transport(url)
137
return _Branch(t), t.relpath(url)
138
except NotBranchError:
140
new_t = t.clone('..')
141
if new_t.base == t.base:
142
# reached the root, whatever that may be
143
raise NotBranchError(path=url)
147
def initialize(base):
148
"""Create a new branch, rooted at 'base' (url)"""
149
t = get_transport(base)
150
return _Branch(t, init=True)
152
def setup_caching(self, cache_root):
153
"""Subclasses that care about caching should override this, and set
154
up cached stores located under cache_root.
156
self.cache_root = cache_root
159
cfg = self.tree_config()
160
return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
162
def _set_nick(self, nick):
163
cfg = self.tree_config()
164
cfg.set_option(nick, "nickname")
165
assert cfg.get_option("nickname") == nick
167
nick = property(_get_nick, _set_nick)
171
class _Branch(Branch):
172
"""A branch stored in the actual filesystem.
174
Note that it's "local" in the context of the filesystem; it doesn't
175
really matter if it's on an nfs/smb/afs/coda/... share, as long as
176
it's writable, and can be accessed via the normal filesystem API.
182
If _lock_mode is true, a positive count of the number of times the
186
Lock object from bzrlib.lock.
188
# We actually expect this class to be somewhat short-lived; part of its
189
# purpose is to try to isolate what bits of the branch logic are tied to
190
# filesystem access, so that in a later step, we can extricate them to
191
# a separarte ("storage") class.
195
_inventory_weave = None
197
# Map some sort of prefix into a namespace
198
# stuff like "revno:10", "revid:", etc.
199
# This should match a prefix with a function which accepts
200
REVISION_NAMESPACES = {}
202
def push_stores(self, branch_to):
203
"""Copy the content of this branches store to branch_to."""
204
if (self._branch_format != branch_to._branch_format
205
or self._branch_format != 4):
206
from bzrlib.fetch import greedy_fetch
207
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
208
self, self._branch_format, branch_to, branch_to._branch_format)
209
greedy_fetch(to_branch=branch_to, from_branch=self,
210
revision=self.last_revision())
213
store_pairs = ((self.text_store, branch_to.text_store),
214
(self.inventory_store, branch_to.inventory_store),
215
(self.revision_store, branch_to.revision_store))
217
for from_store, to_store in store_pairs:
218
copy_all(from_store, to_store)
219
except UnlistableStore:
220
raise UnlistableBranch(from_store)
222
def __init__(self, transport, init=False,
223
relax_version_check=False):
224
"""Create new branch object at a particular location.
226
transport -- A Transport object, defining how to access files.
228
init -- If True, create new control files in a previously
229
unversioned directory. If False, the branch must already
232
relax_version_check -- If true, the usual check for the branch
233
version is not applied. This is intended only for
234
upgrade/recovery type use; it's not guaranteed that
235
all operations will work on old format branches.
237
In the test suite, creation of new trees is tested using the
238
`ScratchBranch` class.
240
assert isinstance(transport, Transport), \
241
"%r is not a Transport" % transport
242
self._transport = transport
245
self._check_format(relax_version_check)
247
def get_store(name, compressed=True, prefixed=False):
248
# FIXME: This approach of assuming stores are all entirely compressed
249
# or entirely uncompressed is tidy, but breaks upgrade from
250
# some existing branches where there's a mixture; we probably
251
# still want the option to look for both.
252
relpath = self._rel_controlfilename(name)
254
store = CompressedTextStore(self._transport.clone(relpath),
257
store = TextStore(self._transport.clone(relpath),
259
#if self._transport.should_cache():
260
# cache_path = os.path.join(self.cache_root, name)
261
# os.mkdir(cache_path)
262
# store = bzrlib.store.CachedStore(store, cache_path)
264
def get_weave(name, prefixed=False):
265
relpath = self._rel_controlfilename(name)
266
ws = WeaveStore(self._transport.clone(relpath), prefixed=prefixed)
267
if self._transport.should_cache():
268
ws.enable_cache = True
271
if self._branch_format == 4:
272
self.inventory_store = get_store('inventory-store')
273
self.text_store = get_store('text-store')
274
self.revision_store = get_store('revision-store')
275
elif self._branch_format == 5:
276
self.control_weaves = get_weave('')
277
self.weave_store = get_weave('weaves')
278
self.revision_store = get_store('revision-store', compressed=False)
279
elif self._branch_format == 6:
280
self.control_weaves = get_weave('')
281
self.weave_store = get_weave('weaves', prefixed=True)
282
self.revision_store = get_store('revision-store', compressed=False,
284
self.revision_store.register_suffix('sig')
285
self._transaction = None
288
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
293
if self._lock_mode or self._lock:
294
# XXX: This should show something every time, and be suitable for
295
# headless operation and embedding
296
warn("branch %r was not explicitly unlocked" % self)
299
# TODO: It might be best to do this somewhere else,
300
# but it is nice for a Branch object to automatically
301
# cache it's information.
302
# Alternatively, we could have the Transport objects cache requests
303
# See the earlier discussion about how major objects (like Branch)
304
# should never expect their __del__ function to run.
305
if hasattr(self, 'cache_root') and self.cache_root is not None:
307
shutil.rmtree(self.cache_root)
310
self.cache_root = None
314
return self._transport.base
317
base = property(_get_base, doc="The URL for the root of this branch.")
319
def _finish_transaction(self):
320
"""Exit the current transaction."""
321
if self._transaction is None:
322
raise errors.LockError('Branch %s is not in a transaction' %
324
transaction = self._transaction
325
self._transaction = None
328
def get_transaction(self):
329
"""Return the current active transaction.
331
If no transaction is active, this returns a passthrough object
332
for which all data is immediately flushed and no caching happens.
334
if self._transaction is None:
335
return transactions.PassThroughTransaction()
337
return self._transaction
339
def _set_transaction(self, new_transaction):
340
"""Set a new active transaction."""
341
if self._transaction is not None:
342
raise errors.LockError('Branch %s is in a transaction already.' %
344
self._transaction = new_transaction
346
def lock_write(self):
347
mutter("lock write: %s (%s)", self, self._lock_count)
348
# TODO: Upgrade locking to support using a Transport,
349
# and potentially a remote locking protocol
351
if self._lock_mode != 'w':
352
raise LockError("can't upgrade to a write lock from %r" %
354
self._lock_count += 1
356
self._lock = self._transport.lock_write(
357
self._rel_controlfilename('branch-lock'))
358
self._lock_mode = 'w'
360
self._set_transaction(transactions.PassThroughTransaction())
363
mutter("lock read: %s (%s)", self, self._lock_count)
365
assert self._lock_mode in ('r', 'w'), \
366
"invalid lock mode %r" % self._lock_mode
367
self._lock_count += 1
369
self._lock = self._transport.lock_read(
370
self._rel_controlfilename('branch-lock'))
371
self._lock_mode = 'r'
373
self._set_transaction(transactions.ReadOnlyTransaction())
374
# 5K may be excessive, but hey, its a knob.
375
self.get_transaction().set_cache_size(5000)
378
mutter("unlock: %s (%s)", self, self._lock_count)
379
if not self._lock_mode:
380
raise LockError('branch %r is not locked' % (self))
382
if self._lock_count > 1:
383
self._lock_count -= 1
385
self._finish_transaction()
388
self._lock_mode = self._lock_count = None
390
def abspath(self, name):
391
"""Return absolute filename for something in the branch
393
XXX: Robert Collins 20051017 what is this used for? why is it a branch
394
method and not a tree method.
396
return self._transport.abspath(name)
398
def _rel_controlfilename(self, file_or_path):
399
if not isinstance(file_or_path, basestring):
400
file_or_path = '/'.join(file_or_path)
401
if file_or_path == '':
403
return bzrlib.transport.urlescape(bzrlib.BZRDIR + '/' + file_or_path)
405
def controlfilename(self, file_or_path):
406
"""Return location relative to branch."""
407
return self._transport.abspath(self._rel_controlfilename(file_or_path))
409
def controlfile(self, file_or_path, mode='r'):
410
"""Open a control file for this branch.
412
There are two classes of file in the control directory: text
413
and binary. binary files are untranslated byte streams. Text
414
control files are stored with Unix newlines and in UTF-8, even
415
if the platform or locale defaults are different.
417
Controlfiles should almost never be opened in write mode but
418
rather should be atomically copied and replaced using atomicfile.
422
relpath = self._rel_controlfilename(file_or_path)
423
#TODO: codecs.open() buffers linewise, so it was overloaded with
424
# a much larger buffer, do we need to do the same for getreader/getwriter?
426
return self._transport.get(relpath)
428
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
430
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
432
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
434
raise BzrError("invalid controlfile mode %r" % mode)
436
def put_controlfile(self, path, f, encode=True):
437
"""Write an entry as a controlfile.
439
:param path: The path to put the file, relative to the .bzr control
441
:param f: A file-like or string object whose contents should be copied.
442
:param encode: If true, encode the contents as utf-8
444
self.put_controlfiles([(path, f)], encode=encode)
446
def put_controlfiles(self, files, encode=True):
447
"""Write several entries as controlfiles.
449
:param files: A list of [(path, file)] pairs, where the path is the directory
450
underneath the bzr control directory
451
:param encode: If true, encode the contents as utf-8
455
for path, f in files:
457
if isinstance(f, basestring):
458
f = f.encode('utf-8', 'replace')
460
f = codecs.getwriter('utf-8')(f, errors='replace')
461
path = self._rel_controlfilename(path)
462
ctrl_files.append((path, f))
463
self._transport.put_multi(ctrl_files)
465
def _make_control(self):
466
from bzrlib.inventory import Inventory
467
from bzrlib.weavefile import write_weave_v5
468
from bzrlib.weave import Weave
470
# Create an empty inventory
472
# if we want per-tree root ids then this is the place to set
473
# them; they're not needed for now and so ommitted for
475
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
476
empty_inv = sio.getvalue()
478
bzrlib.weavefile.write_weave_v5(Weave(), sio)
479
empty_weave = sio.getvalue()
481
dirs = [[], 'revision-store', 'weaves']
483
"This is a Bazaar-NG control directory.\n"
484
"Do not change any files in this directory.\n"),
485
('branch-format', BZR_BRANCH_FORMAT_6),
486
('revision-history', ''),
489
('pending-merges', ''),
490
('inventory', empty_inv),
491
('inventory.weave', empty_weave),
492
('ancestry.weave', empty_weave)
494
cfn = self._rel_controlfilename
495
self._transport.mkdir_multi([cfn(d) for d in dirs])
496
self.put_controlfiles(files)
497
mutter('created control directory in ' + self._transport.base)
499
def _check_format(self, relax_version_check):
500
"""Check this branch format is supported.
502
The format level is stored, as an integer, in
503
self._branch_format for code that needs to check it later.
505
In the future, we might need different in-memory Branch
506
classes to support downlevel branches. But not yet.
509
fmt = self.controlfile('branch-format', 'r').read()
511
raise NotBranchError(path=self.base)
512
mutter("got branch format %r", fmt)
513
if fmt == BZR_BRANCH_FORMAT_6:
514
self._branch_format = 6
515
elif fmt == BZR_BRANCH_FORMAT_5:
516
self._branch_format = 5
517
elif fmt == BZR_BRANCH_FORMAT_4:
518
self._branch_format = 4
520
if (not relax_version_check
521
and self._branch_format not in (5, 6)):
522
raise errors.UnsupportedFormatError(
523
'sorry, branch format %r not supported' % fmt,
524
['use a different bzr version',
525
'or remove the .bzr directory'
526
' and "bzr init" again'])
528
def get_root_id(self):
529
"""Return the id of this branches root"""
530
inv = self.get_inventory(self.last_revision())
531
return inv.root.file_id
534
def set_root_id(self, file_id):
535
inv = self.working_tree().read_working_inventory()
536
orig_root_id = inv.root.file_id
537
del inv._byid[inv.root.file_id]
538
inv.root.file_id = file_id
539
inv._byid[inv.root.file_id] = inv.root
542
if entry.parent_id in (None, orig_root_id):
543
entry.parent_id = inv.root.file_id
544
self._write_inventory(inv)
547
def _write_inventory(self, inv):
548
"""Update the working inventory.
550
That is to say, the inventory describing changes underway, that
551
will be committed to the next revision.
553
from cStringIO import StringIO
555
bzrlib.xml5.serializer_v5.write_inventory(inv, sio)
557
# Transport handles atomicity
558
self.put_controlfile('inventory', sio)
560
mutter('wrote working inventory')
563
def add(self, files, ids=None):
564
"""Make files versioned.
566
Note that the command line normally calls smart_add instead,
567
which can automatically recurse.
569
This puts the files in the Added state, so that they will be
570
recorded by the next commit.
573
List of paths to add, relative to the base of the tree.
576
If set, use these instead of automatically generated ids.
577
Must be the same length as the list of files, but may
578
contain None for ids that are to be autogenerated.
580
TODO: Perhaps have an option to add the ids even if the files do
583
TODO: Perhaps yield the ids and paths as they're added.
585
# TODO: Re-adding a file that is removed in the working copy
586
# should probably put it back with the previous ID.
587
if isinstance(files, basestring):
588
assert(ids is None or isinstance(ids, basestring))
594
ids = [None] * len(files)
596
assert(len(ids) == len(files))
598
inv = self.working_tree().read_working_inventory()
599
for f,file_id in zip(files, ids):
600
if is_control_file(f):
601
raise BzrError("cannot add control file %s" % quotefn(f))
606
raise BzrError("cannot add top-level %r" % f)
608
fullpath = os.path.normpath(self.abspath(f))
611
kind = file_kind(fullpath)
613
# maybe something better?
614
raise BzrError('cannot add: not a regular file, symlink or directory: %s' % quotefn(f))
616
if not InventoryEntry.versionable_kind(kind):
617
raise BzrError('cannot add: not a versionable file ('
618
'i.e. regular file, symlink or directory): %s' % quotefn(f))
621
file_id = gen_file_id(f)
622
inv.add_path(f, kind=kind, file_id=file_id)
624
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
626
self._write_inventory(inv)
629
def print_file(self, file, revno):
630
"""Print `file` to stdout."""
631
tree = self.revision_tree(self.get_rev_id(revno))
632
# use inventory as it was in that revision
633
file_id = tree.inventory.path2id(file)
635
raise BzrError("%r is not present in revision %s" % (file, revno))
636
tree.print_file(file_id)
639
"""Return all unknown files.
641
These are files in the working directory that are not versioned or
642
control files or ignored.
644
>>> from bzrlib.workingtree import WorkingTree
645
>>> b = ScratchBranch(files=['foo', 'foo~'])
646
>>> map(str, b.unknowns())
649
>>> list(b.unknowns())
651
>>> WorkingTree(b.base, b).remove('foo')
652
>>> list(b.unknowns())
655
return self.working_tree().unknowns()
658
def append_revision(self, *revision_ids):
659
for revision_id in revision_ids:
660
mutter("add {%s} to revision-history" % revision_id)
661
rev_history = self.revision_history()
662
rev_history.extend(revision_ids)
663
self.set_revision_history(rev_history)
666
def set_revision_history(self, rev_history):
667
self.put_controlfile('revision-history', '\n'.join(rev_history))
669
def has_revision(self, revision_id):
670
"""True if this branch has a copy of the revision.
672
This does not necessarily imply the revision is merge
673
or on the mainline."""
674
return (revision_id is None
675
or self.revision_store.has_id(revision_id))
678
def get_revision_xml_file(self, revision_id):
679
"""Return XML file object for revision object."""
680
if not revision_id or not isinstance(revision_id, basestring):
681
raise InvalidRevisionId(revision_id=revision_id, branch=self)
683
return self.revision_store.get(revision_id)
684
except (IndexError, KeyError):
685
raise bzrlib.errors.NoSuchRevision(self, revision_id)
688
get_revision_xml = get_revision_xml_file
690
def get_revision_xml(self, revision_id):
691
return self.get_revision_xml_file(revision_id).read()
694
def get_revision(self, revision_id):
695
"""Return the Revision object for a named revision"""
696
xml_file = self.get_revision_xml_file(revision_id)
699
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
700
except SyntaxError, e:
701
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
705
assert r.revision_id == revision_id
708
def get_revision_delta(self, revno):
709
"""Return the delta for one revision.
711
The delta is relative to its mainline predecessor, or the
712
empty tree for revision 1.
714
assert isinstance(revno, int)
715
rh = self.revision_history()
716
if not (1 <= revno <= len(rh)):
717
raise InvalidRevisionNumber(revno)
719
# revno is 1-based; list is 0-based
721
new_tree = self.revision_tree(rh[revno-1])
723
old_tree = EmptyTree()
725
old_tree = self.revision_tree(rh[revno-2])
727
return compare_trees(old_tree, new_tree)
729
def get_revision_sha1(self, revision_id):
730
"""Hash the stored value of a revision, and return it."""
731
# In the future, revision entries will be signed. At that
732
# point, it is probably best *not* to include the signature
733
# in the revision hash. Because that lets you re-sign
734
# the revision, (add signatures/remove signatures) and still
735
# have all hash pointers stay consistent.
736
# But for now, just hash the contents.
737
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
739
def get_ancestry(self, revision_id):
740
"""Return a list of revision-ids integrated by a revision.
742
This currently returns a list, but the ordering is not guaranteed:
745
if revision_id is None:
747
w = self.get_inventory_weave()
748
return [None] + map(w.idx_to_name,
749
w.inclusions([w.lookup(revision_id)]))
751
def get_inventory_weave(self):
752
return self.control_weaves.get_weave('inventory',
753
self.get_transaction())
755
def get_inventory(self, revision_id):
756
"""Get Inventory object by hash."""
757
xml = self.get_inventory_xml(revision_id)
758
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
760
def get_inventory_xml(self, revision_id):
761
"""Get inventory XML as a file object."""
763
assert isinstance(revision_id, basestring), type(revision_id)
764
iw = self.get_inventory_weave()
765
return iw.get_text(iw.lookup(revision_id))
767
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
769
def get_inventory_sha1(self, revision_id):
770
"""Return the sha1 hash of the inventory entry
772
return self.get_revision(revision_id).inventory_sha1
774
def get_revision_inventory(self, revision_id):
775
"""Return inventory of a past revision."""
776
# TODO: Unify this with get_inventory()
777
# bzr 0.0.6 and later imposes the constraint that the inventory_id
778
# must be the same as its revision, so this is trivial.
779
if revision_id == None:
780
# This does not make sense: if there is no revision,
781
# then it is the current tree inventory surely ?!
782
# and thus get_root_id() is something that looks at the last
783
# commit on the branch, and the get_root_id is an inventory check.
784
raise NotImplementedError
785
# return Inventory(self.get_root_id())
787
return self.get_inventory(revision_id)
790
def revision_history(self):
791
"""Return sequence of revision hashes on to this branch."""
792
transaction = self.get_transaction()
793
history = transaction.map.find_revision_history()
794
if history is not None:
795
mutter("cache hit for revision-history in %s", self)
797
history = [l.rstrip('\r\n') for l in
798
self.controlfile('revision-history', 'r').readlines()]
799
transaction.map.add_revision_history(history)
800
# this call is disabled because revision_history is
801
# not really an object yet, and the transaction is for objects.
802
# transaction.register_clean(history, precious=True)
806
"""Return current revision number for this branch.
808
That is equivalent to the number of revisions committed to
811
return len(self.revision_history())
813
def last_revision(self):
814
"""Return last patch hash, or None if no history.
816
ph = self.revision_history()
822
def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
823
"""Return a list of new revisions that would perfectly fit.
825
If self and other have not diverged, return a list of the revisions
826
present in other, but missing from self.
828
>>> from bzrlib.commit import commit
829
>>> bzrlib.trace.silent = True
830
>>> br1 = ScratchBranch()
831
>>> br2 = ScratchBranch()
832
>>> br1.missing_revisions(br2)
834
>>> commit(br2, "lala!", rev_id="REVISION-ID-1")
835
>>> br1.missing_revisions(br2)
837
>>> br2.missing_revisions(br1)
839
>>> commit(br1, "lala!", rev_id="REVISION-ID-1")
840
>>> br1.missing_revisions(br2)
842
>>> commit(br2, "lala!", rev_id="REVISION-ID-2A")
843
>>> br1.missing_revisions(br2)
845
>>> commit(br1, "lala!", rev_id="REVISION-ID-2B")
846
>>> br1.missing_revisions(br2)
847
Traceback (most recent call last):
848
DivergedBranches: These branches have diverged.
850
self_history = self.revision_history()
851
self_len = len(self_history)
852
other_history = other.revision_history()
853
other_len = len(other_history)
854
common_index = min(self_len, other_len) -1
855
if common_index >= 0 and \
856
self_history[common_index] != other_history[common_index]:
857
raise DivergedBranches(self, other)
859
if stop_revision is None:
860
stop_revision = other_len
862
assert isinstance(stop_revision, int)
863
if stop_revision > other_len:
864
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
865
return other_history[self_len:stop_revision]
867
def update_revisions(self, other, stop_revision=None):
868
"""Pull in new perfect-fit revisions."""
869
from bzrlib.fetch import greedy_fetch
870
if stop_revision is None:
871
stop_revision = other.last_revision()
872
### Should this be checking is_ancestor instead of revision_history?
873
if (stop_revision is not None and
874
stop_revision in self.revision_history()):
876
greedy_fetch(to_branch=self, from_branch=other,
877
revision=stop_revision)
878
pullable_revs = self.pullable_revisions(other, stop_revision)
879
if len(pullable_revs) > 0:
880
self.append_revision(*pullable_revs)
882
def pullable_revisions(self, other, stop_revision):
883
other_revno = other.revision_id_to_revno(stop_revision)
885
return self.missing_revisions(other, other_revno)
886
except DivergedBranches, e:
888
pullable_revs = get_intervening_revisions(self.last_revision(),
890
assert self.last_revision() not in pullable_revs
892
except bzrlib.errors.NotAncestor:
893
if is_ancestor(self.last_revision(), stop_revision, self):
898
def commit(self, *args, **kw):
899
from bzrlib.commit import Commit
900
Commit().commit(self, *args, **kw)
902
def revision_id_to_revno(self, revision_id):
903
"""Given a revision id, return its revno"""
904
if revision_id is None:
906
history = self.revision_history()
908
return history.index(revision_id) + 1
910
raise bzrlib.errors.NoSuchRevision(self, revision_id)
912
def get_rev_id(self, revno, history=None):
913
"""Find the revision id of the specified revno."""
917
history = self.revision_history()
918
elif revno <= 0 or revno > len(history):
919
raise bzrlib.errors.NoSuchRevision(self, revno)
920
return history[revno - 1]
922
def revision_tree(self, revision_id):
923
"""Return Tree for a revision on this branch.
925
`revision_id` may be None for the null revision, in which case
926
an `EmptyTree` is returned."""
927
# TODO: refactor this to use an existing revision object
928
# so we don't need to read it in twice.
929
if revision_id == None or revision_id == NULL_REVISION:
932
inv = self.get_revision_inventory(revision_id)
933
return RevisionTree(self.weave_store, inv, revision_id)
935
def working_tree(self):
936
"""Return a `Tree` for the working copy."""
937
from bzrlib.workingtree import WorkingTree
938
# TODO: In the future, perhaps WorkingTree should utilize Transport
939
# RobertCollins 20051003 - I don't think it should - working trees are
940
# much more complex to keep consistent than our careful .bzr subset.
941
# instead, we should say that working trees are local only, and optimise
943
if self._transport.base.find('://') != -1:
944
raise NoWorkingTree(self.base)
945
return WorkingTree(self.base, branch=self)
948
def pull(self, source, overwrite=False):
952
self.update_revisions(source)
953
except DivergedBranches:
956
self.set_revision_history(source.revision_history())
960
def basis_tree(self):
961
"""Return `Tree` object for last revision.
963
If there are no revisions yet, return an `EmptyTree`.
965
return self.revision_tree(self.last_revision())
968
def rename_one(self, from_rel, to_rel):
971
This can change the directory or the filename or both.
973
tree = self.working_tree()
975
if not tree.has_filename(from_rel):
976
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
977
if tree.has_filename(to_rel):
978
raise BzrError("can't rename: new working file %r already exists" % to_rel)
980
file_id = inv.path2id(from_rel)
982
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
984
if inv.path2id(to_rel):
985
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
987
to_dir, to_tail = os.path.split(to_rel)
988
to_dir_id = inv.path2id(to_dir)
989
if to_dir_id == None and to_dir != '':
990
raise BzrError("can't determine destination directory id for %r" % to_dir)
992
mutter("rename_one:")
993
mutter(" file_id {%s}" % file_id)
994
mutter(" from_rel %r" % from_rel)
995
mutter(" to_rel %r" % to_rel)
996
mutter(" to_dir %r" % to_dir)
997
mutter(" to_dir_id {%s}" % to_dir_id)
999
inv.rename(file_id, to_dir_id, to_tail)
1001
from_abs = self.abspath(from_rel)
1002
to_abs = self.abspath(to_rel)
1004
rename(from_abs, to_abs)
1006
raise BzrError("failed to rename %r to %r: %s"
1007
% (from_abs, to_abs, e[1]),
1008
["rename rolled back"])
1010
self._write_inventory(inv)
1013
def move(self, from_paths, to_name):
1016
to_name must exist as a versioned directory.
1018
If to_name exists and is a directory, the files are moved into
1019
it, keeping their old names. If it is a directory,
1021
Note that to_name is only the last component of the new name;
1022
this doesn't change the directory.
1024
This returns a list of (from_path, to_path) pairs for each
1025
entry that is moved.
1028
## TODO: Option to move IDs only
1029
assert not isinstance(from_paths, basestring)
1030
tree = self.working_tree()
1031
inv = tree.inventory
1032
to_abs = self.abspath(to_name)
1033
if not isdir(to_abs):
1034
raise BzrError("destination %r is not a directory" % to_abs)
1035
if not tree.has_filename(to_name):
1036
raise BzrError("destination %r not in working directory" % to_abs)
1037
to_dir_id = inv.path2id(to_name)
1038
if to_dir_id == None and to_name != '':
1039
raise BzrError("destination %r is not a versioned directory" % to_name)
1040
to_dir_ie = inv[to_dir_id]
1041
if to_dir_ie.kind not in ('directory', 'root_directory'):
1042
raise BzrError("destination %r is not a directory" % to_abs)
1044
to_idpath = inv.get_idpath(to_dir_id)
1046
for f in from_paths:
1047
if not tree.has_filename(f):
1048
raise BzrError("%r does not exist in working tree" % f)
1049
f_id = inv.path2id(f)
1051
raise BzrError("%r is not versioned" % f)
1052
name_tail = splitpath(f)[-1]
1053
dest_path = appendpath(to_name, name_tail)
1054
if tree.has_filename(dest_path):
1055
raise BzrError("destination %r already exists" % dest_path)
1056
if f_id in to_idpath:
1057
raise BzrError("can't move %r to a subdirectory of itself" % f)
1059
# OK, so there's a race here, it's possible that someone will
1060
# create a file in this interval and then the rename might be
1061
# left half-done. But we should have caught most problems.
1063
for f in from_paths:
1064
name_tail = splitpath(f)[-1]
1065
dest_path = appendpath(to_name, name_tail)
1066
result.append((f, dest_path))
1067
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1069
rename(self.abspath(f), self.abspath(dest_path))
1071
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1072
["rename rolled back"])
1074
self._write_inventory(inv)
1078
def revert(self, filenames, old_tree=None, backups=True):
1079
"""Restore selected files to the versions from a previous tree.
1082
If true (default) backups are made of files before
1085
from bzrlib.atomicfile import AtomicFile
1086
from bzrlib.osutils import backup_file
1088
inv = self.working_tree().read_working_inventory()
1089
if old_tree is None:
1090
old_tree = self.basis_tree()
1091
old_inv = old_tree.inventory
1094
for fn in filenames:
1095
file_id = inv.path2id(fn)
1097
raise NotVersionedError(path=fn)
1098
if not old_inv.has_id(file_id):
1099
raise BzrError("file not present in old tree", fn, file_id)
1100
nids.append((fn, file_id))
1102
# TODO: Rename back if it was previously at a different location
1104
# TODO: If given a directory, restore the entire contents from
1105
# the previous version.
1107
# TODO: Make a backup to a temporary file.
1109
# TODO: If the file previously didn't exist, delete it?
1110
for fn, file_id in nids:
1113
f = AtomicFile(fn, 'wb')
1115
f.write(old_tree.get_file(file_id).read())
1121
def pending_merges(self):
1122
"""Return a list of pending merges.
1124
These are revisions that have been merged into the working
1125
directory but not yet committed.
1127
cfn = self._rel_controlfilename('pending-merges')
1128
if not self._transport.has(cfn):
1131
for l in self.controlfile('pending-merges', 'r').readlines():
1132
p.append(l.rstrip('\n'))
1136
def add_pending_merge(self, *revision_ids):
1137
# TODO: Perhaps should check at this point that the
1138
# history of the revision is actually present?
1139
p = self.pending_merges()
1141
for rev_id in revision_ids:
1147
self.set_pending_merges(p)
1150
def set_pending_merges(self, rev_list):
1151
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1153
def get_parent(self):
1154
"""Return the parent location of the branch.
1156
This is the default location for push/pull/missing. The usual
1157
pattern is that the user can override it by specifying a
1161
_locs = ['parent', 'pull', 'x-pull']
1164
return self.controlfile(l, 'r').read().strip('\n')
1166
if e.errno != errno.ENOENT:
1170
def get_push_location(self):
1171
"""Return the None or the location to push this branch to."""
1172
config = bzrlib.config.BranchConfig(self)
1173
push_loc = config.get_user_option('push_location')
1176
def set_push_location(self, location):
1177
"""Set a new push location for this branch."""
1178
config = bzrlib.config.LocationConfig(self.base)
1179
config.set_user_option('push_location', location)
1182
def set_parent(self, url):
1183
# TODO: Maybe delete old location files?
1184
from bzrlib.atomicfile import AtomicFile
1185
f = AtomicFile(self.controlfilename('parent'))
1192
def tree_config(self):
1193
return TreeConfig(self)
1195
def check_revno(self, revno):
1197
Check whether a revno corresponds to any revision.
1198
Zero (the NULL revision) is considered valid.
1201
self.check_real_revno(revno)
1203
def check_real_revno(self, revno):
1205
Check whether a revno corresponds to a real revision.
1206
Zero (the NULL revision) is considered invalid
1208
if revno < 1 or revno > self.revno():
1209
raise InvalidRevisionNumber(revno)
1211
def sign_revision(self, revision_id, gpg_strategy):
1212
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1213
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1216
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1217
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1222
class ScratchBranch(_Branch):
1223
"""Special test class: a branch that cleans up after itself.
1225
>>> b = ScratchBranch()
1229
>>> b._transport.__del__()
1234
def __init__(self, files=[], dirs=[], transport=None):
1235
"""Make a test branch.
1237
This creates a temporary directory and runs init-tree in it.
1239
If any files are listed, they are created in the working copy.
1241
if transport is None:
1242
transport = bzrlib.transport.local.ScratchTransport()
1243
super(ScratchBranch, self).__init__(transport, init=True)
1245
super(ScratchBranch, self).__init__(transport)
1248
self._transport.mkdir(d)
1251
self._transport.put(f, 'content of %s' % f)
1256
>>> orig = ScratchBranch(files=["file1", "file2"])
1257
>>> clone = orig.clone()
1258
>>> if os.name != 'nt':
1259
... os.path.samefile(orig.base, clone.base)
1261
... orig.base == clone.base
1264
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1267
from shutil import copytree
1268
from tempfile import mkdtemp
1271
copytree(self.base, base, symlinks=True)
1272
return ScratchBranch(
1273
transport=bzrlib.transport.local.ScratchTransport(base))
1276
######################################################################
1280
def is_control_file(filename):
1281
## FIXME: better check
1282
filename = os.path.normpath(filename)
1283
while filename != '':
1284
head, tail = os.path.split(filename)
1285
## mutter('check %r for control file' % ((head, tail), ))
1286
if tail == bzrlib.BZRDIR:
1288
if filename == head:
1295
def gen_file_id(name):
1296
"""Return new file id.
1298
This should probably generate proper UUIDs, but for the moment we
1299
cope with just randomness because running uuidgen every time is
1302
from binascii import hexlify
1303
from time import time
1305
# get last component
1306
idx = name.rfind('/')
1308
name = name[idx+1 : ]
1309
idx = name.rfind('\\')
1311
name = name[idx+1 : ]
1313
# make it not a hidden file
1314
name = name.lstrip('.')
1316
# remove any wierd characters; we don't escape them but rather
1317
# just pull them out
1318
name = re.sub(r'[^\w.]', '', name)
1320
s = hexlify(rand_bytes(8))
1321
return '-'.join((name, compact_date(time()), s))
1325
"""Return a new tree-root file id."""
1326
return gen_file_id('TREE_ROOT')