104
144
"""Branch holding a history of revisions.
107
Base directory/url of the branch.
147
Base directory of the branch.
153
If _lock_mode is true, a positive count of the number of times the
157
Lock object from bzrlib.lock.
109
# this is really an instance variable - FIXME move it there
113
_default_initializer = None
114
"""The default initializer for making new branches."""
116
def __init__(self, *ignored, **ignored_too):
117
raise NotImplementedError('The Branch class is abstract')
120
def open_downlevel(base):
121
"""Open a branch which may be of an old format."""
122
return Branch.open(base, _unsupported=True)
125
def open(base, _unsupported=False):
126
"""Open an existing branch, rooted at 'base' (url)
128
_unsupported is a private parameter to the Branch class.
130
t = get_transport(base)
131
mutter("trying to open %r with transport %r", base, t)
132
format = BzrBranchFormat.find_format(t)
133
if not _unsupported and not format.is_supported():
134
# see open_downlevel to open legacy branches.
135
raise errors.UnsupportedFormatError(
136
'sorry, branch format %s not supported' % format,
137
['use a different bzr version',
138
'or remove the .bzr directory'
139
' and "bzr init" again'])
140
return format.open(t)
143
def open_containing(url):
144
"""Open an existing branch which contains url.
146
This probes for a branch at url, and searches upwards from there.
148
Basically we keep looking up until we find the control directory or
149
run into the root. If there isn't one, raises NotBranchError.
150
If there is one, it is returned, along with the unused portion of url.
152
t = get_transport(url)
156
format = BzrBranchFormat.find_format(t)
157
return format.open(t), t.relpath(url)
158
# TODO FIXME, distinguish between formats that cannot be
159
# identified, and a lack of format.
160
except NotBranchError, e:
161
mutter('not a branch in: %r %s', t.base, e)
162
new_t = t.clone('..')
163
if new_t.base == t.base:
164
# reached the root, whatever that may be
165
raise NotBranchError(path=url)
169
def initialize(base):
170
"""Create a new branch, rooted at 'base' (url)
172
This will call the current default initializer with base
173
as the only parameter.
175
return Branch._default_initializer(safe_unicode(base))
178
def get_default_initializer():
179
"""Return the initializer being used for new branches."""
180
return Branch._default_initializer
183
def set_default_initializer(initializer):
184
"""Set the initializer to be used for new branches."""
185
Branch._default_initializer = staticmethod(initializer)
187
def setup_caching(self, cache_root):
188
"""Subclasses that care about caching should override this, and set
189
up cached stores located under cache_root.
191
self.cache_root = cache_root
194
cfg = self.tree_config()
195
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
197
def _set_nick(self, nick):
198
cfg = self.tree_config()
199
cfg.set_option(nick, "nickname")
200
assert cfg.get_option("nickname") == nick
202
nick = property(_get_nick, _set_nick)
204
def push_stores(self, branch_to):
205
"""Copy the content of this branches store to branch_to."""
206
raise NotImplementedError('push_stores is abstract')
208
def get_transaction(self):
209
"""Return the current active transaction.
211
If no transaction is active, this returns a passthrough object
212
for which all data is immediately flushed and no caching happens.
214
raise NotImplementedError('get_transaction is abstract')
164
# Map some sort of prefix into a namespace
165
# stuff like "revno:10", "revid:", etc.
166
# This should match a prefix with a function which accepts
167
REVISION_NAMESPACES = {}
169
def __init__(self, base, init=False, find_root=True):
170
"""Create new branch object at a particular location.
172
base -- Base directory for the branch.
174
init -- If True, create new control files in a previously
175
unversioned directory. If False, the branch must already
178
find_root -- If true and init is false, find the root of the
179
existing branch containing base.
181
In the test suite, creation of new trees is tested using the
182
`ScratchBranch` class.
184
from bzrlib.store import ImmutableStore
186
self.base = os.path.realpath(base)
189
self.base = find_branch_root(base)
191
self.base = os.path.realpath(base)
192
if not isdir(self.controlfilename('.')):
193
from errors import NotBranchError
194
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
195
['use "bzr init" to initialize a new working tree',
196
'current bzr can only operate from top-of-tree'])
199
self.text_store = ImmutableStore(self.controlfilename('text-store'))
200
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
201
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
205
return '%s(%r)' % (self.__class__.__name__, self.base)
212
if self._lock_mode or self._lock:
213
from warnings import warn
214
warn("branch %r was not explicitly unlocked" % self)
216
219
def lock_write(self):
217
raise NotImplementedError('lock_write is abstract')
221
if self._lock_mode != 'w':
222
from errors import LockError
223
raise LockError("can't upgrade to a write lock from %r" %
225
self._lock_count += 1
227
from bzrlib.lock import WriteLock
229
self._lock = WriteLock(self.controlfilename('branch-lock'))
230
self._lock_mode = 'w'
219
235
def lock_read(self):
220
raise NotImplementedError('lock_read is abstract')
237
assert self._lock_mode in ('r', 'w'), \
238
"invalid lock mode %r" % self._lock_mode
239
self._lock_count += 1
241
from bzrlib.lock import ReadLock
243
self._lock = ReadLock(self.controlfilename('branch-lock'))
244
self._lock_mode = 'r'
222
249
def unlock(self):
223
raise NotImplementedError('unlock is abstract')
250
if not self._lock_mode:
251
from errors import LockError
252
raise LockError('branch %r is not locked' % (self))
254
if self._lock_count > 1:
255
self._lock_count -= 1
259
self._lock_mode = self._lock_count = None
225
262
def abspath(self, name):
226
"""Return absolute filename for something in the branch
228
XXX: Robert Collins 20051017 what is this used for? why is it a branch
229
method and not a tree method.
231
raise NotImplementedError('abspath is abstract')
263
"""Return absolute filename for something in the branch"""
264
return os.path.join(self.base, name)
267
def relpath(self, path):
268
"""Return path relative to this branch of something inside it.
270
Raises an error if path is not in this branch."""
271
return _relpath(self.base, path)
233
274
def controlfilename(self, file_or_path):
234
275
"""Return location relative to branch."""
235
raise NotImplementedError('controlfilename is abstract')
276
if isinstance(file_or_path, basestring):
277
file_or_path = [file_or_path]
278
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
237
281
def controlfile(self, file_or_path, mode='r'):
238
282
"""Open a control file for this branch.
245
289
Controlfiles should almost never be opened in write mode but
246
290
rather should be atomically copied and replaced using atomicfile.
248
raise NotImplementedError('controlfile is abstract')
250
def put_controlfile(self, path, f, encode=True):
251
"""Write an entry as a controlfile.
253
:param path: The path to put the file, relative to the .bzr control
255
:param f: A file-like or string object whose contents should be copied.
256
:param encode: If true, encode the contents as utf-8
258
raise NotImplementedError('put_controlfile is abstract')
260
def put_controlfiles(self, files, encode=True):
261
"""Write several entries as controlfiles.
263
:param files: A list of [(path, file)] pairs, where the path is the directory
264
underneath the bzr control directory
265
:param encode: If true, encode the contents as utf-8
267
raise NotImplementedError('put_controlfiles is abstract')
293
fn = self.controlfilename(file_or_path)
295
if mode == 'rb' or mode == 'wb':
296
return file(fn, mode)
297
elif mode == 'r' or mode == 'w':
298
# open in binary mode anyhow so there's no newline translation;
299
# codecs uses line buffering by default; don't want that.
301
return codecs.open(fn, mode + 'b', 'utf-8',
304
raise BzrError("invalid controlfile mode %r" % mode)
308
def _make_control(self):
309
from bzrlib.inventory import Inventory
310
from bzrlib.xml import pack_xml
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)
317
for d in ('text-store', 'inventory-store', 'revision-store'):
318
os.mkdir(self.controlfilename(d))
319
for f in ('revision-history', 'merged-patches',
320
'pending-merged-patches', 'branch-name',
323
self.controlfile(f, 'w').write('')
324
mutter('created control directory in ' + self.base)
326
# if we want per-tree root ids then this is the place to set
327
# them; they're not needed for now and so ommitted for
329
pack_xml(Inventory(), self.controlfile('inventory','w'))
332
def _check_format(self):
333
"""Check this branch format is supported.
335
The current tool only supports the current unstable format.
337
In the future, we might need different in-memory Branch
338
classes to support downlevel branches. But not yet.
340
# This ignores newlines so that we can open branches created
341
# on Windows from Linux and so on. I think it might be better
342
# to always make all internal files in unix format.
343
fmt = self.controlfile('branch-format', 'r').read()
344
fmt.replace('\r\n', '')
345
if fmt != BZR_BRANCH_FORMAT:
346
raise BzrError('sorry, branch format %r not supported' % fmt,
347
['use a different bzr version',
348
'or remove the .bzr directory and "bzr init" again'])
269
350
def get_root_id(self):
270
351
"""Return the id of this branches root"""
271
raise NotImplementedError('get_root_id is abstract')
352
inv = self.read_working_inventory()
353
return inv.root.file_id
273
355
def set_root_id(self, file_id):
274
raise NotImplementedError('set_root_id is abstract')
276
def print_file(self, file, revision_id):
356
inv = self.read_working_inventory()
357
orig_root_id = inv.root.file_id
358
del inv._byid[inv.root.file_id]
359
inv.root.file_id = file_id
360
inv._byid[inv.root.file_id] = inv.root
363
if entry.parent_id in (None, orig_root_id):
364
entry.parent_id = inv.root.file_id
365
self._write_inventory(inv)
367
def read_working_inventory(self):
368
"""Read the working inventory."""
369
from bzrlib.inventory import Inventory
370
from bzrlib.xml import unpack_xml
371
from time import time
375
# ElementTree does its own conversion from UTF-8, so open in
377
inv = unpack_xml(Inventory,
378
self.controlfile('inventory', 'rb'))
379
mutter("loaded inventory of %d items in %f"
380
% (len(inv), time() - before))
386
def _write_inventory(self, inv):
387
"""Update the working inventory.
389
That is to say, the inventory describing changes underway, that
390
will be committed to the next revision.
392
from bzrlib.atomicfile import AtomicFile
393
from bzrlib.xml import pack_xml
397
f = AtomicFile(self.controlfilename('inventory'), 'wb')
406
mutter('wrote working inventory')
409
inventory = property(read_working_inventory, _write_inventory, None,
410
"""Inventory for the working copy.""")
413
def add(self, files, verbose=False, ids=None):
414
"""Make files versioned.
416
Note that the command line normally calls smart_add instead.
418
This puts the files in the Added state, so that they will be
419
recorded by the next commit.
422
List of paths to add, relative to the base of the tree.
425
If set, use these instead of automatically generated ids.
426
Must be the same length as the list of files, but may
427
contain None for ids that are to be autogenerated.
429
TODO: Perhaps have an option to add the ids even if the files do
432
TODO: Perhaps return the ids of the files? But then again it
433
is easy to retrieve them if they're needed.
435
TODO: Adding a directory should optionally recurse down and
436
add all non-ignored children. Perhaps do that in a
439
# TODO: Re-adding a file that is removed in the working copy
440
# should probably put it back with the previous ID.
441
if isinstance(files, basestring):
442
assert(ids is None or isinstance(ids, basestring))
448
ids = [None] * len(files)
450
assert(len(ids) == len(files))
454
inv = self.read_working_inventory()
455
for f,file_id in zip(files, ids):
456
if is_control_file(f):
457
raise BzrError("cannot add control file %s" % quotefn(f))
462
raise BzrError("cannot add top-level %r" % f)
464
fullpath = os.path.normpath(self.abspath(f))
467
kind = file_kind(fullpath)
469
# maybe something better?
470
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
472
if kind != 'file' and kind != 'directory':
473
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
476
file_id = gen_file_id(f)
477
inv.add_path(f, kind=kind, file_id=file_id)
480
print 'added', quotefn(f)
482
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
484
self._write_inventory(inv)
489
def print_file(self, file, revno):
277
490
"""Print `file` to stdout."""
278
raise NotImplementedError('print_file is abstract')
493
tree = self.revision_tree(self.lookup_revision(revno))
494
# use inventory as it was in that revision
495
file_id = tree.inventory.path2id(file)
497
raise BzrError("%r is not present in revision %s" % (file, revno))
498
tree.print_file(file_id)
503
def remove(self, files, verbose=False):
504
"""Mark nominated files for removal from the inventory.
506
This does not remove their text. This does not run on
508
TODO: Refuse to remove modified files unless --force is given?
510
TODO: Do something useful with directories.
512
TODO: Should this remove the text or not? Tough call; not
513
removing may be useful and the user can just use use rm, and
514
is the opposite of add. Removing it is consistent with most
515
other tools. Maybe an option.
517
## TODO: Normalize names
518
## TODO: Remove nested loops; better scalability
519
if isinstance(files, basestring):
525
tree = self.working_tree()
528
# do this before any modifications
532
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
533
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
535
# having remove it, it must be either ignored or unknown
536
if tree.is_ignored(f):
540
show_status(new_status, inv[fid].kind, quotefn(f))
543
self._write_inventory(inv)
548
# FIXME: this doesn't need to be a branch method
549
def set_inventory(self, new_inventory_list):
550
from bzrlib.inventory import Inventory, InventoryEntry
551
inv = Inventory(self.get_root_id())
552
for path, file_id, parent, kind in new_inventory_list:
553
name = os.path.basename(path)
556
inv.add(InventoryEntry(file_id, name, kind, parent))
557
self._write_inventory(inv)
561
"""Return all unknown files.
563
These are files in the working directory that are not versioned or
564
control files or ignored.
566
>>> b = ScratchBranch(files=['foo', 'foo~'])
567
>>> list(b.unknowns())
570
>>> list(b.unknowns())
573
>>> list(b.unknowns())
576
return self.working_tree().unknowns()
280
579
def append_revision(self, *revision_ids):
281
raise NotImplementedError('append_revision is abstract')
283
def set_revision_history(self, rev_history):
284
raise NotImplementedError('set_revision_history is abstract')
286
def has_revision(self, revision_id):
287
"""True if this branch has a copy of the revision.
289
This does not necessarily imply the revision is merge
290
or on the mainline."""
291
raise NotImplementedError('has_revision is abstract')
580
from bzrlib.atomicfile import AtomicFile
582
for revision_id in revision_ids:
583
mutter("add {%s} to revision-history" % revision_id)
585
rev_history = self.revision_history()
586
rev_history.extend(revision_ids)
588
f = AtomicFile(self.controlfilename('revision-history'))
590
for rev_id in rev_history:
293
597
def get_revision_xml(self, revision_id):
294
raise NotImplementedError('get_revision_xml is abstract')
598
"""Return XML file object for revision object."""
599
if not revision_id or not isinstance(revision_id, basestring):
600
raise InvalidRevisionId(revision_id)
605
return self.revision_store[revision_id]
607
raise bzrlib.errors.NoSuchRevision(self, revision_id)
296
612
def get_revision(self, revision_id):
297
613
"""Return the Revision object for a named revision"""
298
raise NotImplementedError('get_revision is abstract')
614
xml_file = self.get_revision_xml(revision_id)
617
r = unpack_xml(Revision, xml_file)
618
except SyntaxError, e:
619
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
623
assert r.revision_id == revision_id
300
627
def get_revision_delta(self, revno):
301
628
"""Return the delta for one revision.
406
809
if stop_revision is None:
407
810
stop_revision = other_len
409
assert isinstance(stop_revision, int)
410
if stop_revision > other_len:
411
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
811
elif stop_revision > other_len:
812
raise NoSuchRevision(self, stop_revision)
412
814
return other_history[self_len:stop_revision]
414
817
def update_revisions(self, other, stop_revision=None):
415
"""Pull in new perfect-fit revisions."""
416
raise NotImplementedError('update_revisions is abstract')
418
def pullable_revisions(self, other, stop_revision):
419
raise NotImplementedError('pullable_revisions is abstract')
421
def revision_id_to_revno(self, revision_id):
422
"""Given a revision id, return its revno"""
423
if revision_id is None:
425
history = self.revision_history()
427
return history.index(revision_id) + 1
429
raise bzrlib.errors.NoSuchRevision(self, revision_id)
431
def get_rev_id(self, revno, history=None):
432
"""Find the revision id of the specified revno."""
436
history = self.revision_history()
437
elif revno <= 0 or revno > len(history):
438
raise bzrlib.errors.NoSuchRevision(self, revno)
439
return history[revno - 1]
818
"""Pull in all new revisions from other branch.
820
>>> from bzrlib.commit import commit
821
>>> bzrlib.trace.silent = True
822
>>> br1 = ScratchBranch(files=['foo', 'bar'])
825
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
826
>>> br2 = ScratchBranch()
827
>>> br2.update_revisions(br1)
831
>>> br2.revision_history()
833
>>> br2.update_revisions(br1)
837
>>> br1.text_store.total_size() == br2.text_store.total_size()
840
from bzrlib.progress import ProgressBar
844
pb.update('comparing histories')
845
revision_ids = self.missing_revisions(other, stop_revision)
847
if hasattr(other.revision_store, "prefetch"):
848
other.revision_store.prefetch(revision_ids)
849
if hasattr(other.inventory_store, "prefetch"):
850
inventory_ids = [other.get_revision(r).inventory_id
851
for r in revision_ids]
852
other.inventory_store.prefetch(inventory_ids)
857
for rev_id in revision_ids:
859
pb.update('fetching revision', i, len(revision_ids))
860
rev = other.get_revision(rev_id)
861
revisions.append(rev)
862
inv = other.get_inventory(str(rev.inventory_id))
863
for key, entry in inv.iter_entries():
864
if entry.text_id is None:
866
if entry.text_id not in self.text_store:
867
needed_texts.add(entry.text_id)
871
count = self.text_store.copy_multi(other.text_store, needed_texts)
872
print "Added %d texts." % count
873
inventory_ids = [ f.inventory_id for f in revisions ]
874
count = self.inventory_store.copy_multi(other.inventory_store,
876
print "Added %d inventories." % count
877
revision_ids = [ f.revision_id for f in revisions]
878
count = self.revision_store.copy_multi(other.revision_store,
880
for revision_id in revision_ids:
881
self.append_revision(revision_id)
882
print "Added %d revisions." % count
885
def commit(self, *args, **kw):
886
from bzrlib.commit import commit
887
commit(self, *args, **kw)
890
def lookup_revision(self, revision):
891
"""Return the revision identifier for a given revision information."""
892
revno, info = self.get_revision_info(revision)
895
def get_revision_info(self, revision):
896
"""Return (revno, revision id) for revision identifier.
898
revision can be an integer, in which case it is assumed to be revno (though
899
this will translate negative values into positive ones)
900
revision can also be a string, in which case it is parsed for something like
901
'date:' or 'revid:' etc.
906
try:# Convert to int if possible
907
revision = int(revision)
910
revs = self.revision_history()
911
if isinstance(revision, int):
914
# Mabye we should do this first, but we don't need it if revision == 0
916
revno = len(revs) + revision + 1
919
elif isinstance(revision, basestring):
920
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
921
if revision.startswith(prefix):
922
revno = func(self, revs, revision)
925
raise BzrError('No namespace registered for string: %r' % revision)
927
if revno is None or revno <= 0 or revno > len(revs):
928
raise BzrError("no such revision %s" % revision)
929
return revno, revs[revno-1]
931
def _namespace_revno(self, revs, revision):
932
"""Lookup a revision by revision number"""
933
assert revision.startswith('revno:')
935
return int(revision[6:])
938
REVISION_NAMESPACES['revno:'] = _namespace_revno
940
def _namespace_revid(self, revs, revision):
941
assert revision.startswith('revid:')
943
return revs.index(revision[6:]) + 1
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
441
1048
def revision_tree(self, revision_id):
442
1049
"""Return Tree for a revision on this branch.
444
1051
`revision_id` may be None for the null revision, in which case
445
1052
an `EmptyTree` is returned."""
446
raise NotImplementedError('revision_tree is abstract')
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.text_store, inv)
448
1062
def working_tree(self):
449
"""Return a `Tree` for the working copy if this is a local branch."""
450
raise NotImplementedError('working_tree is abstract')
1063
"""Return a `Tree` for the working copy."""
1064
from workingtree import WorkingTree
1065
return WorkingTree(self.base, self.read_working_inventory())
452
def pull(self, source, overwrite=False):
453
raise NotImplementedError('pull is abstract')
455
1068
def basis_tree(self):
456
1069
"""Return `Tree` object for last revision.
458
1071
If there are no revisions yet, return an `EmptyTree`.
460
return self.revision_tree(self.last_revision())
1073
r = self.last_patch()
1077
return RevisionTree(self.text_store, self.get_revision_inventory(r))
462
1081
def rename_one(self, from_rel, to_rel):
463
1082
"""Rename one file.
465
1084
This can change the directory or the filename or both.
467
raise NotImplementedError('rename_one is abstract')
1088
tree = self.working_tree()
1089
inv = tree.inventory
1090
if not tree.has_filename(from_rel):
1091
raise BzrError("can't rename: old working file %r does not exist" % from_rel)
1092
if tree.has_filename(to_rel):
1093
raise BzrError("can't rename: new working file %r already exists" % to_rel)
1095
file_id = inv.path2id(from_rel)
1097
raise BzrError("can't rename: old name %r is not versioned" % from_rel)
1099
if inv.path2id(to_rel):
1100
raise BzrError("can't rename: new name %r is already versioned" % to_rel)
1102
to_dir, to_tail = os.path.split(to_rel)
1103
to_dir_id = inv.path2id(to_dir)
1104
if to_dir_id == None and to_dir != '':
1105
raise BzrError("can't determine destination directory id for %r" % to_dir)
1107
mutter("rename_one:")
1108
mutter(" file_id {%s}" % file_id)
1109
mutter(" from_rel %r" % from_rel)
1110
mutter(" to_rel %r" % to_rel)
1111
mutter(" to_dir %r" % to_dir)
1112
mutter(" to_dir_id {%s}" % to_dir_id)
1114
inv.rename(file_id, to_dir_id, to_tail)
1116
print "%s => %s" % (from_rel, to_rel)
1118
from_abs = self.abspath(from_rel)
1119
to_abs = self.abspath(to_rel)
1121
os.rename(from_abs, to_abs)
1123
raise BzrError("failed to rename %r to %r: %s"
1124
% (from_abs, to_abs, e[1]),
1125
["rename rolled back"])
1127
self._write_inventory(inv)
469
1132
def move(self, from_paths, to_name):
470
1133
"""Rename files.
477
1140
Note that to_name is only the last component of the new name;
478
1141
this doesn't change the directory.
480
This returns a list of (from_path, to_path) pairs for each
483
raise NotImplementedError('move is abstract')
485
def get_parent(self):
486
"""Return the parent location of the branch.
488
This is the default location for push/pull/missing. The usual
489
pattern is that the user can override it by specifying a
492
raise NotImplementedError('get_parent is abstract')
494
def get_push_location(self):
495
"""Return the None or the location to push this branch to."""
496
raise NotImplementedError('get_push_location is abstract')
498
def set_push_location(self, location):
499
"""Set a new push location for this branch."""
500
raise NotImplementedError('set_push_location is abstract')
502
def set_parent(self, url):
503
raise NotImplementedError('set_parent is abstract')
505
def check_revno(self, revno):
507
Check whether a revno corresponds to any revision.
508
Zero (the NULL revision) is considered valid.
511
self.check_real_revno(revno)
513
def check_real_revno(self, revno):
515
Check whether a revno corresponds to a real revision.
516
Zero (the NULL revision) is considered invalid
518
if revno < 1 or revno > self.revno():
519
raise InvalidRevisionNumber(revno)
521
def sign_revision(self, revision_id, gpg_strategy):
522
raise NotImplementedError('sign_revision is abstract')
524
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
525
raise NotImplementedError('store_revision_signature is abstract')
528
class BzrBranchFormat(object):
529
"""An encapsulation of the initialization and open routines for a format.
531
Formats provide three things:
532
* An initialization routine,
536
Formats are placed in an dict by their format string for reference
537
during branch opening. Its not required that these be instances, they
538
can be classes themselves with class methods - it simply depends on
539
whether state is needed for a given format or not.
541
Once a format is deprecated, just deprecate the initialize and open
542
methods on the format class. Do not deprecate the object, as the
543
object will be created every time regardless.
547
"""The known formats."""
550
def find_format(klass, transport):
551
"""Return the format registered for URL."""
553
format_string = transport.get(".bzr/branch-format").read()
554
return klass._formats[format_string]
556
raise NotBranchError(path=transport.base)
558
raise errors.UnknownFormatError(format_string)
560
def get_format_string(self):
561
"""Return the ASCII format string that identifies this format."""
562
raise NotImplementedError(self.get_format_string)
564
def _find_modes(self, t):
565
"""Determine the appropriate modes for files and directories.
567
FIXME: When this merges into, or from storage,
568
this code becomes delgatable to a LockableFiles instance.
570
For now its cribbed and returns (dir_mode, file_mode)
574
except errors.TransportNotPossible:
578
dir_mode = st.st_mode & 07777
579
# Remove the sticky and execute bits for files
580
file_mode = dir_mode & ~07111
581
if not BzrBranch._set_dir_mode:
583
if not BzrBranch._set_file_mode:
585
return dir_mode, file_mode
587
def initialize(self, url):
588
"""Create a branch of this format at url and return an open branch."""
589
t = get_transport(url)
590
from bzrlib.inventory import Inventory
591
from bzrlib.weavefile import write_weave_v5
592
from bzrlib.weave import Weave
594
# Create an empty inventory
596
# if we want per-tree root ids then this is the place to set
597
# them; they're not needed for now and so ommitted for
599
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
600
empty_inv = sio.getvalue()
602
bzrlib.weavefile.write_weave_v5(Weave(), sio)
603
empty_weave = sio.getvalue()
605
# Since we don't have a .bzr directory, inherit the
606
# mode from the root directory
607
dir_mode, file_mode = self._find_modes(t)
609
t.mkdir('.bzr', mode=dir_mode)
610
control = t.clone('.bzr')
611
dirs = ['revision-store', 'weaves']
613
StringIO("This is a Bazaar-NG control directory.\n"
614
"Do not change any files in this directory.\n")),
615
('branch-format', StringIO(self.get_format_string())),
616
('revision-history', StringIO('')),
617
('branch-name', StringIO('')),
618
('branch-lock', StringIO('')),
619
('pending-merges', StringIO('')),
620
('inventory', StringIO(empty_inv)),
621
('inventory.weave', StringIO(empty_weave)),
622
('ancestry.weave', StringIO(empty_weave))
624
control.mkdir_multi(dirs, mode=dir_mode)
625
control.put_multi(files, mode=file_mode)
626
mutter('created control directory in ' + t.base)
627
return BzrBranch(t, format=self)
629
def is_supported(self):
630
"""Is this format supported?
632
Supported formats can be initialized and opened.
633
Unsupported formats may not support initialization or committing or
634
some other features depending on the reason for not being supported.
638
def open(self, transport):
639
"""Fill out the data in branch for the branch at url."""
640
return BzrBranch(transport, format=self)
643
def register_format(klass, format):
644
klass._formats[format.get_format_string()] = format
647
def unregister_format(klass, format):
648
assert klass._formats[format.get_format_string()] is format
649
del klass._formats[format.get_format_string()]
652
class BzrBranchFormat4(BzrBranchFormat):
653
"""Bzr branch format 4.
657
- TextStores for texts, inventories,revisions.
659
This format is deprecated: it indexes texts using a text it which is
660
removed in format 5; write support for this format has been removed.
663
def get_format_string(self):
664
"""See BzrBranchFormat.get_format_string()."""
665
return BZR_BRANCH_FORMAT_4
667
def initialize(self, url):
668
"""Format 4 branches cannot be created."""
669
raise UninitializableFormat(self)
671
def is_supported(self):
672
"""Format 4 is not supported.
674
It is not supported because the model changed from 4 to 5 and the
675
conversion logic is expensive - so doing it on the fly was not
681
class BzrBranchFormat5(BzrBranchFormat):
682
"""Bzr branch format 5.
685
- weaves for file texts and inventory
687
- TextStores for revisions and signatures.
690
def get_format_string(self):
691
"""See BzrBranchFormat.get_format_string()."""
692
return BZR_BRANCH_FORMAT_5
695
class BzrBranchFormat6(BzrBranchFormat):
696
"""Bzr branch format 6.
699
- weaves for file texts and inventory
700
- hash subdirectory based stores.
701
- TextStores for revisions and signatures.
704
def get_format_string(self):
705
"""See BzrBranchFormat.get_format_string()."""
706
return BZR_BRANCH_FORMAT_6
709
BzrBranchFormat.register_format(BzrBranchFormat4())
710
BzrBranchFormat.register_format(BzrBranchFormat5())
711
BzrBranchFormat.register_format(BzrBranchFormat6())
714
class BzrBranch(Branch):
715
"""A branch stored in the actual filesystem.
717
Note that it's "local" in the context of the filesystem; it doesn't
718
really matter if it's on an nfs/smb/afs/coda/... share, as long as
719
it's writable, and can be accessed via the normal filesystem API.
725
If _lock_mode is true, a positive count of the number of times the
729
Lock object from bzrlib.lock.
731
# We actually expect this class to be somewhat short-lived; part of its
732
# purpose is to try to isolate what bits of the branch logic are tied to
733
# filesystem access, so that in a later step, we can extricate them to
734
# a separarte ("storage") class.
738
_inventory_weave = None
739
# If set to False (by a plugin, etc) BzrBranch will not set the
740
# mode on created files or directories
741
_set_file_mode = True
744
# Map some sort of prefix into a namespace
745
# stuff like "revno:10", "revid:", etc.
746
# This should match a prefix with a function which accepts
747
REVISION_NAMESPACES = {}
749
def push_stores(self, branch_to):
750
"""See Branch.push_stores."""
751
if (not isinstance(self._branch_format, BzrBranchFormat4) or
752
self._branch_format != branch_to._branch_format):
753
from bzrlib.fetch import greedy_fetch
754
mutter("Using fetch logic to push between %s(%s) and %s(%s)",
755
self, self._branch_format, branch_to, branch_to._branch_format)
756
greedy_fetch(to_branch=branch_to, from_branch=self,
757
revision=self.last_revision())
760
# format 4 to format 4 logic only.
761
store_pairs = ((self.text_store, branch_to.text_store),
762
(self.inventory_store, branch_to.inventory_store),
763
(self.revision_store, branch_to.revision_store))
765
for from_store, to_store in store_pairs:
766
copy_all(from_store, to_store)
767
except UnlistableStore:
768
raise UnlistableBranch(from_store)
770
def __init__(self, transport, init=deprecated_nonce,
771
relax_version_check=deprecated_nonce, format=None):
772
"""Create new branch object at a particular location.
774
transport -- A Transport object, defining how to access files.
776
init -- If True, create new control files in a previously
777
unversioned directory. If False, the branch must already
780
relax_version_check -- If true, the usual check for the branch
781
version is not applied. This is intended only for
782
upgrade/recovery type use; it's not guaranteed that
783
all operations will work on old format branches.
785
In the test suite, creation of new trees is tested using the
786
`ScratchBranch` class.
788
assert isinstance(transport, Transport), \
789
"%r is not a Transport" % transport
790
self._transport = transport
791
if deprecated_passed(init):
792
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
793
"deprecated as of bzr 0.8. Please use Branch.initialize().",
796
# this is slower than before deprecation, oh well never mind.
798
self._initialize(transport.base)
800
self._check_format(format)
801
if deprecated_passed(relax_version_check):
802
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
803
"relax_version_check parameter is deprecated as of bzr 0.8. "
804
"Please use Branch.open_downlevel, or a BzrBranchFormat's "
805
"open() method.", DeprecationWarning)
806
if (not relax_version_check
807
and not self._branch_format.is_supported()):
808
raise errors.UnsupportedFormatError(
809
'sorry, branch format %r not supported' % fmt,
810
['use a different bzr version',
811
'or remove the .bzr directory'
812
' and "bzr init" again'])
814
def get_store(name, compressed=True, prefixed=False):
815
relpath = self._rel_controlfilename(safe_unicode(name))
816
store = TextStore(self._transport.clone(relpath),
817
dir_mode=self._dir_mode,
818
file_mode=self._file_mode,
820
compressed=compressed)
823
def get_weave(name, prefixed=False):
824
relpath = self._rel_controlfilename(unicode(name))
825
ws = WeaveStore(self._transport.clone(relpath),
827
dir_mode=self._dir_mode,
828
file_mode=self._file_mode)
829
if self._transport.should_cache():
830
ws.enable_cache = True
833
if isinstance(self._branch_format, BzrBranchFormat4):
834
self.inventory_store = get_store('inventory-store')
835
self.text_store = get_store('text-store')
836
self.revision_store = get_store('revision-store')
837
elif isinstance(self._branch_format, BzrBranchFormat5):
838
self.control_weaves = get_weave(u'')
839
self.weave_store = get_weave(u'weaves')
840
self.revision_store = get_store(u'revision-store', compressed=False)
841
elif isinstance(self._branch_format, BzrBranchFormat6):
842
self.control_weaves = get_weave(u'')
843
self.weave_store = get_weave(u'weaves', prefixed=True)
844
self.revision_store = get_store(u'revision-store', compressed=False,
846
self.revision_store.register_suffix('sig')
847
self._transaction = None
850
def _initialize(base):
851
"""Create a bzr branch in the latest format."""
852
return BzrBranchFormat6().initialize(base)
855
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
860
if self._lock_mode or self._lock:
861
# XXX: This should show something every time, and be suitable for
862
# headless operation and embedding
863
warn("branch %r was not explicitly unlocked" % self)
866
# TODO: It might be best to do this somewhere else,
867
# but it is nice for a Branch object to automatically
868
# cache it's information.
869
# Alternatively, we could have the Transport objects cache requests
870
# See the earlier discussion about how major objects (like Branch)
871
# should never expect their __del__ function to run.
872
if hasattr(self, 'cache_root') and self.cache_root is not None:
874
shutil.rmtree(self.cache_root)
877
self.cache_root = None
881
return self._transport.base
884
base = property(_get_base, doc="The URL for the root of this branch.")
886
def _finish_transaction(self):
887
"""Exit the current transaction."""
888
if self._transaction is None:
889
raise errors.LockError('Branch %s is not in a transaction' %
891
transaction = self._transaction
892
self._transaction = None
895
def get_transaction(self):
896
"""See Branch.get_transaction."""
897
if self._transaction is None:
898
return transactions.PassThroughTransaction()
900
return self._transaction
902
def _set_transaction(self, new_transaction):
903
"""Set a new active transaction."""
904
if self._transaction is not None:
905
raise errors.LockError('Branch %s is in a transaction already.' %
907
self._transaction = new_transaction
909
def lock_write(self):
910
#mutter("lock write: %s (%s)", self, self._lock_count)
911
# TODO: Upgrade locking to support using a Transport,
912
# and potentially a remote locking protocol
914
if self._lock_mode != 'w':
915
raise LockError("can't upgrade to a write lock from %r" %
917
self._lock_count += 1
919
self._lock = self._transport.lock_write(
920
self._rel_controlfilename('branch-lock'))
921
self._lock_mode = 'w'
923
self._set_transaction(transactions.PassThroughTransaction())
926
#mutter("lock read: %s (%s)", self, self._lock_count)
928
assert self._lock_mode in ('r', 'w'), \
929
"invalid lock mode %r" % self._lock_mode
930
self._lock_count += 1
932
self._lock = self._transport.lock_read(
933
self._rel_controlfilename('branch-lock'))
934
self._lock_mode = 'r'
936
self._set_transaction(transactions.ReadOnlyTransaction())
937
# 5K may be excessive, but hey, its a knob.
938
self.get_transaction().set_cache_size(5000)
941
#mutter("unlock: %s (%s)", self, self._lock_count)
942
if not self._lock_mode:
943
raise LockError('branch %r is not locked' % (self))
945
if self._lock_count > 1:
946
self._lock_count -= 1
948
self._finish_transaction()
951
self._lock_mode = self._lock_count = None
953
def abspath(self, name):
954
"""See Branch.abspath."""
955
return self._transport.abspath(name)
957
def _rel_controlfilename(self, file_or_path):
958
if not isinstance(file_or_path, basestring):
959
file_or_path = u'/'.join(file_or_path)
960
if file_or_path == '':
962
return bzrlib.transport.urlescape(bzrlib.BZRDIR + u'/' + file_or_path)
964
def controlfilename(self, file_or_path):
965
"""See Branch.controlfilename."""
966
return self._transport.abspath(self._rel_controlfilename(file_or_path))
968
def controlfile(self, file_or_path, mode='r'):
969
"""See Branch.controlfile."""
972
relpath = self._rel_controlfilename(file_or_path)
973
#TODO: codecs.open() buffers linewise, so it was overloaded with
974
# a much larger buffer, do we need to do the same for getreader/getwriter?
976
return self._transport.get(relpath)
978
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
980
# XXX: Do we really want errors='replace'? Perhaps it should be
981
# an error, or at least reported, if there's incorrectly-encoded
982
# data inside a file.
983
# <https://launchpad.net/products/bzr/+bug/3823>
984
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
986
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
988
raise BzrError("invalid controlfile mode %r" % mode)
990
def put_controlfile(self, path, f, encode=True):
991
"""See Branch.put_controlfile."""
992
self.put_controlfiles([(path, f)], encode=encode)
994
def put_controlfiles(self, files, encode=True):
995
"""See Branch.put_controlfiles."""
998
for path, f in files:
1000
if isinstance(f, basestring):
1001
f = f.encode('utf-8', 'replace')
1003
f = codecs.getwriter('utf-8')(f, errors='replace')
1004
path = self._rel_controlfilename(path)
1005
ctrl_files.append((path, f))
1006
self._transport.put_multi(ctrl_files, mode=self._file_mode)
1008
def _find_modes(self, path=None):
1009
"""Determine the appropriate modes for files and directories."""
1012
path = self._rel_controlfilename('')
1013
st = self._transport.stat(path)
1014
except errors.TransportNotPossible:
1015
self._dir_mode = 0755
1016
self._file_mode = 0644
1018
self._dir_mode = st.st_mode & 07777
1019
# Remove the sticky and execute bits for files
1020
self._file_mode = self._dir_mode & ~07111
1021
if not self._set_dir_mode:
1022
self._dir_mode = None
1023
if not self._set_file_mode:
1024
self._file_mode = None
1026
def _check_format(self, format):
1027
"""Identify the branch format if needed.
1029
The format is stored as a reference to the format object in
1030
self._branch_format for code that needs to check it later.
1032
The format parameter is either None or the branch format class
1033
used to open this branch.
1036
format = BzrBranchFormat.find_format(self._transport)
1037
self._branch_format = format
1038
mutter("got branch format %s", self._branch_format)
1041
def get_root_id(self):
1042
"""See Branch.get_root_id."""
1043
inv = self.get_inventory(self.last_revision())
1044
return inv.root.file_id
1047
def print_file(self, file, revision_id):
1048
"""See Branch.print_file."""
1049
tree = self.revision_tree(revision_id)
1050
# use inventory as it was in that revision
1051
file_id = tree.inventory.path2id(file)
1054
revno = self.revision_id_to_revno(revision_id)
1055
except errors.NoSuchRevision:
1056
# TODO: This should not be BzrError,
1057
# but NoSuchFile doesn't fit either
1058
raise BzrError('%r is not present in revision %s'
1059
% (file, revision_id))
1061
raise BzrError('%r is not present in revision %s'
1063
tree.print_file(file_id)
1066
def append_revision(self, *revision_ids):
1067
"""See Branch.append_revision."""
1068
for revision_id in revision_ids:
1069
mutter("add {%s} to revision-history" % revision_id)
1070
rev_history = self.revision_history()
1071
rev_history.extend(revision_ids)
1072
self.set_revision_history(rev_history)
1075
def set_revision_history(self, rev_history):
1076
"""See Branch.set_revision_history."""
1077
old_revision = self.last_revision()
1078
new_revision = rev_history[-1]
1079
self.put_controlfile('revision-history', '\n'.join(rev_history))
1081
self.working_tree().set_last_revision(new_revision, old_revision)
1082
except NoWorkingTree:
1083
mutter('Unable to set_last_revision without a working tree.')
1085
def has_revision(self, revision_id):
1086
"""See Branch.has_revision."""
1087
return (revision_id is None
1088
or self.revision_store.has_id(revision_id))
1091
def _get_revision_xml_file(self, revision_id):
1092
if not revision_id or not isinstance(revision_id, basestring):
1093
raise InvalidRevisionId(revision_id=revision_id, branch=self)
1095
return self.revision_store.get(revision_id)
1096
except (IndexError, KeyError):
1097
raise bzrlib.errors.NoSuchRevision(self, revision_id)
1099
def get_revision_xml(self, revision_id):
1100
"""See Branch.get_revision_xml."""
1101
return self._get_revision_xml_file(revision_id).read()
1103
def get_revision(self, revision_id):
1104
"""See Branch.get_revision."""
1105
xml_file = self._get_revision_xml_file(revision_id)
1108
r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
1109
except SyntaxError, e:
1110
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
1114
assert r.revision_id == revision_id
1117
def get_revision_sha1(self, revision_id):
1118
"""See Branch.get_revision_sha1."""
1119
# In the future, revision entries will be signed. At that
1120
# point, it is probably best *not* to include the signature
1121
# in the revision hash. Because that lets you re-sign
1122
# the revision, (add signatures/remove signatures) and still
1123
# have all hash pointers stay consistent.
1124
# But for now, just hash the contents.
1125
return bzrlib.osutils.sha_file(self.get_revision_xml_file(revision_id))
1127
def get_ancestry(self, revision_id):
1128
"""See Branch.get_ancestry."""
1129
if revision_id is None:
1131
w = self._get_inventory_weave()
1132
return [None] + map(w.idx_to_name,
1133
w.inclusions([w.lookup(revision_id)]))
1135
def _get_inventory_weave(self):
1136
return self.control_weaves.get_weave('inventory',
1137
self.get_transaction())
1139
def get_inventory(self, revision_id):
1140
"""See Branch.get_inventory."""
1141
xml = self.get_inventory_xml(revision_id)
1142
return bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1144
def get_inventory_xml(self, revision_id):
1145
"""See Branch.get_inventory_xml."""
1147
assert isinstance(revision_id, basestring), type(revision_id)
1148
iw = self._get_inventory_weave()
1149
return iw.get_text(iw.lookup(revision_id))
1151
raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
1153
def get_inventory_sha1(self, revision_id):
1154
"""See Branch.get_inventory_sha1."""
1155
return self.get_revision(revision_id).inventory_sha1
1157
def get_revision_inventory(self, revision_id):
1158
"""See Branch.get_revision_inventory."""
1159
# TODO: Unify this with get_inventory()
1160
# bzr 0.0.6 and later imposes the constraint that the inventory_id
1161
# must be the same as its revision, so this is trivial.
1162
if revision_id == None:
1163
# This does not make sense: if there is no revision,
1164
# then it is the current tree inventory surely ?!
1165
# and thus get_root_id() is something that looks at the last
1166
# commit on the branch, and the get_root_id is an inventory check.
1167
raise NotImplementedError
1168
# return Inventory(self.get_root_id())
1170
return self.get_inventory(revision_id)
1173
def revision_history(self):
1174
"""See Branch.revision_history."""
1175
transaction = self.get_transaction()
1176
history = transaction.map.find_revision_history()
1177
if history is not None:
1178
mutter("cache hit for revision-history in %s", self)
1179
return list(history)
1180
history = [l.rstrip('\r\n') for l in
1181
self.controlfile('revision-history', 'r').readlines()]
1182
transaction.map.add_revision_history(history)
1183
# this call is disabled because revision_history is
1184
# not really an object yet, and the transaction is for objects.
1185
# transaction.register_clean(history, precious=True)
1186
return list(history)
1188
def update_revisions(self, other, stop_revision=None):
1189
"""See Branch.update_revisions."""
1190
from bzrlib.fetch import greedy_fetch
1191
if stop_revision is None:
1192
stop_revision = other.last_revision()
1193
### Should this be checking is_ancestor instead of revision_history?
1194
if (stop_revision is not None and
1195
stop_revision in self.revision_history()):
1197
greedy_fetch(to_branch=self, from_branch=other,
1198
revision=stop_revision)
1199
pullable_revs = self.pullable_revisions(other, stop_revision)
1200
if len(pullable_revs) > 0:
1201
self.append_revision(*pullable_revs)
1203
def pullable_revisions(self, other, stop_revision):
1204
"""See Branch.pullable_revisions."""
1205
other_revno = other.revision_id_to_revno(stop_revision)
1207
return self.missing_revisions(other, other_revno)
1208
except DivergedBranches, e:
1210
pullable_revs = get_intervening_revisions(self.last_revision(),
1211
stop_revision, self)
1212
assert self.last_revision() not in pullable_revs
1213
return pullable_revs
1214
except bzrlib.errors.NotAncestor:
1215
if is_ancestor(self.last_revision(), stop_revision, self):
1220
def revision_tree(self, revision_id):
1221
"""See Branch.revision_tree."""
1222
# TODO: refactor this to use an existing revision object
1223
# so we don't need to read it in twice.
1224
if revision_id == None or revision_id == NULL_REVISION:
1227
inv = self.get_revision_inventory(revision_id)
1228
return RevisionTree(self, inv, revision_id)
1230
def basis_tree(self):
1231
"""See Branch.basis_tree."""
1233
revision_id = self.revision_history()[-1]
1234
xml = self.working_tree().read_basis_inventory(revision_id)
1235
inv = bzrlib.xml5.serializer_v5.read_inventory_from_string(xml)
1236
return RevisionTree(self, inv, revision_id)
1237
except (IndexError, NoSuchFile, NoWorkingTree), e:
1238
return self.revision_tree(self.last_revision())
1240
def working_tree(self):
1241
"""See Branch.working_tree."""
1242
from bzrlib.workingtree import WorkingTree
1243
if self._transport.base.find('://') != -1:
1244
raise NoWorkingTree(self.base)
1245
return WorkingTree(self.base, branch=self)
1248
def pull(self, source, overwrite=False):
1249
"""See Branch.pull."""
1252
old_count = len(self.revision_history())
1254
self.update_revisions(source)
1255
except DivergedBranches:
1259
self.set_revision_history(source.revision_history())
1260
new_count = len(self.revision_history())
1261
return new_count - old_count
1265
def get_parent(self):
1266
"""See Branch.get_parent."""
1268
_locs = ['parent', 'pull', 'x-pull']
1271
return self.controlfile(l, 'r').read().strip('\n')
1276
def get_push_location(self):
1277
"""See Branch.get_push_location."""
1278
config = bzrlib.config.BranchConfig(self)
1279
push_loc = config.get_user_option('push_location')
1282
def set_push_location(self, location):
1283
"""See Branch.set_push_location."""
1284
config = bzrlib.config.LocationConfig(self.base)
1285
config.set_user_option('push_location', location)
1288
def set_parent(self, url):
1289
"""See Branch.set_parent."""
1290
# TODO: Maybe delete old location files?
1291
from bzrlib.atomicfile import AtomicFile
1292
f = AtomicFile(self.controlfilename('parent'))
1299
def tree_config(self):
1300
return TreeConfig(self)
1302
def sign_revision(self, revision_id, gpg_strategy):
1303
"""See Branch.sign_revision."""
1304
plaintext = Testament.from_revision(self, revision_id).as_short_text()
1305
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1308
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1309
"""See Branch.store_revision_signature."""
1310
self.revision_store.add(StringIO(gpg_strategy.sign(plaintext)),
1314
Branch.set_default_initializer(BzrBranch._initialize)
1317
class BranchTestProviderAdapter(object):
1318
"""A tool to generate a suite testing multiple branch formats at once.
1320
This is done by copying the test once for each transport and injecting
1321
the transport_server, transport_readonly_server, and branch_format
1322
classes into each copy. Each copy is also given a new id() to make it
1326
def __init__(self, transport_server, transport_readonly_server, formats):
1327
self._transport_server = transport_server
1328
self._transport_readonly_server = transport_readonly_server
1329
self._formats = formats
1331
def adapt(self, test):
1332
result = TestSuite()
1333
for format in self._formats:
1334
new_test = deepcopy(test)
1335
new_test.transport_server = self._transport_server
1336
new_test.transport_readonly_server = self._transport_readonly_server
1337
new_test.branch_format = format
1338
def make_new_test_id():
1339
new_id = "%s(%s)" % (new_test.id(), format.__class__.__name__)
1340
return lambda: new_id
1341
new_test.id = make_new_test_id()
1342
result.addTest(new_test)
1346
class ScratchBranch(BzrBranch):
1145
## TODO: Option to move IDs only
1146
assert not isinstance(from_paths, basestring)
1147
tree = self.working_tree()
1148
inv = tree.inventory
1149
to_abs = self.abspath(to_name)
1150
if not isdir(to_abs):
1151
raise BzrError("destination %r is not a directory" % to_abs)
1152
if not tree.has_filename(to_name):
1153
raise BzrError("destination %r not in working directory" % to_abs)
1154
to_dir_id = inv.path2id(to_name)
1155
if to_dir_id == None and to_name != '':
1156
raise BzrError("destination %r is not a versioned directory" % to_name)
1157
to_dir_ie = inv[to_dir_id]
1158
if to_dir_ie.kind not in ('directory', 'root_directory'):
1159
raise BzrError("destination %r is not a directory" % to_abs)
1161
to_idpath = inv.get_idpath(to_dir_id)
1163
for f in from_paths:
1164
if not tree.has_filename(f):
1165
raise BzrError("%r does not exist in working tree" % f)
1166
f_id = inv.path2id(f)
1168
raise BzrError("%r is not versioned" % f)
1169
name_tail = splitpath(f)[-1]
1170
dest_path = appendpath(to_name, name_tail)
1171
if tree.has_filename(dest_path):
1172
raise BzrError("destination %r already exists" % dest_path)
1173
if f_id in to_idpath:
1174
raise BzrError("can't move %r to a subdirectory of itself" % f)
1176
# OK, so there's a race here, it's possible that someone will
1177
# create a file in this interval and then the rename might be
1178
# left half-done. But we should have caught most problems.
1180
for f in from_paths:
1181
name_tail = splitpath(f)[-1]
1182
dest_path = appendpath(to_name, name_tail)
1183
print "%s => %s" % (f, dest_path)
1184
inv.rename(inv.path2id(f), to_dir_id, name_tail)
1186
os.rename(self.abspath(f), self.abspath(dest_path))
1188
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
1189
["rename rolled back"])
1191
self._write_inventory(inv)
1196
def revert(self, filenames, old_tree=None, backups=True):
1197
"""Restore selected files to the versions from a previous tree.
1200
If true (default) backups are made of files before
1203
from bzrlib.errors import NotVersionedError, BzrError
1204
from bzrlib.atomicfile import AtomicFile
1205
from bzrlib.osutils import backup_file
1207
inv = self.read_working_inventory()
1208
if old_tree is None:
1209
old_tree = self.basis_tree()
1210
old_inv = old_tree.inventory
1213
for fn in filenames:
1214
file_id = inv.path2id(fn)
1216
raise NotVersionedError("not a versioned file", fn)
1217
if not old_inv.has_id(file_id):
1218
raise BzrError("file not present in old tree", fn, file_id)
1219
nids.append((fn, file_id))
1221
# TODO: Rename back if it was previously at a different location
1223
# TODO: If given a directory, restore the entire contents from
1224
# the previous version.
1226
# TODO: Make a backup to a temporary file.
1228
# TODO: If the file previously didn't exist, delete it?
1229
for fn, file_id in nids:
1232
f = AtomicFile(fn, 'wb')
1234
f.write(old_tree.get_file(file_id).read())
1240
def pending_merges(self):
1241
"""Return a list of pending merges.
1243
These are revisions that have been merged into the working
1244
directory but not yet committed.
1246
cfn = self.controlfilename('pending-merges')
1247
if not os.path.exists(cfn):
1250
for l in self.controlfile('pending-merges', 'r').readlines():
1251
p.append(l.rstrip('\n'))
1255
def add_pending_merge(self, revision_id):
1256
from bzrlib.revision import validate_revision_id
1258
validate_revision_id(revision_id)
1260
p = self.pending_merges()
1261
if revision_id in p:
1263
p.append(revision_id)
1264
self.set_pending_merges(p)
1267
def set_pending_merges(self, rev_list):
1268
from bzrlib.atomicfile import AtomicFile
1271
f = AtomicFile(self.controlfilename('pending-merges'))
1283
class ScratchBranch(Branch):
1347
1284
"""Special test class: a branch that cleans up after itself.
1349
1286
>>> b = ScratchBranch()
1350
1287
>>> isdir(b.base)
1352
1289
>>> bd = b.base
1353
>>> b._transport.__del__()
1358
def __init__(self, files=[], dirs=[], transport=None):
1294
def __init__(self, files=[], dirs=[], base=None):
1359
1295
"""Make a test branch.
1361
1297
This creates a temporary directory and runs init-tree in it.
1363
1299
If any files are listed, they are created in the working copy.
1365
if transport is None:
1366
transport = bzrlib.transport.local.ScratchTransport()
1367
Branch.initialize(transport.base)
1368
super(ScratchBranch, self).__init__(transport)
1370
super(ScratchBranch, self).__init__(transport)
1301
from tempfile import mkdtemp
1306
Branch.__init__(self, base, init=init)
1373
self._transport.mkdir(d)
1308
os.mkdir(self.abspath(d))
1375
1310
for f in files:
1376
self._transport.put(f, 'content of %s' % f)
1311
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1379
1314
def clone(self):
1381
1316
>>> orig = ScratchBranch(files=["file1", "file2"])
1382
1317
>>> clone = orig.clone()
1383
>>> if os.name != 'nt':
1384
... os.path.samefile(orig.base, clone.base)
1386
... orig.base == clone.base
1318
>>> os.path.samefile(orig.base, clone.base)
1389
>>> os.path.isfile(pathjoin(clone.base, "file1"))
1320
>>> os.path.isfile(os.path.join(clone.base, "file1"))
1392
1323
from shutil import copytree
1393
from bzrlib.osutils import mkdtemp
1324
from tempfile import mkdtemp
1394
1325
base = mkdtemp()
1396
1327
copytree(self.base, base, symlinks=True)
1397
return ScratchBranch(
1398
transport=bzrlib.transport.local.ScratchTransport(base))
1328
return ScratchBranch(base=base)
1334
"""Destroy the test branch, removing the scratch directory."""
1335
from shutil import rmtree
1338
mutter("delete ScratchBranch %s" % self.base)
1341
# Work around for shutil.rmtree failing on Windows when
1342
# readonly files are encountered
1343
mutter("hit exception in destroying ScratchBranch: %s" % e)
1344
for root, dirs, files in os.walk(self.base, topdown=False):
1346
os.chmod(os.path.join(root, name), 0700)
1401
1352
######################################################################