157
98
"""Branch holding a history of revisions.
160
Base directory of the branch.
166
If _lock_mode is true, a positive count of the number of times the
170
Lock object from bzrlib.lock.
101
Base directory/url of the branch.
176
_inventory_weave = None
178
# Map some sort of prefix into a namespace
179
# stuff like "revno:10", "revid:", etc.
180
# This should match a prefix with a function which accepts
181
REVISION_NAMESPACES = {}
183
def __init__(self, base, init=False, find_root=True):
184
"""Create new branch object at a particular location.
186
base -- Base directory for the branch.
188
init -- If True, create new control files in a previously
189
unversioned directory. If False, the branch must already
192
find_root -- If true and init is false, find the root of the
193
existing branch containing base.
195
In the test suite, creation of new trees is tested using the
196
`ScratchBranch` class.
199
self.base = os.path.realpath(base)
202
self.base = find_branch_root(base)
204
self.base = os.path.realpath(base)
205
if not isdir(self.controlfilename('.')):
206
from errors import NotBranchError
207
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
208
['use "bzr init" to initialize a new working tree',
209
'current bzr can only operate from top-of-tree'])
212
self.weave_store = WeaveStore(self.controlfilename('weaves'))
213
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
217
return '%s(%r)' % (self.__class__.__name__, self.base)
224
if self._lock_mode or self._lock:
225
from warnings import warn
226
warn("branch %r was not explicitly unlocked" % self)
105
def __init__(self, *ignored, **ignored_too):
106
raise NotImplementedError('The Branch class is abstract')
109
def open_downlevel(base):
110
"""Open a branch which may be of an old format.
112
Only local branches are supported."""
113
return BzrBranch(get_transport(base), relax_version_check=True)
117
"""Open an existing branch, rooted at 'base' (url)"""
118
t = get_transport(base)
119
mutter("trying to open %r with transport %r", base, t)
123
def open_containing(url):
124
"""Open an existing branch which contains url.
126
This probes for a branch at url, and searches upwards from there.
128
Basically we keep looking up until we find the control directory or
129
run into the root. If there isn't one, raises NotBranchError.
130
If there is one, it is returned, along with the unused portion of url.
132
t = get_transport(url)
135
return BzrBranch(t), t.relpath(url)
136
except NotBranchError, e:
137
mutter('not a branch in: %r %s', t.base, e)
138
new_t = t.clone('..')
139
if new_t.base == t.base:
140
# reached the root, whatever that may be
141
raise NotBranchError(path=url)
145
def initialize(base):
146
"""Create a new branch, rooted at 'base' (url)"""
147
t = get_transport(base)
148
return BzrBranch(t, init=True)
150
def setup_caching(self, cache_root):
151
"""Subclasses that care about caching should override this, and set
152
up cached stores located under cache_root.
154
self.cache_root = cache_root
157
cfg = self.tree_config()
158
return cfg.get_option(u"nickname", default=self.base.split('/')[-1])
160
def _set_nick(self, nick):
161
cfg = self.tree_config()
162
cfg.set_option(nick, "nickname")
163
assert cfg.get_option("nickname") == nick
165
nick = property(_get_nick, _set_nick)
167
def push_stores(self, branch_to):
168
"""Copy the content of this branches store to branch_to."""
169
raise NotImplementedError('push_stores is abstract')
171
def get_transaction(self):
172
"""Return the current active transaction.
174
If no transaction is active, this returns a passthrough object
175
for which all data is immediately flushed and no caching happens.
177
raise NotImplementedError('get_transaction is abstract')
230
179
def lock_write(self):
232
if self._lock_mode != 'w':
233
from errors import LockError
234
raise LockError("can't upgrade to a write lock from %r" %
236
self._lock_count += 1
238
from bzrlib.lock import WriteLock
240
self._lock = WriteLock(self.controlfilename('branch-lock'))
241
self._lock_mode = 'w'
180
raise NotImplementedError('lock_write is abstract')
245
182
def lock_read(self):
247
assert self._lock_mode in ('r', 'w'), \
248
"invalid lock mode %r" % self._lock_mode
249
self._lock_count += 1
251
from bzrlib.lock import ReadLock
183
raise NotImplementedError('lock_read is abstract')
253
self._lock = ReadLock(self.controlfilename('branch-lock'))
254
self._lock_mode = 'r'
257
185
def unlock(self):
258
if not self._lock_mode:
259
from errors import LockError
260
raise LockError('branch %r is not locked' % (self))
262
if self._lock_count > 1:
263
self._lock_count -= 1
267
self._lock_mode = self._lock_count = None
186
raise NotImplementedError('unlock is abstract')
269
188
def abspath(self, name):
270
"""Return absolute filename for something in the branch"""
271
return os.path.join(self.base, name)
273
def relpath(self, path):
274
"""Return path relative to this branch of something inside it.
276
Raises an error if path is not in this branch."""
277
return _relpath(self.base, path)
189
"""Return absolute filename for something in the branch
191
XXX: Robert Collins 20051017 what is this used for? why is it a branch
192
method and not a tree method.
194
raise NotImplementedError('abspath is abstract')
279
196
def controlfilename(self, file_or_path):
280
197
"""Return location relative to branch."""
281
if isinstance(file_or_path, basestring):
282
file_or_path = [file_or_path]
283
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
198
raise NotImplementedError('controlfilename is abstract')
286
200
def controlfile(self, file_or_path, mode='r'):
287
201
"""Open a control file for this branch.
294
208
Controlfiles should almost never be opened in write mode but
295
209
rather should be atomically copied and replaced using atomicfile.
298
fn = self.controlfilename(file_or_path)
300
if mode == 'rb' or mode == 'wb':
301
return file(fn, mode)
302
elif mode == 'r' or mode == 'w':
303
# open in binary mode anyhow so there's no newline translation;
304
# codecs uses line buffering by default; don't want that.
306
return codecs.open(fn, mode + 'b', 'utf-8',
309
raise BzrError("invalid controlfile mode %r" % mode)
311
def _make_control(self):
312
os.mkdir(self.controlfilename([]))
313
self.controlfile('README', 'w').write(
314
"This is a Bazaar-NG control directory.\n"
315
"Do not change any files in this directory.\n")
316
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
317
for d in ('text-store', 'revision-store',
319
os.mkdir(self.controlfilename(d))
320
for f in ('revision-history', 'merged-patches',
321
'pending-merged-patches', 'branch-name',
324
self.controlfile(f, 'w').write('')
325
mutter('created control directory in ' + self.base)
327
# if we want per-tree root ids then this is the place to set
328
# them; they're not needed for now and so ommitted for
330
f = self.controlfile('inventory','w')
331
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
335
def _check_format(self):
336
"""Check this branch format is supported.
338
The format level is stored, as an integer, in
339
self._branch_format for code that needs to check it later.
341
In the future, we might need different in-memory Branch
342
classes to support downlevel branches. But not yet.
344
fmt = self.controlfile('branch-format', 'r').read()
345
if fmt == BZR_BRANCH_FORMAT_5:
346
self._branch_format = 5
348
raise BzrError('sorry, branch format "%s" not supported; '
349
'use a different bzr version, '
350
'or run "bzr upgrade", '
351
'or remove the .bzr directory and "bzr init" again'
352
% fmt.rstrip('\n\r'))
211
raise NotImplementedError('controlfile is abstract')
213
def put_controlfile(self, path, f, encode=True):
214
"""Write an entry as a controlfile.
216
:param path: The path to put the file, relative to the .bzr control
218
:param f: A file-like or string object whose contents should be copied.
219
:param encode: If true, encode the contents as utf-8
221
raise NotImplementedError('put_controlfile is abstract')
223
def put_controlfiles(self, files, encode=True):
224
"""Write several entries as controlfiles.
226
:param files: A list of [(path, file)] pairs, where the path is the directory
227
underneath the bzr control directory
228
:param encode: If true, encode the contents as utf-8
230
raise NotImplementedError('put_controlfiles is abstract')
354
232
def get_root_id(self):
355
233
"""Return the id of this branches root"""
356
inv = self.read_working_inventory()
357
return inv.root.file_id
234
raise NotImplementedError('get_root_id is abstract')
359
236
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
def read_working_inventory(self):
372
"""Read the working inventory."""
375
# ElementTree does its own conversion from UTF-8, so open in
377
f = self.controlfile('inventory', 'rb')
378
return bzrlib.xml5.serializer_v5.read_inventory(f)
383
def _write_inventory(self, inv):
384
"""Update the working inventory.
386
That is to say, the inventory describing changes underway, that
387
will be committed to the next revision.
389
from bzrlib.atomicfile import AtomicFile
393
f = AtomicFile(self.controlfilename('inventory'), 'wb')
395
bzrlib.xml5.serializer_v5.write_inventory(inv, f)
402
mutter('wrote working inventory')
405
inventory = property(read_working_inventory, _write_inventory, None,
406
"""Inventory for the working copy.""")
409
def add(self, files, ids=None):
410
"""Make files versioned.
412
Note that the command line normally calls smart_add instead,
413
which can automatically recurse.
415
This puts the files in the Added state, so that they will be
416
recorded by the next commit.
419
List of paths to add, relative to the base of the tree.
422
If set, use these instead of automatically generated ids.
423
Must be the same length as the list of files, but may
424
contain None for ids that are to be autogenerated.
426
TODO: Perhaps have an option to add the ids even if the files do
429
TODO: Perhaps yield the ids and paths as they're added.
431
# TODO: Re-adding a file that is removed in the working copy
432
# should probably put it back with the previous ID.
433
if isinstance(files, basestring):
434
assert(ids is None or isinstance(ids, basestring))
440
ids = [None] * len(files)
442
assert(len(ids) == len(files))
446
inv = self.read_working_inventory()
447
for f,file_id in zip(files, ids):
448
if is_control_file(f):
449
raise BzrError("cannot add control file %s" % quotefn(f))
454
raise BzrError("cannot add top-level %r" % f)
456
fullpath = os.path.normpath(self.abspath(f))
459
kind = file_kind(fullpath)
461
# maybe something better?
462
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
464
if kind != 'file' and kind != 'directory':
465
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
468
file_id = gen_file_id(f)
469
inv.add_path(f, kind=kind, file_id=file_id)
471
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
473
self._write_inventory(inv)
478
def print_file(self, file, revno):
237
raise NotImplementedError('set_root_id is abstract')
239
def print_file(self, file, revision_id):
479
240
"""Print `file` to stdout."""
482
tree = self.revision_tree(self.lookup_revision(revno))
483
# use inventory as it was in that revision
484
file_id = tree.inventory.path2id(file)
486
raise BzrError("%r is not present in revision %s" % (file, revno))
487
tree.print_file(file_id)
492
def remove(self, files, verbose=False):
493
"""Mark nominated files for removal from the inventory.
495
This does not remove their text. This does not run on
497
TODO: Refuse to remove modified files unless --force is given?
499
TODO: Do something useful with directories.
501
TODO: Should this remove the text or not? Tough call; not
502
removing may be useful and the user can just use use rm, and
503
is the opposite of add. Removing it is consistent with most
504
other tools. Maybe an option.
506
## TODO: Normalize names
507
## TODO: Remove nested loops; better scalability
508
if isinstance(files, basestring):
514
tree = self.working_tree()
517
# do this before any modifications
521
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
522
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
524
# having remove it, it must be either ignored or unknown
525
if tree.is_ignored(f):
529
show_status(new_status, inv[fid].kind, quotefn(f))
532
self._write_inventory(inv)
537
# FIXME: this doesn't need to be a branch method
538
def set_inventory(self, new_inventory_list):
539
from bzrlib.inventory import Inventory, InventoryEntry
540
inv = Inventory(self.get_root_id())
541
for path, file_id, parent, kind in new_inventory_list:
542
name = os.path.basename(path)
545
inv.add(InventoryEntry(file_id, name, kind, parent))
546
self._write_inventory(inv)
550
"""Return all unknown files.
552
These are files in the working directory that are not versioned or
553
control files or ignored.
555
>>> b = ScratchBranch(files=['foo', 'foo~'])
556
>>> list(b.unknowns())
559
>>> list(b.unknowns())
562
>>> list(b.unknowns())
565
return self.working_tree().unknowns()
241
raise NotImplementedError('print_file is abstract')
568
243
def append_revision(self, *revision_ids):
569
from bzrlib.atomicfile import AtomicFile
571
for revision_id in revision_ids:
572
mutter("add {%s} to revision-history" % revision_id)
574
rev_history = self.revision_history()
575
rev_history.extend(revision_ids)
577
f = AtomicFile(self.controlfilename('revision-history'))
579
for rev_id in rev_history:
586
def get_revision_xml_file(self, revision_id):
587
"""Return XML file object for revision object."""
588
if not revision_id or not isinstance(revision_id, basestring):
589
raise InvalidRevisionId(revision_id)
594
return self.revision_store[revision_id]
596
raise bzrlib.errors.NoSuchRevision(self, revision_id)
602
get_revision_xml = get_revision_xml_file
244
raise NotImplementedError('append_revision is abstract')
246
def set_revision_history(self, rev_history):
247
raise NotImplementedError('set_revision_history is abstract')
249
def has_revision(self, revision_id):
250
"""True if this branch has a copy of the revision.
252
This does not necessarily imply the revision is merge
253
or on the mainline."""
254
raise NotImplementedError('has_revision is abstract')
256
def get_revision_xml(self, revision_id):
257
raise NotImplementedError('get_revision_xml is abstract')
605
259
def get_revision(self, revision_id):
606
260
"""Return the Revision object for a named revision"""
607
xml_file = self.get_revision_xml_file(revision_id)
610
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
611
except SyntaxError, e:
612
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
616
assert r.revision_id == revision_id
261
raise NotImplementedError('get_revision is abstract')
620
263
def get_revision_delta(self, revno):
621
264
"""Return the delta for one revision.
879
401
raise bzrlib.errors.NoSuchRevision(self, revno)
880
402
return history[revno - 1]
882
def _get_revision_info(self, revision):
883
"""Return (revno, revision id) for revision specifier.
885
revision can be an integer, in which case it is assumed to be revno
886
(though this will translate negative values into positive ones)
887
revision can also be a string, in which case it is parsed for something
888
like 'date:' or 'revid:' etc.
890
A revid is always returned. If it is None, the specifier referred to
891
the null revision. If the revid does not occur in the revision
892
history, revno will be None.
898
try:# Convert to int if possible
899
revision = int(revision)
902
revs = self.revision_history()
903
if isinstance(revision, int):
905
revno = len(revs) + revision + 1
908
rev_id = self.get_rev_id(revno, revs)
909
elif isinstance(revision, basestring):
910
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
911
if revision.startswith(prefix):
912
result = func(self, revs, revision)
914
revno, rev_id = result
917
rev_id = self.get_rev_id(revno, revs)
920
raise BzrError('No namespace registered for string: %r' %
923
raise TypeError('Unhandled revision type %s' % revision)
927
raise bzrlib.errors.NoSuchRevision(self, revision)
930
def _namespace_revno(self, revs, revision):
931
"""Lookup a revision by revision number"""
932
assert revision.startswith('revno:')
934
return (int(revision[6:]),)
937
REVISION_NAMESPACES['revno:'] = _namespace_revno
939
def _namespace_revid(self, revs, revision):
940
assert revision.startswith('revid:')
941
rev_id = revision[len('revid:'):]
943
return revs.index(rev_id) + 1, rev_id
946
REVISION_NAMESPACES['revid:'] = _namespace_revid
948
def _namespace_last(self, revs, revision):
949
assert revision.startswith('last:')
951
offset = int(revision[5:])
956
raise BzrError('You must supply a positive value for --revision last:XXX')
957
return (len(revs) - offset + 1,)
958
REVISION_NAMESPACES['last:'] = _namespace_last
960
def _namespace_tag(self, revs, revision):
961
assert revision.startswith('tag:')
962
raise BzrError('tag: namespace registered, but not implemented.')
963
REVISION_NAMESPACES['tag:'] = _namespace_tag
965
def _namespace_date(self, revs, revision):
966
assert revision.startswith('date:')
968
# Spec for date revisions:
970
# value can be 'yesterday', 'today', 'tomorrow' or a YYYY-MM-DD string.
971
# it can also start with a '+/-/='. '+' says match the first
972
# entry after the given date. '-' is match the first entry before the date
973
# '=' is match the first entry after, but still on the given date.
975
# +2005-05-12 says find the first matching entry after May 12th, 2005 at 0:00
976
# -2005-05-12 says find the first matching entry before May 12th, 2005 at 0:00
977
# =2005-05-12 says find the first match after May 12th, 2005 at 0:00 but before
978
# May 13th, 2005 at 0:00
980
# So the proper way of saying 'give me all entries for today' is:
981
# -r {date:+today}:{date:-tomorrow}
982
# The default is '=' when not supplied
985
if val[:1] in ('+', '-', '='):
986
match_style = val[:1]
989
today = datetime.datetime.today().replace(hour=0,minute=0,second=0,microsecond=0)
990
if val.lower() == 'yesterday':
991
dt = today - datetime.timedelta(days=1)
992
elif val.lower() == 'today':
994
elif val.lower() == 'tomorrow':
995
dt = today + datetime.timedelta(days=1)
998
# This should be done outside the function to avoid recompiling it.
999
_date_re = re.compile(
1000
r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1002
r'(?P<time>(?P<hour>\d\d):(?P<minute>\d\d)(:(?P<second>\d\d))?)?'
1004
m = _date_re.match(val)
1005
if not m or (not m.group('date') and not m.group('time')):
1006
raise BzrError('Invalid revision date %r' % revision)
1009
year, month, day = int(m.group('year')), int(m.group('month')), int(m.group('day'))
1011
year, month, day = today.year, today.month, today.day
1013
hour = int(m.group('hour'))
1014
minute = int(m.group('minute'))
1015
if m.group('second'):
1016
second = int(m.group('second'))
1020
hour, minute, second = 0,0,0
1022
dt = datetime.datetime(year=year, month=month, day=day,
1023
hour=hour, minute=minute, second=second)
1027
if match_style == '-':
1029
elif match_style == '=':
1030
last = dt + datetime.timedelta(days=1)
1033
for i in range(len(revs)-1, -1, -1):
1034
r = self.get_revision(revs[i])
1035
# TODO: Handle timezone.
1036
dt = datetime.datetime.fromtimestamp(r.timestamp)
1037
if first >= dt and (last is None or dt >= last):
1040
for i in range(len(revs)):
1041
r = self.get_revision(revs[i])
1042
# TODO: Handle timezone.
1043
dt = datetime.datetime.fromtimestamp(r.timestamp)
1044
if first <= dt and (last is None or dt <= last):
1046
REVISION_NAMESPACES['date:'] = _namespace_date
1048
404
def revision_tree(self, revision_id):
1049
405
"""Return Tree for a revision on this branch.
1051
407
`revision_id` may be None for the null revision, in which case
1052
408
an `EmptyTree` is returned."""
1053
# TODO: refactor this to use an existing revision object
1054
# so we don't need to read it in twice.
1055
if revision_id == None:
1058
inv = self.get_revision_inventory(revision_id)
1059
return RevisionTree(self.weave_store, inv, revision_id)
409
raise NotImplementedError('revision_tree is abstract')
1062
411
def working_tree(self):
1063
"""Return a `Tree` for the working copy."""
1064
from workingtree import WorkingTree
1065
return WorkingTree(self.base, self.read_working_inventory())
412
"""Return a `Tree` for the working copy if this is a local branch."""
413
raise NotImplementedError('working_tree is abstract')
415
def pull(self, source, overwrite=False):
416
raise NotImplementedError('pull is abstract')
1068
418
def basis_tree(self):
1069
419
"""Return `Tree` object for last revision.
1071
421
If there are no revisions yet, return an `EmptyTree`.
1073
return self.revision_tree(self.last_patch())
423
return self.revision_tree(self.last_revision())
1076
425
def rename_one(self, from_rel, to_rel):
1077
426
"""Rename one file.
1079
428
This can change the directory or the filename or both.
1083
tree = self.working_tree()
1084
inv = tree.inventory
1085
if not tree.has_filename(from_rel):
1086
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1087
if tree.has_filename(to_rel):
1088
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1090
file_id = inv.path2id(from_rel)
1092
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1094
if inv.path2id(to_rel):
1095
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1097
to_dir, to_tail = os.path.split(to_rel)
1098
to_dir_id = inv.path2id(to_dir)
1099
if to_dir_id == None and to_dir != '':
1100
raise BzrError("can't determine destination directory id for %r" % to_dir)
1102
mutter("rename_one:")
1103
mutter(" file_id {%s}" % file_id)
1104
mutter(" from_rel %r" % from_rel)
1105
mutter(" to_rel %r" % to_rel)
1106
mutter(" to_dir %r" % to_dir)
1107
mutter(" to_dir_id {%s}" % to_dir_id)
1109
inv.rename(file_id, to_dir_id, to_tail)
1111
from_abs = self.abspath(from_rel)
1112
to_abs = self.abspath(to_rel)
1114
os.rename(from_abs, to_abs)
1116
raise BzrError("failed to rename %r to %r: %s"
1117
% (from_abs, to_abs, e[1]),
1118
["rename rolled back"])
1120
self._write_inventory(inv)
430
raise NotImplementedError('rename_one is abstract')
1125
432
def move(self, from_paths, to_name):
1126
433
"""Rename files.
1326
481
if revno < 1 or revno > self.revno():
1327
482
raise InvalidRevisionNumber(revno)
1332
class ScratchBranch(Branch):
484
def sign_revision(self, revision_id, gpg_strategy):
485
raise NotImplementedError('sign_revision is abstract')
487
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
488
raise NotImplementedError('store_revision_signature is abstract')
490
class BzrBranch(Branch):
491
"""A branch stored in the actual filesystem.
493
Note that it's "local" in the context of the filesystem; it doesn't
494
really matter if it's on an nfs/smb/afs/coda/... share, as long as
495
it's writable, and can be accessed via the normal filesystem API.
501
If _lock_mode is true, a positive count of the number of times the
505
Lock object from bzrlib.lock.
507
# We actually expect this class to be somewhat short-lived; part of its
508
# purpose is to try to isolate what bits of the branch logic are tied to
509
# filesystem access, so that in a later step, we can extricate them to
510
# a separarte ("storage") class.
514
_inventory_weave = None
515
# If set to False (by a plugin, etc) BzrBranch will not set the
516
# mode on created files or directories
517
_set_file_mode = True
520
# Map some sort of prefix into a namespace
521
# stuff like "revno:10", "revid:", etc.
522
# This should match a prefix with a function which accepts
523
REVISION_NAMESPACES = {}
525
def push_stores(self, branch_to):
526
"""See Branch.push_stores."""
527
if (self._branch_format != branch_to._branch_format
528
or self._branch_format != 4):
529
from bzrlib.fetch import greedy_fetch
530
mutter("falling back to fetch logic to push between %s(%s) and %s(%s)",
531
self, self._branch_format, branch_to, branch_to._branch_format)
532
greedy_fetch(to_branch=branch_to, from_branch=self,
533
revision=self.last_revision())
536
store_pairs = ((self.text_store, branch_to.text_store),
537
(self.inventory_store, branch_to.inventory_store),
538
(self.revision_store, branch_to.revision_store))
540
for from_store, to_store in store_pairs:
541
copy_all(from_store, to_store)
542
except UnlistableStore:
543
raise UnlistableBranch(from_store)
545
def __init__(self, transport, init=False,
546
relax_version_check=False):
547
"""Create new branch object at a particular location.
549
transport -- A Transport object, defining how to access files.
551
init -- If True, create new control files in a previously
552
unversioned directory. If False, the branch must already
555
relax_version_check -- If true, the usual check for the branch
556
version is not applied. This is intended only for
557
upgrade/recovery type use; it's not guaranteed that
558
all operations will work on old format branches.
560
In the test suite, creation of new trees is tested using the
561
`ScratchBranch` class.
563
assert isinstance(transport, Transport), \
564
"%r is not a Transport" % transport
565
self._transport = transport
568
self._check_format(relax_version_check)
571
def get_store(name, compressed=True, prefixed=False):
572
relpath = self._rel_controlfilename(unicode(name))
573
store = TextStore(self._transport.clone(relpath),
574
dir_mode=self._dir_mode,
575
file_mode=self._file_mode,
577
compressed=compressed)
580
def get_weave(name, prefixed=False):
581
relpath = self._rel_controlfilename(unicode(name))
582
ws = WeaveStore(self._transport.clone(relpath),
584
dir_mode=self._dir_mode,
585
file_mode=self._file_mode)
586
if self._transport.should_cache():
587
ws.enable_cache = True
590
if self._branch_format == 4:
591
self.inventory_store = get_store('inventory-store')
592
self.text_store = get_store('text-store')
593
self.revision_store = get_store('revision-store')
594
elif self._branch_format == 5:
595
self.control_weaves = get_weave(u'')
596
self.weave_store = get_weave(u'weaves')
597
self.revision_store = get_store(u'revision-store', compressed=False)
598
elif self._branch_format == 6:
599
self.control_weaves = get_weave(u'')
600
self.weave_store = get_weave(u'weaves', prefixed=True)
601
self.revision_store = get_store(u'revision-store', compressed=False,
603
self.revision_store.register_suffix('sig')
604
self._transaction = None
607
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
612
if self._lock_mode or self._lock:
613
# XXX: This should show something every time, and be suitable for
614
# headless operation and embedding
615
warn("branch %r was not explicitly unlocked" % self)
618
# TODO: It might be best to do this somewhere else,
619
# but it is nice for a Branch object to automatically
620
# cache it's information.
621
# Alternatively, we could have the Transport objects cache requests
622
# See the earlier discussion about how major objects (like Branch)
623
# should never expect their __del__ function to run.
624
if hasattr(self, 'cache_root') and self.cache_root is not None:
626
shutil.rmtree(self.cache_root)
629
self.cache_root = None
633
return self._transport.base
636
base = property(_get_base, doc="The URL for the root of this branch.")
638
def _finish_transaction(self):
639
"""Exit the current transaction."""
640
if self._transaction is None:
641
raise errors.LockError('Branch %s is not in a transaction' %
643
transaction = self._transaction
644
self._transaction = None
647
def get_transaction(self):
648
"""See Branch.get_transaction."""
649
if self._transaction is None:
650
return transactions.PassThroughTransaction()
652
return self._transaction
654
def _set_transaction(self, new_transaction):
655
"""Set a new active transaction."""
656
if self._transaction is not None:
657
raise errors.LockError('Branch %s is in a transaction already.' %
659
self._transaction = new_transaction
661
def lock_write(self):
662
#mutter("lock write: %s (%s)", self, self._lock_count)
663
# TODO: Upgrade locking to support using a Transport,
664
# and potentially a remote locking protocol
666
if self._lock_mode != 'w':
667
raise LockError("can't upgrade to a write lock from %r" %
669
self._lock_count += 1
671
self._lock = self._transport.lock_write(
672
self._rel_controlfilename('branch-lock'))
673
self._lock_mode = 'w'
675
self._set_transaction(transactions.PassThroughTransaction())
678
#mutter("lock read: %s (%s)", self, self._lock_count)
680
assert self._lock_mode in ('r', 'w'), \
681
"invalid lock mode %r" % self._lock_mode
682
self._lock_count += 1
684
self._lock = self._transport.lock_read(
685
self._rel_controlfilename('branch-lock'))
686
self._lock_mode = 'r'
688
self._set_transaction(transactions.ReadOnlyTransaction())
689
# 5K may be excessive, but hey, its a knob.
690
self.get_transaction().set_cache_size(5000)
693
#mutter("unlock: %s (%s)", self, self._lock_count)
694
if not self._lock_mode:
695
raise LockError('branch %r is not locked' % (self))
697
if self._lock_count > 1:
698
self._lock_count -= 1
700
self._finish_transaction()
703
self._lock_mode = self._lock_count = None
705
def abspath(self, name):
706
"""See Branch.abspath."""
707
return self._transport.abspath(name)
709
def _rel_controlfilename(self, file_or_path):
710
if not isinstance(file_or_path, basestring):
711
file_or_path = u'/'.join(file_or_path)
712
if file_or_path == '':
714
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
716
def controlfilename(self, file_or_path):
717
"""See Branch.controlfilename."""
718
return self._transport.abspath(self._rel_controlfilename(file_or_path))
720
def controlfile(self, file_or_path, mode='r'):
721
"""See Branch.controlfile."""
724
relpath = self._rel_controlfilename(file_or_path)
725
#TODO: codecs.open() buffers linewise, so it was overloaded with
726
# a much larger buffer, do we need to do the same for getreader/getwriter?
728
return self._transport.get(relpath)
730
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
732
# XXX: Do we really want errors='replace'? Perhaps it should be
733
# an error, or at least reported, if there's incorrectly-encoded
734
# data inside a file.
735
# <https://launchpad.net/products/bzr/+bug/3823>
736
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
738
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
740
raise BzrError("invalid controlfile mode %r" % mode)
742
def put_controlfile(self, path, f, encode=True):
743
"""See Branch.put_controlfile."""
744
self.put_controlfiles([(path, f)], encode=encode)
746
def put_controlfiles(self, files, encode=True):
747
"""See Branch.put_controlfiles."""
750
for path, f in files:
752
if isinstance(f, basestring):
753
f = f.encode('utf-8', 'replace')
755
f = codecs.getwriter('utf-8')(f, errors='replace')
756
path = self._rel_controlfilename(path)
757
ctrl_files.append((path, f))
758
self._transport.put_multi(ctrl_files, mode=self._file_mode)
760
def _find_modes(self, path=None):
761
"""Determine the appropriate modes for files and directories."""
764
path = self._rel_controlfilename('')
765
st = self._transport.stat(path)
766
except errors.TransportNotPossible:
767
self._dir_mode = 0755
768
self._file_mode = 0644
770
self._dir_mode = st.st_mode & 07777
771
# Remove the sticky and execute bits for files
772
self._file_mode = self._dir_mode & ~07111
773
if not self._set_dir_mode:
774
self._dir_mode = None
775
if not self._set_file_mode:
776
self._file_mode = None
778
def _make_control(self):
779
from bzrlib.inventory import Inventory
780
from bzrlib.weavefile import write_weave_v5
781
from bzrlib.weave import Weave
783
# Create an empty inventory
785
# if we want per-tree root ids then this is the place to set
786
# them; they're not needed for now and so ommitted for
788
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
789
empty_inv = sio.getvalue()
791
bzrlib.weavefile.write_weave_v5(Weave(), sio)
792
empty_weave = sio.getvalue()
794
cfn = self._rel_controlfilename
795
# Since we don't have a .bzr directory, inherit the
796
# mode from the root directory
797
self._find_modes(u'.')
799
dirs = ['', 'revision-store', 'weaves']
801
"This is a Bazaar-NG control directory.\n"
802
"Do not change any files in this directory.\n"),
803
('branch-format', BZR_BRANCH_FORMAT_6),
804
('revision-history', ''),
807
('pending-merges', ''),
808
('inventory', empty_inv),
809
('inventory.weave', empty_weave),
810
('ancestry.weave', empty_weave)
812
self._transport.mkdir_multi([cfn(d) for d in dirs], mode=self._dir_mode)
813
self.put_controlfiles(files)
814
mutter('created control directory in ' + self._transport.base)
816
def _check_format(self, relax_version_check):
817
"""Check this branch format is supported.
819
The format level is stored, as an integer, in
820
self._branch_format for code that needs to check it later.
822
In the future, we might need different in-memory Branch
823
classes to support downlevel branches. But not yet.
826
fmt = self.controlfile('branch-format', 'r').read()
828
raise NotBranchError(path=self.base)
829
mutter("got branch format %r", fmt)
830
if fmt == BZR_BRANCH_FORMAT_6:
831
self._branch_format = 6
832
elif fmt == BZR_BRANCH_FORMAT_5:
833
self._branch_format = 5
834
elif fmt == BZR_BRANCH_FORMAT_4:
835
self._branch_format = 4
837
if (not relax_version_check
838
and self._branch_format not in (5, 6)):
839
raise errors.UnsupportedFormatError(
840
'sorry, branch format %r not supported' % fmt,
841
['use a different bzr version',
842
'or remove the .bzr directory'
843
' and "bzr init" again'])
846
def get_root_id(self):
847
"""See Branch.get_root_id."""
848
inv = self.get_inventory(self.last_revision())
849
return inv.root.file_id
852
def print_file(self, file, revision_id):
853
"""See Branch.print_file."""
854
tree = self.revision_tree(revision_id)
855
# use inventory as it was in that revision
856
file_id = tree.inventory.path2id(file)
859
revno = self.revision_id_to_revno(revision_id)
860
except errors.NoSuchRevision:
861
# TODO: This should not be BzrError,
862
# but NoSuchFile doesn't fit either
863
raise BzrError('%r is not present in revision %s'
864
% (file, revision_id))
866
raise BzrError('%r is not present in revision %s'
868
tree.print_file(file_id)
871
def append_revision(self, *revision_ids):
872
"""See Branch.append_revision."""
873
for revision_id in revision_ids:
874
mutter("add {%s} to revision-history" % revision_id)
875
rev_history = self.revision_history()
876
rev_history.extend(revision_ids)
877
self.set_revision_history(rev_history)
880
def set_revision_history(self, rev_history):
881
"""See Branch.set_revision_history."""
882
old_revision = self.last_revision()
883
new_revision = rev_history[-1]
884
self.put_controlfile('revision-history', '\n'.join(rev_history))
886
self.working_tree().set_last_revision(new_revision, old_revision)
887
except NoWorkingTree:
888
mutter('Unable to set_last_revision without a working tree.')
890
def has_revision(self, revision_id):
891
"""See Branch.has_revision."""
892
return (revision_id is None
893
or self.revision_store.has_id(revision_id))
896
def _get_revision_xml_file(self, revision_id):
897
if not revision_id or not isinstance(revision_id, basestring):
898
raise InvalidRevisionId(revision_id=revision_id, branch=self)
900
return self.revision_store.get(revision_id)
901
except (IndexError, KeyError):
902
raise bzrlib.errors.NoSuchRevision(self, revision_id)
904
def get_revision_xml(self, revision_id):
905
"""See Branch.get_revision_xml."""
906
return self._get_revision_xml_file(revision_id).read()
908
def get_revision(self, revision_id):
909
"""See Branch.get_revision."""
910
xml_file = self._get_revision_xml_file(revision_id)
913
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
914
except SyntaxError, e:
915
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
919
assert r.revision_id == revision_id
922
def get_revision_sha1(self, revision_id):
923
"""See Branch.get_revision_sha1."""
924
# In the future, revision entries will be signed. At that
925
# point, it is probably best *not* to include the signature
926
# in the revision hash. Because that lets you re-sign
927
# the revision, (add signatures/remove signatures) and still
928
# have all hash pointers stay consistent.
929
# But for now, just hash the contents.
930
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
932
def get_ancestry(self, revision_id):
933
"""See Branch.get_ancestry."""
934
if revision_id is None:
936
w = self._get_inventory_weave()
937
return [None] + map(w.idx_to_name,
938
w.inclusions([w.lookup(revision_id)]))
940
def _get_inventory_weave(self):
941
return self.control_weaves.get_weave('inventory',
942
self.get_transaction())
944
def get_inventory(self, revision_id):
945
"""See Branch.get_inventory."""
946
xml = self.get_inventory_xml(revision_id)
947
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
949
def get_inventory_xml(self, revision_id):
950
"""See Branch.get_inventory_xml."""
952
assert isinstance(revision_id, basestring), type(revision_id)
953
iw = self._get_inventory_weave()
954
return iw.get_text(iw.lookup(revision_id))
956
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
958
def get_inventory_sha1(self, revision_id):
959
"""See Branch.get_inventory_sha1."""
960
return self.get_revision(revision_id).inventory_sha1
962
def get_revision_inventory(self, revision_id):
963
"""See Branch.get_revision_inventory."""
964
# TODO: Unify this with get_inventory()
965
# bzr 0.0.6 and later imposes the constraint that the inventory_id
966
# must be the same as its revision, so this is trivial.
967
if revision_id == None:
968
# This does not make sense: if there is no revision,
969
# then it is the current tree inventory surely ?!
970
# and thus get_root_id() is something that looks at the last
971
# commit on the branch, and the get_root_id is an inventory check.
972
raise NotImplementedError
973
# return Inventory(self.get_root_id())
975
return self.get_inventory(revision_id)
978
def revision_history(self):
979
"""See Branch.revision_history."""
980
transaction = self.get_transaction()
981
history = transaction.map.find_revision_history()
982
if history is not None:
983
mutter("cache hit for revision-history in %s", self)
985
history = [l.rstrip('\r\n') for l in
986
self.controlfile('revision-history', 'r').readlines()]
987
transaction.map.add_revision_history(history)
988
# this call is disabled because revision_history is
989
# not really an object yet, and the transaction is for objects.
990
# transaction.register_clean(history, precious=True)
993
def update_revisions(self, other, stop_revision=None):
994
"""See Branch.update_revisions."""
995
from bzrlib.fetch import greedy_fetch
996
if stop_revision is None:
997
stop_revision = other.last_revision()
998
### Should this be checking is_ancestor instead of revision_history?
999
if (stop_revision is not None and
1000
stop_revision in self.revision_history()):
1002
greedy_fetch(to_branch=self, from_branch=other,
1003
revision=stop_revision)
1004
pullable_revs = self.pullable_revisions(other, stop_revision)
1005
if len(pullable_revs) > 0:
1006
self.append_revision(*pullable_revs)
1008
def pullable_revisions(self, other, stop_revision):
1009
"""See Branch.pullable_revisions."""
1010
other_revno = other.revision_id_to_revno(stop_revision)
1012
return self.missing_revisions(other, other_revno)
1013
except DivergedBranches, e:
1015
pullable_revs = get_intervening_revisions(self.last_revision(),
1016
stop_revision, self)
1017
assert self.last_revision() not in pullable_revs
1018
return pullable_revs
1019
except bzrlib.errors.NotAncestor:
1020
if is_ancestor(self.last_revision(), stop_revision, self):
1025
def revision_tree(self, revision_id):
1026
"""See Branch.revision_tree."""
1027
# TODO: refactor this to use an existing revision object
1028
# so we don't need to read it in twice.
1029
if revision_id == None or revision_id == NULL_REVISION:
1032
inv = self.get_revision_inventory(revision_id)
1033
return RevisionTree(self, inv, revision_id)
1035
def basis_tree(self):
1036
"""See Branch.basis_tree."""
1038
revision_id = self.revision_history()[-1]
1039
xml = self.working_tree().read_basis_inventory(revision_id)
1040
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1041
return RevisionTree(self, inv, revision_id)
1042
except (IndexError, NoSuchFile, NoWorkingTree), e:
1043
return self.revision_tree(self.last_revision())
1045
def working_tree(self):
1046
"""See Branch.working_tree."""
1047
from bzrlib.workingtree import WorkingTree
1048
if self._transport.base.find('://') != -1:
1049
raise NoWorkingTree(self.base)
1050
return WorkingTree(self.base, branch=self)
1053
def pull(self, source, overwrite=False):
1054
"""See Branch.pull."""
1057
old_count = len(self.revision_history())
1059
self.update_revisions(source)
1060
except DivergedBranches:
1064
self.set_revision_history(source.revision_history())
1065
new_count = len(self.revision_history())
1066
return new_count - old_count
1070
def get_parent(self):
1071
"""See Branch.get_parent."""
1073
_locs = ['parent', 'pull', 'x-pull']
1076
return self.controlfile(l, 'r').read().strip('\n')
1081
def get_push_location(self):
1082
"""See Branch.get_push_location."""
1083
config = bzrlib.config.BranchConfig(self)
1084
push_loc = config.get_user_option('push_location')
1087
def set_push_location(self, location):
1088
"""See Branch.set_push_location."""
1089
config = bzrlib.config.LocationConfig(self.base)
1090
config.set_user_option('push_location', location)
1093
def set_parent(self, url):
1094
"""See Branch.set_parent."""
1095
# TODO: Maybe delete old location files?
1096
from bzrlib.atomicfile import AtomicFile
1097
f = AtomicFile(self.controlfilename('parent'))
1104
def tree_config(self):
1105
return TreeConfig(self)
1107
def sign_revision(self, revision_id, gpg_strategy):
1108
"""See Branch.sign_revision."""
1109
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1110
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1113
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1114
"""See Branch.store_revision_signature."""
1115
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1119
class ScratchBranch(BzrBranch):
1333
1120
"""Special test class: a branch that cleans up after itself.
1335
1122
>>> b = ScratchBranch()
1336
1123
>>> isdir(b.base)
1338
1125
>>> bd = b.base
1126
>>> b._transport.__del__()
1343
def __init__(self, files=[], dirs=[], base=None):
1131
def __init__(self, files=[], dirs=[], transport=None):
1344
1132
"""Make a test branch.
1346
1134
This creates a temporary directory and runs init-tree in it.
1348
1136
If any files are listed, they are created in the working copy.
1350
from tempfile import mkdtemp
1355
Branch.__init__(self, base, init=init)
1138
if transport is None:
1139
transport = bzrlib.transport.local.ScratchTransport()
1140
super(ScratchBranch, self).__init__(transport, init=True)
1142
super(ScratchBranch, self).__init__(transport)
1357
os.mkdir(self.abspath(d))
1145
self._transport.mkdir(d)
1359
1147
for f in files:
1360
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1148
self._transport.put(f, 'content of %s' % f)
1363
1151
def clone(self):
1365
1153
>>> orig = ScratchBranch(files=["file1", "file2"])
1366
1154
>>> clone = orig.clone()
1367
>>> os.path.samefile(orig.base, clone.base)
1155
>>> if os.name != 'nt':
1156
... os.path.samefile(orig.base, clone.base)
1158
... orig.base == clone.base
1369
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1161
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1372
1164
from shutil import copytree
1373
from tempfile import mkdtemp
1165
from bzrlib.osutils import mkdtemp
1374
1166
base = mkdtemp()
1376
1168
copytree(self.base, base, symlinks=True)
1377
return ScratchBranch(base=base)
1385
"""Destroy the test branch, removing the scratch directory."""
1386
from shutil import rmtree
1389
mutter("delete ScratchBranch %s" % self.base)
1392
# Work around for shutil.rmtree failing on Windows when
1393
# readonly files are encountered
1394
mutter("hit exception in destroying ScratchBranch: %s" % e)
1395
for root, dirs, files in os.walk(self.base, topdown=False):
1397
os.chmod(os.path.join(root, name), 0700)
1169
return ScratchBranch(
1170
transport=bzrlib.transport.local.ScratchTransport(base))
1403
1173
######################################################################