50
50
def find_branch(f, **args):
51
from bzrlib.transport import transport
52
from bzrlib.local_transport import LocalTransport
54
# FIXME: This is a hack around transport so that
55
# We can search the local directories for
57
if args.has_key('init') and args['init']:
58
# Don't search if we are init-ing
59
return Branch(t, **args)
60
if isinstance(t, LocalTransport):
61
root = find_branch_root(f)
64
return Branch(t, **args)
51
if f and (f.startswith('http://') or f.startswith('https://')):
53
return remotebranch.RemoteBranch(f, **args)
55
return Branch(f, **args)
58
def find_cached_branch(f, cache_root, **args):
59
from remotebranch import RemoteBranch
60
br = find_branch(f, **args)
61
def cacheify(br, store_name):
62
from meta_store import CachedStore
63
cache_path = os.path.join(cache_root, store_name)
65
new_store = CachedStore(getattr(br, store_name), cache_path)
66
setattr(br, store_name, new_store)
68
if isinstance(br, RemoteBranch):
69
cacheify(br, 'inventory_store')
70
cacheify(br, 'text_store')
71
cacheify(br, 'revision_store')
66
75
def _relpath(base, path):
67
76
"""Return path relative to base, or raise exception.
145
164
_lock_mode = None
146
165
_lock_count = None
150
168
# Map some sort of prefix into a namespace
151
169
# stuff like "revno:10", "revid:", etc.
152
170
# This should match a prefix with a function which accepts
153
171
REVISION_NAMESPACES = {}
155
def __init__(self, transport, init=False):
173
def __init__(self, base, init=False, find_root=True):
156
174
"""Create new branch object at a particular location.
158
transport -- A Transport object, defining how to access files.
159
(If a string, transport.transport() will be used to
160
create a Transport object)
176
base -- Base directory for the branch.
162
178
init -- If True, create new control files in a previously
163
179
unversioned directory. If False, the branch must already
182
find_root -- If true and init is false, find the root of the
183
existing branch containing base.
166
185
In the test suite, creation of new trees is tested using the
167
186
`ScratchBranch` class.
169
if isinstance(transport, basestring):
170
from transport import transport as get_transport
171
transport = get_transport(transport)
173
self._transport = transport
188
from bzrlib.store import ImmutableStore
190
self.base = os.path.realpath(base)
175
191
self._make_control()
193
self.base = find_branch_root(base)
195
self.base = os.path.realpath(base)
196
if not isdir(self.controlfilename('.')):
197
from errors import NotBranchError
198
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
199
['use "bzr init" to initialize a new working tree',
200
'current bzr can only operate from top-of-tree'])
176
201
self._check_format()
203
self.text_store = ImmutableStore(self.controlfilename('text-store'))
204
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
205
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
179
208
def __str__(self):
180
return '%s(%r)' % (self.__class__.__name__, self._transport.base)
209
return '%s(%r)' % (self.__class__.__name__, self.base)
183
212
__repr__ = __str__
186
215
def __del__(self):
187
216
if self._lock_mode or self._lock:
188
from bzrlib.warnings import warn
217
from warnings import warn
189
218
warn("branch %r was not explicitly unlocked" % self)
190
219
self._lock.unlock()
192
# TODO: It might be best to do this somewhere else,
193
# but it is nice for a Branch object to automatically
194
# cache it's information.
195
# Alternatively, we could have the Transport objects cache requests
196
# See the earlier discussion about how major objects (like Branch)
197
# should never expect their __del__ function to run.
198
if self.cache_root is not None:
199
#from warnings import warn
200
#warn("branch %r auto-cleanup of cache files" % self)
203
shutil.rmtree(self.cache_root)
206
self.cache_root = None
210
return self._transport.base
213
base = property(_get_base)
216
223
def lock_write(self):
217
# TODO: Upgrade locking to support using a Transport,
218
# and potentially a remote locking protocol
219
224
if self._lock_mode:
220
225
if self._lock_mode != 'w':
221
from bzrlib.errors import LockError
226
from errors import LockError
222
227
raise LockError("can't upgrade to a write lock from %r" %
224
229
self._lock_count += 1
226
self._lock = self._transport.lock_write(
227
self._rel_controlfilename('branch-lock'))
231
from bzrlib.lock import WriteLock
233
self._lock = WriteLock(self.controlfilename('branch-lock'))
228
234
self._lock_mode = 'w'
229
235
self._lock_count = 1
232
239
def lock_read(self):
233
240
if self._lock_mode:
234
241
assert self._lock_mode in ('r', 'w'), \
235
242
"invalid lock mode %r" % self._lock_mode
236
243
self._lock_count += 1
238
self._lock = self._transport.lock_read(
239
self._rel_controlfilename('branch-lock'))
245
from bzrlib.lock import ReadLock
247
self._lock = ReadLock(self.controlfilename('branch-lock'))
240
248
self._lock_mode = 'r'
241
249
self._lock_count = 1
243
253
def unlock(self):
244
254
if not self._lock_mode:
245
from bzrlib.errors import LockError
255
from errors import LockError
246
256
raise LockError('branch %r is not locked' % (self))
248
258
if self._lock_count > 1:
252
262
self._lock = None
253
263
self._lock_mode = self._lock_count = None
255
266
def abspath(self, name):
256
267
"""Return absolute filename for something in the branch"""
257
return self._transport.abspath(name)
268
return os.path.join(self.base, name)
259
271
def relpath(self, path):
260
272
"""Return path relative to this branch of something inside it.
262
274
Raises an error if path is not in this branch."""
263
return self._transport.relpath(path)
266
def _rel_controlfilename(self, file_or_path):
275
return _relpath(self.base, path)
278
def controlfilename(self, file_or_path):
279
"""Return location relative to branch."""
267
280
if isinstance(file_or_path, basestring):
268
281
file_or_path = [file_or_path]
269
return [bzrlib.BZRDIR] + file_or_path
271
def controlfilename(self, file_or_path):
272
"""Return location relative to branch."""
273
return self._transport.abspath(self._rel_controlfilename(file_or_path))
282
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
276
285
def controlfile(self, file_or_path, mode='r'):
284
293
Controlfiles should almost never be opened in write mode but
285
294
rather should be atomically copied and replaced using atomicfile.
289
relpath = self._rel_controlfilename(file_or_path)
290
#TODO: codecs.open() buffers linewise, so it was overloaded with
291
# a much larger buffer, do we need to do the same for getreader/getwriter?
293
return self._transport.get(relpath)
295
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
297
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
299
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
297
fn = self.controlfilename(file_or_path)
299
if mode == 'rb' or mode == 'wb':
300
return file(fn, mode)
301
elif mode == 'r' or mode == 'w':
302
# open in binary mode anyhow so there's no newline translation;
303
# codecs uses line buffering by default; don't want that.
305
return codecs.open(fn, mode + 'b', 'utf-8',
301
308
raise BzrError("invalid controlfile mode %r" % mode)
303
def put_controlfile(self, path, f, encode=True):
304
"""Write an entry as a controlfile.
306
:param path: The path to put the file, relative to the .bzr control
308
:param f: A file-like or string object whose contents should be copied.
309
:param encode: If true, encode the contents as utf-8
311
self.put_controlfiles([(path, f)], encode=encode)
313
def put_controlfiles(self, files, encode=True):
314
"""Write several entries as controlfiles.
316
:param files: A list of [(path, file)] pairs, where the path is the directory
317
underneath the bzr control directory
318
:param encode: If true, encode the contents as utf-8
322
for path, f in files:
324
if isinstance(f, basestring):
325
f = f.encode('utf-8', 'replace')
327
f = codecs.getwriter('utf-8')(f, errors='replace')
328
path = self._rel_controlfilename(path)
329
ctrl_files.append((path, f))
330
self._transport.put_multi(ctrl_files)
332
312
def _make_control(self):
333
313
from bzrlib.inventory import Inventory
334
from cStringIO import StringIO
314
from bzrlib.xml import pack_xml
336
# Create an empty inventory
316
os.mkdir(self.controlfilename([]))
317
self.controlfile('README', 'w').write(
318
"This is a Bazaar-NG control directory.\n"
319
"Do not change any files in this directory.\n")
320
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
321
for d in ('text-store', 'inventory-store', 'revision-store'):
322
os.mkdir(self.controlfilename(d))
323
for f in ('revision-history', 'merged-patches',
324
'pending-merged-patches', 'branch-name',
327
self.controlfile(f, 'w').write('')
328
mutter('created control directory in ' + self.base)
338
330
# if we want per-tree root ids then this is the place to set
339
331
# them; they're not needed for now and so ommitted for
341
bzrlib.xml.serializer_v4.write_inventory(Inventory(), sio)
333
pack_xml(Inventory(), self.controlfile('inventory','w'))
343
dirs = [[], 'text-store', 'inventory-store', 'revision-store']
345
"This is a Bazaar-NG control directory.\n"
346
"Do not change any files in this directory.\n"),
347
('branch-format', BZR_BRANCH_FORMAT),
348
('revision-history', ''),
349
('merged-patches', ''),
350
('pending-merged-patches', ''),
353
('pending-merges', ''),
354
('inventory', sio.getvalue())
356
self._transport.mkdir_multi([self._rel_controlfilename(d) for d in dirs])
357
self.put_controlfiles(files)
358
mutter('created control directory in ' + self._transport.base)
360
336
def _check_format(self):
361
337
"""Check this branch format is supported.
369
345
# on Windows from Linux and so on. I think it might be better
370
346
# to always make all internal files in unix format.
371
347
fmt = self.controlfile('branch-format', 'r').read()
372
fmt = fmt.replace('\r\n', '\n')
348
fmt.replace('\r\n', '')
373
349
if fmt != BZR_BRANCH_FORMAT:
374
350
raise BzrError('sorry, branch format %r not supported' % fmt,
375
351
['use a different bzr version',
376
352
'or remove the .bzr directory and "bzr init" again'])
378
# We know that the format is the currently supported one.
379
# So create the rest of the entries.
380
from bzrlib.store.compressed_text import CompressedTextStore
382
if self._transport.should_cache():
384
self.cache_root = tempfile.mkdtemp(prefix='bzr-cache')
385
mutter('Branch %r using caching in %r' % (self, self.cache_root))
387
self.cache_root = None
390
relpath = self._rel_controlfilename(name)
391
store = CompressedTextStore(self._transport.clone(relpath))
392
if self._transport.should_cache():
393
from meta_store import CachedStore
394
cache_path = os.path.join(self.cache_root, name)
396
store = CachedStore(store, cache_path)
399
self.text_store = get_store('text-store')
400
self.revision_store = get_store('revision-store')
401
self.inventory_store = get_store('inventory-store')
403
354
def get_root_id(self):
404
355
"""Return the id of this branches root"""
405
356
inv = self.read_working_inventory()
616
576
def append_revision(self, *revision_ids):
577
from bzrlib.atomicfile import AtomicFile
617
579
for revision_id in revision_ids:
618
580
mutter("add {%s} to revision-history" % revision_id)
620
582
rev_history = self.revision_history()
621
583
rev_history.extend(revision_ids)
585
f = AtomicFile(self.controlfilename('revision-history'))
625
self.put_controlfile('revision-history', '\n'.join(rev_history))
587
for rev_id in rev_history:
630
def get_revision_xml_file(self, revision_id):
594
def get_revision_xml(self, revision_id):
631
595
"""Return XML file object for revision object."""
632
596
if not revision_id or not isinstance(revision_id, basestring):
633
597
raise InvalidRevisionId(revision_id)
683
643
return compare_trees(old_tree, new_tree)
686
def get_revisions(self, revision_ids, pb=None):
687
"""Return the Revision object for a set of named revisions"""
688
from bzrlib.revision import Revision
689
from bzrlib.xml import unpack_xml
691
# TODO: We need to decide what to do here
692
# we cannot use a generator with a try/finally, because
693
# you cannot guarantee that the caller will iterate through
695
# in the past, get_inventory wasn't even wrapped in a
696
# try/finally locking block.
697
# We could either lock without the try/finally, or just
698
# not lock at all. We are reading entries that should
700
# I prefer locking with no finally, so that if someone
701
# asks for a list of revisions, but doesn't consume them,
702
# that is their problem, and they will suffer the consequences
704
for xml_file in self.revision_store.get(revision_ids, pb=pb):
706
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
707
except SyntaxError, e:
708
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
714
647
def get_revision_sha1(self, revision_id):
715
648
"""Hash the stored value of a revision, and return it."""
716
649
# In the future, revision entries will be signed. At that
728
661
TODO: Perhaps for this and similar methods, take a revision
729
662
parameter which can be either an integer revno or a
732
f = self.get_inventory_xml_file(inventory_id)
733
return bzrlib.xml.serializer_v4.read_inventory(f)
664
from bzrlib.inventory import Inventory
665
from bzrlib.xml import unpack_xml
667
return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
736
670
def get_inventory_xml(self, inventory_id):
737
671
"""Get inventory XML as a file object."""
738
# Shouldn't this have a read-lock around it?
739
# As well as some sort of trap for missing ids?
740
672
return self.inventory_store[inventory_id]
742
get_inventory_xml_file = get_inventory_xml
744
def get_inventories(self, inventory_ids, pb=None, ignore_missing=False):
745
"""Get Inventory objects by id
747
from bzrlib.inventory import Inventory
749
# See the discussion in get_revisions for why
750
# we don't use a try/finally block here
752
for f in self.inventory_store.get(inventory_ids, pb=pb, ignore_missing=ignore_missing):
754
# TODO: Possibly put a try/except around this to handle
755
# read serialization errors
756
r = bzrlib.xml.serializer_v4.read_inventory(f)
761
raise bzrlib.errors.NoSuchRevision(self, revision_id)
764
675
def get_inventory_sha1(self, inventory_id):
765
676
"""Return the sha1 hash of the inventory entry
795
706
def common_ancestor(self, other, self_revno=None, other_revno=None):
797
>>> from bzrlib.commit import commit
798
709
>>> sb = ScratchBranch(files=['foo', 'foo~'])
799
710
>>> sb.common_ancestor(sb) == (None, None)
801
>>> commit(sb, "Committing first revision", verbose=False)
712
>>> commit.commit(sb, "Committing first revision", verbose=False)
802
713
>>> sb.common_ancestor(sb)[0]
804
715
>>> clone = sb.clone()
805
>>> commit(sb, "Committing second revision", verbose=False)
716
>>> commit.commit(sb, "Committing second revision", verbose=False)
806
717
>>> sb.common_ancestor(sb)[0]
808
719
>>> sb.common_ancestor(clone)[0]
810
>>> commit(clone, "Committing divergent second revision",
721
>>> commit.commit(clone, "Committing divergent second revision",
811
722
... verbose=False)
812
723
>>> sb.common_ancestor(clone)[0]
904
815
"""Pull in all new revisions from other branch.
906
817
from bzrlib.fetch import greedy_fetch
907
from bzrlib.revision import get_intervening_revisions
909
819
pb = bzrlib.ui.ui_factory.progress_bar()
910
820
pb.update('comparing histories')
911
if stop_revision is None:
912
other_revision = other.last_patch()
822
revision_ids = self.missing_revisions(other, stop_revision)
824
if len(revision_ids) > 0:
825
count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
914
other_revision = other.lookup_revision(stop_revision)
915
count = greedy_fetch(self, other, other_revision, pb)[0]
917
revision_ids = self.missing_revisions(other, stop_revision)
918
except DivergedBranches, e:
920
revision_ids = get_intervening_revisions(self.last_patch(),
921
other_revision, self)
922
assert self.last_patch() not in revision_ids
923
except bzrlib.errors.NotAncestor:
926
828
self.append_revision(*revision_ids)
829
## note("Added %d revisions." % count)
929
834
def install_revisions(self, other, revision_ids, pb):
930
# We are going to iterate this many times, so make sure
931
# that it is a list, and not a generator
932
revision_ids = list(revision_ids)
933
835
if hasattr(other.revision_store, "prefetch"):
934
836
other.revision_store.prefetch(revision_ids)
935
837
if hasattr(other.inventory_store, "prefetch"):
838
inventory_ids = [other.get_revision(r).inventory_id
839
for r in revision_ids]
936
840
other.inventory_store.prefetch(inventory_ids)
939
843
pb = bzrlib.ui.ui_factory.progress_bar()
941
# This entire next section is generally done
942
# with either generators, or bulk updates
943
inventories = other.get_inventories(revision_ids, ignore_missing=True)
944
846
needed_texts = set()
947
good_revisions = set()
948
for i, (inv, rev_id) in enumerate(zip(inventories, revision_ids)):
850
for i, rev_id in enumerate(revision_ids):
949
851
pb.update('fetching revision', i+1, len(revision_ids))
951
# We don't really need to get the revision here, because
952
# the only thing we needed was the inventory_id, which now
953
# is (by design) identical to the revision_id
955
# rev = other.get_revision(rev_id)
956
# except bzrlib.errors.NoSuchRevision:
957
# failures.add(rev_id)
853
rev = other.get_revision(rev_id)
854
except bzrlib.errors.NoSuchRevision:
961
855
failures.add(rev_id)
964
good_revisions.add(rev_id)
858
revisions.append(rev)
859
inv = other.get_inventory(str(rev.inventory_id))
967
860
for key, entry in inv.iter_entries():
968
861
if entry.text_id is None:
970
text_ids.append(entry.text_id)
972
has_ids = self.text_store.has(text_ids)
973
for has, text_id in zip(has_ids, text_ids):
975
needed_texts.add(text_id)
863
if entry.text_id not in self.text_store:
864
needed_texts.add(entry.text_id)
979
868
count, cp_fail = self.text_store.copy_multi(other.text_store,
981
870
#print "Added %d texts." % count
982
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
871
inventory_ids = [ f.inventory_id for f in revisions ]
872
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
984
874
#print "Added %d inventories." % count
875
revision_ids = [ f.revision_id for f in revisions]
985
877
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
987
879
permit_failure=True)
988
880
assert len(cp_fail) == 0
989
881
return count, failures
1017
909
revision can also be a string, in which case it is parsed for something like
1018
910
'date:' or 'revid:' etc.
1020
revno, rev_id = self._get_revision_info(revision)
1022
raise bzrlib.errors.NoSuchRevision(self, revision)
1023
return revno, rev_id
1025
def get_rev_id(self, revno, history=None):
1026
"""Find the revision id of the specified revno."""
1030
history = self.revision_history()
1031
elif revno <= 0 or revno > len(history):
1032
raise bzrlib.errors.NoSuchRevision(self, revno)
1033
return history[revno - 1]
1035
def _get_revision_info(self, revision):
1036
"""Return (revno, revision id) for revision specifier.
1038
revision can be an integer, in which case it is assumed to be revno
1039
(though this will translate negative values into positive ones)
1040
revision can also be a string, in which case it is parsed for something
1041
like 'date:' or 'revid:' etc.
1043
A revid is always returned. If it is None, the specifier referred to
1044
the null revision. If the revid does not occur in the revision
1045
history, revno will be None.
1048
912
if revision is None:
1055
919
revs = self.revision_history()
1056
920
if isinstance(revision, int):
923
# Mabye we should do this first, but we don't need it if revision == 0
1057
924
if revision < 0:
1058
925
revno = len(revs) + revision + 1
1060
927
revno = revision
1061
rev_id = self.get_rev_id(revno, revs)
1062
928
elif isinstance(revision, basestring):
1063
929
for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
1064
930
if revision.startswith(prefix):
1065
result = func(self, revs, revision)
1067
revno, rev_id = result
1070
rev_id = self.get_rev_id(revno, revs)
931
revno = func(self, revs, revision)
1073
raise BzrError('No namespace registered for string: %r' %
1076
raise TypeError('Unhandled revision type %s' % revision)
934
raise BzrError('No namespace registered for string: %r' % revision)
1080
raise bzrlib.errors.NoSuchRevision(self, revision)
1081
return revno, rev_id
936
if revno is None or revno <= 0 or revno > len(revs):
937
raise BzrError("no such revision %s" % revision)
938
return revno, revs[revno-1]
1083
940
def _namespace_revno(self, revs, revision):
1084
941
"""Lookup a revision by revision number"""
1085
942
assert revision.startswith('revno:')
1087
return (int(revision[6:]),)
944
return int(revision[6:])
1088
945
except ValueError:
1090
947
REVISION_NAMESPACES['revno:'] = _namespace_revno
1092
949
def _namespace_revid(self, revs, revision):
1093
950
assert revision.startswith('revid:')
1094
rev_id = revision[len('revid:'):]
1096
return revs.index(rev_id) + 1, rev_id
952
return revs.index(revision[6:]) + 1
1097
953
except ValueError:
1099
955
REVISION_NAMESPACES['revid:'] = _namespace_revid
1101
957
def _namespace_last(self, revs, revision):
1188
1044
# TODO: Handle timezone.
1189
1045
dt = datetime.datetime.fromtimestamp(r.timestamp)
1190
1046
if first >= dt and (last is None or dt >= last):
1193
1049
for i in range(len(revs)):
1194
1050
r = self.get_revision(revs[i])
1195
1051
# TODO: Handle timezone.
1196
1052
dt = datetime.datetime.fromtimestamp(r.timestamp)
1197
1053
if first <= dt and (last is None or dt <= last):
1199
1055
REVISION_NAMESPACES['date:'] = _namespace_date
1202
def _namespace_ancestor(self, revs, revision):
1203
from revision import common_ancestor, MultipleRevisionSources
1204
other_branch = find_branch(_trim_namespace('ancestor', revision))
1205
revision_a = self.last_patch()
1206
revision_b = other_branch.last_patch()
1207
for r, b in ((revision_a, self), (revision_b, other_branch)):
1209
raise bzrlib.errors.NoCommits(b)
1210
revision_source = MultipleRevisionSources(self, other_branch)
1211
result = common_ancestor(revision_a, revision_b, revision_source)
1213
revno = self.revision_id_to_revno(result)
1214
except bzrlib.errors.NoSuchRevision:
1219
REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1221
1057
def revision_tree(self, revision_id):
1222
1058
"""Return Tree for a revision on this branch.
1433
def add_pending_merge(self, *revision_ids):
1268
def add_pending_merge(self, revision_id):
1434
1269
from bzrlib.revision import validate_revision_id
1436
for rev_id in revision_ids:
1437
validate_revision_id(rev_id)
1271
validate_revision_id(revision_id)
1439
1273
p = self.pending_merges()
1441
for rev_id in revision_ids:
1447
self.set_pending_merges(p)
1274
if revision_id in p:
1276
p.append(revision_id)
1277
self.set_pending_merges(p)
1449
1280
def set_pending_merges(self, rev_list):
1452
self.put_controlfile('pending-merges', '\n'.join(rev_list))
1457
def get_parent(self):
1458
"""Return the parent location of the branch.
1460
This is the default location for push/pull/missing. The usual
1461
pattern is that the user can override it by specifying a
1465
_locs = ['parent', 'pull', 'x-pull']
1468
return self.controlfile(l, 'r').read().strip('\n')
1470
if e.errno != errno.ENOENT:
1475
def set_parent(self, url):
1476
# TODO: Maybe delete old location files?
1477
1281
from bzrlib.atomicfile import AtomicFile
1478
1282
self.lock_write()
1480
f = AtomicFile(self.controlfilename('parent'))
1284
f = AtomicFile(self.controlfilename('pending-merges'))
1489
def check_revno(self, revno):
1491
Check whether a revno corresponds to any revision.
1492
Zero (the NULL revision) is considered valid.
1495
self.check_real_revno(revno)
1497
def check_real_revno(self, revno):
1499
Check whether a revno corresponds to a real revision.
1500
Zero (the NULL revision) is considered invalid
1502
if revno < 1 or revno > self.revno():
1503
raise InvalidRevisionNumber(revno)
1508
1296
class ScratchBranch(Branch):
1628
1414
"""Return a new tree-root file id."""
1629
1415
return gen_file_id('TREE_ROOT')
1632
def copy_branch(branch_from, to_location, revision=None):
1633
"""Copy branch_from into the existing directory to_location.
1636
If not None, only revisions up to this point will be copied.
1637
The head of the new branch will be that revision.
1640
The name of a local directory that exists but is empty.
1642
from bzrlib.merge import merge
1644
assert isinstance(branch_from, Branch)
1645
assert isinstance(to_location, basestring)
1647
br_to = Branch(to_location, init=True)
1648
br_to.set_root_id(branch_from.get_root_id())
1649
if revision is None:
1650
revno = branch_from.revno()
1652
revno, rev_id = branch_from.get_revision_info(revision)
1653
br_to.update_revisions(branch_from, stop_revision=revno)
1654
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1655
check_clean=False, ignore_zero=True)
1656
br_to.set_parent(branch_from.base)
1659
def _trim_namespace(namespace, spec):
1660
full_namespace = namespace + ':'
1661
assert spec.startswith(full_namespace)
1662
return spec[len(full_namespace):]