15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
from binascii import hexlify
23
from inventory import Inventory
24
from trace import mutter, note
25
from tree import Tree, EmptyTree, RevisionTree
26
from inventory import InventoryEntry, Inventory
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
28
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
29
joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
30
from store import ImmutableStore
31
from revision import Revision
32
from errors import BzrError
33
from textui import show_status
22
from bzrlib.trace import mutter, note
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
rename, splitpath, sha_file, appendpath, file_kind
26
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
27
DivergedBranches, NotBranchError
28
from bzrlib.textui import show_status
29
from bzrlib.revision import Revision
30
from bzrlib.delta import compare_trees
31
from bzrlib.tree import EmptyTree, RevisionTree
35
37
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
36
38
## TODO: Maybe include checks for common corruption of newlines, etc?
40
def find_branch(f, **args):
41
if f and (f.startswith('http://') or f.startswith('https://')):
43
return remotebranch.RemoteBranch(f, **args)
45
return Branch(f, **args)
41
# TODO: Some operations like log might retrieve the same revisions
42
# repeatedly to calculate deltas. We could perhaps have a weakref
43
# cache in memory to make this faster.
45
def find_branch(*ignored, **ignored_too):
46
# XXX: leave this here for about one release, then remove it
47
raise NotImplementedError('find_branch() is not supported anymore, '
48
'please use one of the new branch constructors')
49
50
def _relpath(base, path):
50
51
"""Return path relative to base, or raise exception.
101
102
head, tail = os.path.split(f)
103
104
# reached the root, whatever that may be
104
raise BzrError('%r is not in a branch' % orig_f)
105
raise NotBranchError('%s is not in a branch' % orig_f)
107
class DivergedBranches(Exception):
108
def __init__(self, branch1, branch2):
109
self.branch1 = branch1
110
self.branch2 = branch2
111
Exception.__init__(self, "These branches have diverged.")
114
class NoSuchRevision(BzrError):
115
def __init__(self, branch, revision):
117
self.revision = revision
118
msg = "Branch %s has no revision %d" % (branch, revision)
119
BzrError.__init__(self, msg)
122
111
######################################################################
126
115
"""Branch holding a history of revisions.
129
Base directory of the branch.
118
Base directory/url of the branch.
122
def __init__(self, *ignored, **ignored_too):
123
raise NotImplementedError('The Branch class is abstract')
127
"""Open an existing branch, rooted at 'base' (url)"""
128
if base and (base.startswith('http://') or base.startswith('https://')):
129
from bzrlib.remotebranch import RemoteBranch
130
return RemoteBranch(base, find_root=False)
132
return LocalBranch(base, find_root=False)
135
def open_containing(url):
136
"""Open an existing branch, containing url (search upwards for the root)
138
if url and (url.startswith('http://') or url.startswith('https://')):
139
from bzrlib.remotebranch import RemoteBranch
140
return RemoteBranch(url)
142
return LocalBranch(url)
145
def initialize(base):
146
"""Create a new branch, rooted at 'base' (url)"""
147
if base and (base.startswith('http://') or base.startswith('https://')):
148
from bzrlib.remotebranch import RemoteBranch
149
return RemoteBranch(base, init=True)
151
return LocalBranch(base, init=True)
153
def setup_caching(self, cache_root):
154
"""Subclasses that care about caching should override this, and set
155
up cached stores located under cache_root.
159
class LocalBranch(Branch):
160
"""A branch stored in the actual filesystem.
162
Note that it's "local" in the context of the filesystem; it doesn't
163
really matter if it's on an nfs/smb/afs/coda/... share, as long as
164
it's writable, and can be accessed via the normal filesystem API.
132
167
None, or 'r' or 'w'
139
174
Lock object from bzrlib.lock.
176
# We actually expect this class to be somewhat short-lived; part of its
177
# purpose is to try to isolate what bits of the branch logic are tied to
178
# filesystem access, so that in a later step, we can extricate them to
179
# a separarte ("storage") class.
142
180
_lock_mode = None
143
181
_lock_count = None
146
184
def __init__(self, base, init=False, find_root=True):
147
185
"""Create new branch object at a particular location.
149
base -- Base directory for the branch.
187
base -- Base directory for the branch. May be a file:// url.
151
189
init -- If True, create new control files in a previously
152
190
unversioned directory. If False, the branch must already
158
196
In the test suite, creation of new trees is tested using the
159
197
`ScratchBranch` class.
199
from bzrlib.store import ImmutableStore
162
201
self.base = os.path.realpath(base)
163
202
self._make_control()
165
204
self.base = find_branch_root(base)
206
if base.startswith("file://"):
167
208
self.base = os.path.realpath(base)
168
209
if not isdir(self.controlfilename('.')):
169
from errors import NotBranchError
170
210
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
171
211
['use "bzr init" to initialize a new working tree',
172
212
'current bzr can only operate from top-of-tree'])
234
269
self._lock = None
235
270
self._lock_mode = self._lock_count = None
238
272
def abspath(self, name):
239
273
"""Return absolute filename for something in the branch"""
240
274
return os.path.join(self.base, name)
243
276
def relpath(self, path):
244
277
"""Return path relative to this branch of something inside it.
246
279
Raises an error if path is not in this branch."""
247
280
return _relpath(self.base, path)
250
282
def controlfilename(self, file_or_path):
251
283
"""Return location relative to branch."""
252
if isinstance(file_or_path, types.StringTypes):
284
if isinstance(file_or_path, basestring):
253
285
file_or_path = [file_or_path]
254
286
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
291
323
os.mkdir(self.controlfilename(d))
292
324
for f in ('revision-history', 'merged-patches',
293
325
'pending-merged-patches', 'branch-name',
295
328
self.controlfile(f, 'w').write('')
296
329
mutter('created control directory in ' + self.base)
297
Inventory().write_xml(self.controlfile('inventory','w'))
331
# if we want per-tree root ids then this is the place to set
332
# them; they're not needed for now and so ommitted for
334
f = self.controlfile('inventory','w')
335
bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
300
338
def _check_format(self):
309
347
# on Windows from Linux and so on. I think it might be better
310
348
# to always make all internal files in unix format.
311
349
fmt = self.controlfile('branch-format', 'r').read()
312
fmt.replace('\r\n', '')
350
fmt = fmt.replace('\r\n', '\n')
313
351
if fmt != BZR_BRANCH_FORMAT:
314
352
raise BzrError('sorry, branch format %r not supported' % fmt,
315
353
['use a different bzr version',
316
354
'or remove the .bzr directory and "bzr init" again'])
356
def get_root_id(self):
357
"""Return the id of this branches root"""
358
inv = self.read_working_inventory()
359
return inv.root.file_id
361
def set_root_id(self, file_id):
362
inv = self.read_working_inventory()
363
orig_root_id = inv.root.file_id
364
del inv._byid[inv.root.file_id]
365
inv.root.file_id = file_id
366
inv._byid[inv.root.file_id] = inv.root
369
if entry.parent_id in (None, orig_root_id):
370
entry.parent_id = inv.root.file_id
371
self._write_inventory(inv)
320
373
def read_working_inventory(self):
321
374
"""Read the working inventory."""
323
# ElementTree does its own conversion from UTF-8, so open in
375
from bzrlib.inventory import Inventory
327
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
328
mutter("loaded inventory of %d items in %f"
329
% (len(inv), time.time() - before))
378
# ElementTree does its own conversion from UTF-8, so open in
380
f = self.controlfile('inventory', 'rb')
381
return bzrlib.xml.serializer_v4.read_inventory(f)
338
389
That is to say, the inventory describing changes underway, that
339
390
will be committed to the next revision.
341
## TODO: factor out to atomicfile? is rename safe on windows?
342
## TODO: Maybe some kind of clean/dirty marker on inventory?
343
tmpfname = self.controlfilename('inventory.tmp')
344
tmpf = file(tmpfname, 'wb')
347
inv_fname = self.controlfilename('inventory')
348
if sys.platform == 'win32':
350
os.rename(tmpfname, inv_fname)
392
from bzrlib.atomicfile import AtomicFile
396
f = AtomicFile(self.controlfilename('inventory'), 'wb')
398
bzrlib.xml.serializer_v4.write_inventory(inv, f)
351
405
mutter('wrote working inventory')
355
409
"""Inventory for the working copy.""")
358
def add(self, files, verbose=False, ids=None):
412
def add(self, files, ids=None):
359
413
"""Make files versioned.
361
Note that the command line normally calls smart_add instead.
415
Note that the command line normally calls smart_add instead,
416
which can automatically recurse.
363
418
This puts the files in the Added state, so that they will be
364
419
recorded by the next commit.
374
429
TODO: Perhaps have an option to add the ids even if the files do
377
TODO: Perhaps return the ids of the files? But then again it
378
is easy to retrieve them if they're needed.
380
TODO: Adding a directory should optionally recurse down and
381
add all non-ignored children. Perhaps do that in a
432
TODO: Perhaps yield the ids and paths as they're added.
384
434
# TODO: Re-adding a file that is removed in the working copy
385
435
# should probably put it back with the previous ID.
386
if isinstance(files, types.StringTypes):
387
assert(ids is None or isinstance(ids, types.StringTypes))
436
if isinstance(files, basestring):
437
assert(ids is None or isinstance(ids, basestring))
389
439
if ids is not None:
520
568
return self.working_tree().unknowns()
523
def append_revision(self, revision_id):
524
mutter("add {%s} to revision-history" % revision_id)
571
def append_revision(self, *revision_ids):
572
from bzrlib.atomicfile import AtomicFile
574
for revision_id in revision_ids:
575
mutter("add {%s} to revision-history" % revision_id)
525
577
rev_history = self.revision_history()
527
tmprhname = self.controlfilename('revision-history.tmp')
528
rhname = self.controlfilename('revision-history')
530
f = file(tmprhname, 'wt')
531
rev_history.append(revision_id)
532
f.write('\n'.join(rev_history))
536
if sys.platform == 'win32':
538
os.rename(tmprhname, rhname)
578
rev_history.extend(revision_ids)
580
f = AtomicFile(self.controlfilename('revision-history'))
582
for rev_id in rev_history:
589
def get_revision_xml_file(self, revision_id):
590
"""Return XML file object for revision object."""
591
if not revision_id or not isinstance(revision_id, basestring):
592
raise InvalidRevisionId(revision_id)
597
return self.revision_store[revision_id]
598
except (IndexError, KeyError):
599
raise bzrlib.errors.NoSuchRevision(self, revision_id)
605
get_revision_xml = get_revision_xml_file
542
608
def get_revision(self, revision_id):
543
609
"""Return the Revision object for a named revision"""
544
if not revision_id or not isinstance(revision_id, basestring):
545
raise ValueError('invalid revision-id: %r' % revision_id)
546
r = Revision.read_xml(self.revision_store[revision_id])
610
xml_file = self.get_revision_xml_file(revision_id)
613
r = bzrlib.xml.serializer_v4.read_revision(xml_file)
614
except SyntaxError, e:
615
raise bzrlib.errors.BzrError('failed to unpack revision_xml',
547
619
assert r.revision_id == revision_id
623
def get_revision_delta(self, revno):
624
"""Return the delta for one revision.
626
The delta is relative to its mainline predecessor, or the
627
empty tree for revision 1.
629
assert isinstance(revno, int)
630
rh = self.revision_history()
631
if not (1 <= revno <= len(rh)):
632
raise InvalidRevisionNumber(revno)
634
# revno is 1-based; list is 0-based
636
new_tree = self.revision_tree(rh[revno-1])
638
old_tree = EmptyTree()
640
old_tree = self.revision_tree(rh[revno-2])
642
return compare_trees(old_tree, new_tree)
550
646
def get_revision_sha1(self, revision_id):
551
647
"""Hash the stored value of a revision, and return it."""
552
648
# In the future, revision entries will be signed. At that
564
660
TODO: Perhaps for this and similar methods, take a revision
565
661
parameter which can be either an integer revno or a
567
i = Inventory.read_xml(self.inventory_store[inventory_id])
663
from bzrlib.inventory import Inventory
665
f = self.get_inventory_xml_file(inventory_id)
666
return bzrlib.xml.serializer_v4.read_inventory(f)
669
def get_inventory_xml(self, inventory_id):
670
"""Get inventory XML as a file object."""
671
return self.inventory_store[inventory_id]
673
get_inventory_xml_file = get_inventory_xml
570
676
def get_inventory_sha1(self, inventory_id):
571
677
"""Return the sha1 hash of the inventory entry
573
return sha_file(self.inventory_store[inventory_id])
679
return sha_file(self.get_inventory_xml(inventory_id))
576
682
def get_revision_inventory(self, revision_id):
577
683
"""Return inventory of a past revision."""
684
# bzr 0.0.6 imposes the constraint that the inventory_id
685
# must be the same as its revision, so this is trivial.
578
686
if revision_id == None:
687
from bzrlib.inventory import Inventory
688
return Inventory(self.get_root_id())
581
return self.get_inventory(self.get_revision(revision_id).inventory_id)
690
return self.get_inventory(revision_id)
584
693
def revision_history(self):
598
707
def common_ancestor(self, other, self_revno=None, other_revno=None):
709
>>> from bzrlib.commit import commit
601
710
>>> sb = ScratchBranch(files=['foo', 'foo~'])
602
711
>>> sb.common_ancestor(sb) == (None, None)
604
>>> commit.commit(sb, "Committing first revision", verbose=False)
713
>>> commit(sb, "Committing first revision", verbose=False)
605
714
>>> sb.common_ancestor(sb)[0]
607
716
>>> clone = sb.clone()
608
>>> commit.commit(sb, "Committing second revision", verbose=False)
717
>>> commit(sb, "Committing second revision", verbose=False)
609
718
>>> sb.common_ancestor(sb)[0]
611
720
>>> sb.common_ancestor(clone)[0]
613
>>> commit.commit(clone, "Committing divergent second revision",
722
>>> commit(clone, "Committing divergent second revision",
614
723
... verbose=False)
615
724
>>> sb.common_ancestor(clone)[0]
639
748
return r+1, my_history[r]
640
749
return None, None
642
def enum_history(self, direction):
643
"""Return (revno, revision_id) for history of branch.
646
'forward' is from earliest to latest
647
'reverse' is from latest to earliest
649
rh = self.revision_history()
650
if direction == 'forward':
655
elif direction == 'reverse':
661
raise ValueError('invalid history direction', direction)
665
753
"""Return current revision number for this branch.
719
807
if stop_revision is None:
720
808
stop_revision = other_len
721
809
elif stop_revision > other_len:
722
raise NoSuchRevision(self, stop_revision)
810
raise bzrlib.errors.NoSuchRevision(self, stop_revision)
724
812
return other_history[self_len:stop_revision]
727
815
def update_revisions(self, other, stop_revision=None):
728
816
"""Pull in all new revisions from other branch.
730
>>> from bzrlib.commit import commit
731
>>> bzrlib.trace.silent = True
732
>>> br1 = ScratchBranch(files=['foo', 'bar'])
735
>>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
736
>>> br2 = ScratchBranch()
737
>>> br2.update_revisions(br1)
741
>>> br2.revision_history()
743
>>> br2.update_revisions(br1)
747
>>> br1.text_store.total_size() == br2.text_store.total_size()
750
from bzrlib.progress import ProgressBar
818
from bzrlib.fetch import greedy_fetch
819
from bzrlib.revision import get_intervening_revisions
821
pb = bzrlib.ui.ui_factory.progress_bar()
754
822
pb.update('comparing histories')
755
revision_ids = self.missing_revisions(other, stop_revision)
823
if stop_revision is None:
824
other_revision = other.last_patch()
826
other_revision = other.get_rev_id(stop_revision)
827
count = greedy_fetch(self, other, other_revision, pb)[0]
829
revision_ids = self.missing_revisions(other, stop_revision)
830
except DivergedBranches, e:
832
revision_ids = get_intervening_revisions(self.last_patch(),
833
other_revision, self)
834
assert self.last_patch() not in revision_ids
835
except bzrlib.errors.NotAncestor:
838
self.append_revision(*revision_ids)
841
def install_revisions(self, other, revision_ids, pb):
842
if hasattr(other.revision_store, "prefetch"):
843
other.revision_store.prefetch(revision_ids)
844
if hasattr(other.inventory_store, "prefetch"):
846
for rev_id in revision_ids:
848
revision = other.get_revision(rev_id).inventory_id
849
inventory_ids.append(revision)
850
except bzrlib.errors.NoSuchRevision:
852
other.inventory_store.prefetch(inventory_ids)
855
pb = bzrlib.ui.ui_factory.progress_bar()
757
needed_texts = sets.Set()
759
for rev_id in revision_ids:
761
pb.update('fetching revision', i, len(revision_ids))
762
rev = other.get_revision(rev_id)
862
for i, rev_id in enumerate(revision_ids):
863
pb.update('fetching revision', i+1, len(revision_ids))
865
rev = other.get_revision(rev_id)
866
except bzrlib.errors.NoSuchRevision:
763
870
revisions.append(rev)
764
871
inv = other.get_inventory(str(rev.inventory_id))
765
872
for key, entry in inv.iter_entries():
773
count = self.text_store.copy_multi(other.text_store, needed_texts)
774
print "Added %d texts." % count
880
count, cp_fail = self.text_store.copy_multi(other.text_store,
882
#print "Added %d texts." % count
775
883
inventory_ids = [ f.inventory_id for f in revisions ]
776
count = self.inventory_store.copy_multi(other.inventory_store,
778
print "Added %d inventories." % count
884
count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
886
#print "Added %d inventories." % count
779
887
revision_ids = [ f.revision_id for f in revisions]
780
count = self.revision_store.copy_multi(other.revision_store,
782
for revision_id in revision_ids:
783
self.append_revision(revision_id)
784
print "Added %d revisions." % count
889
count, cp_fail = self.revision_store.copy_multi(other.revision_store,
892
assert len(cp_fail) == 0
893
return count, failures
787
896
def commit(self, *args, **kw):
789
897
from bzrlib.commit import commit
790
898
commit(self, *args, **kw)
900
def revision_id_to_revno(self, revision_id):
901
"""Given a revision id, return its revno"""
902
history = self.revision_history()
904
return history.index(revision_id) + 1
906
raise bzrlib.errors.NoSuchRevision(self, revision_id)
793
def lookup_revision(self, revno):
794
"""Return revision hash for revision number."""
908
def get_rev_id(self, revno, history=None):
909
"""Find the revision id of the specified revno."""
799
# list is 0-based; revisions are 1-based
800
return self.revision_history()[revno-1]
802
raise BzrError("no such revision %s" % revno)
913
history = self.revision_history()
914
elif revno <= 0 or revno > len(history):
915
raise bzrlib.errors.NoSuchRevision(self, revno)
916
return history[revno - 1]
805
919
def revision_tree(self, revision_id):
937
1053
for f in from_paths:
938
1054
name_tail = splitpath(f)[-1]
939
1055
dest_path = appendpath(to_name, name_tail)
940
print "%s => %s" % (f, dest_path)
1056
result.append((f, dest_path))
941
1057
inv.rename(inv.path2id(f), to_dir_id, name_tail)
943
os.rename(self.abspath(f), self.abspath(dest_path))
1059
rename(self.abspath(f), self.abspath(dest_path))
944
1060
except OSError, e:
945
1061
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
946
1062
["rename rolled back"])
954
class ScratchBranch(Branch):
1071
def revert(self, filenames, old_tree=None, backups=True):
1072
"""Restore selected files to the versions from a previous tree.
1075
If true (default) backups are made of files before
1078
from bzrlib.errors import NotVersionedError, BzrError
1079
from bzrlib.atomicfile import AtomicFile
1080
from bzrlib.osutils import backup_file
1082
inv = self.read_working_inventory()
1083
if old_tree is None:
1084
old_tree = self.basis_tree()
1085
old_inv = old_tree.inventory
1088
for fn in filenames:
1089
file_id = inv.path2id(fn)
1091
raise NotVersionedError("not a versioned file", fn)
1092
if not old_inv.has_id(file_id):
1093
raise BzrError("file not present in old tree", fn, file_id)
1094
nids.append((fn, file_id))
1096
# TODO: Rename back if it was previously at a different location
1098
# TODO: If given a directory, restore the entire contents from
1099
# the previous version.
1101
# TODO: Make a backup to a temporary file.
1103
# TODO: If the file previously didn't exist, delete it?
1104
for fn, file_id in nids:
1107
f = AtomicFile(fn, 'wb')
1109
f.write(old_tree.get_file(file_id).read())
1115
def pending_merges(self):
1116
"""Return a list of pending merges.
1118
These are revisions that have been merged into the working
1119
directory but not yet committed.
1121
cfn = self.controlfilename('pending-merges')
1122
if not os.path.exists(cfn):
1125
for l in self.controlfile('pending-merges', 'r').readlines():
1126
p.append(l.rstrip('\n'))
1130
def add_pending_merge(self, revision_id):
1131
from bzrlib.revision import validate_revision_id
1133
validate_revision_id(revision_id)
1135
p = self.pending_merges()
1136
if revision_id in p:
1138
p.append(revision_id)
1139
self.set_pending_merges(p)
1142
def set_pending_merges(self, rev_list):
1143
from bzrlib.atomicfile import AtomicFile
1146
f = AtomicFile(self.controlfilename('pending-merges'))
1157
def get_parent(self):
1158
"""Return the parent location of the branch.
1160
This is the default location for push/pull/missing. The usual
1161
pattern is that the user can override it by specifying a
1165
_locs = ['parent', 'pull', 'x-pull']
1168
return self.controlfile(l, 'r').read().strip('\n')
1170
if e.errno != errno.ENOENT:
1175
def set_parent(self, url):
1176
# TODO: Maybe delete old location files?
1177
from bzrlib.atomicfile import AtomicFile
1180
f = AtomicFile(self.controlfilename('parent'))
1189
def check_revno(self, revno):
1191
Check whether a revno corresponds to any revision.
1192
Zero (the NULL revision) is considered valid.
1195
self.check_real_revno(revno)
1197
def check_real_revno(self, revno):
1199
Check whether a revno corresponds to a real revision.
1200
Zero (the NULL revision) is considered invalid
1202
if revno < 1 or revno > self.revno():
1203
raise InvalidRevisionNumber(revno)
1209
class ScratchBranch(LocalBranch):
955
1210
"""Special test class: a branch that cleans up after itself.
957
1212
>>> b = ScratchBranch()
986
1242
>>> orig = ScratchBranch(files=["file1", "file2"])
987
1243
>>> clone = orig.clone()
988
>>> os.path.samefile(orig.base, clone.base)
1244
>>> if os.name != 'nt':
1245
... os.path.samefile(orig.base, clone.base)
1247
... orig.base == clone.base
990
1250
>>> os.path.isfile(os.path.join(clone.base, "file1"))
993
base = tempfile.mkdtemp()
1253
from shutil import copytree
1254
from tempfile import mkdtemp
995
shutil.copytree(self.base, base, symlinks=True)
1257
copytree(self.base, base, symlinks=True)
996
1258
return ScratchBranch(base=base)
998
1262
def __del__(self):
1001
1265
def destroy(self):
1002
1266
"""Destroy the test branch, removing the scratch directory."""
1267
from shutil import rmtree
1005
1270
mutter("delete ScratchBranch %s" % self.base)
1006
shutil.rmtree(self.base)
1007
1272
except OSError, e:
1008
1273
# Work around for shutil.rmtree failing on Windows when
1009
1274
# readonly files are encountered
1059
1326
name = re.sub(r'[^\w.]', '', name)
1061
1328
s = hexlify(rand_bytes(8))
1062
return '-'.join((name, compact_date(time.time()), s))
1329
return '-'.join((name, compact_date(time()), s))
1333
"""Return a new tree-root file id."""
1334
return gen_file_id('TREE_ROOT')
1337
def copy_branch(branch_from, to_location, revno=None):
1338
"""Copy branch_from into the existing directory to_location.
1341
If not None, only revisions up to this point will be copied.
1342
The head of the new branch will be that revision.
1345
The name of a local directory that exists but is empty.
1347
from bzrlib.merge import merge
1349
assert isinstance(branch_from, Branch)
1350
assert isinstance(to_location, basestring)
1352
br_to = Branch.initialize(to_location)
1353
br_to.set_root_id(branch_from.get_root_id())
1355
revno = branch_from.revno()
1356
br_to.update_revisions(branch_from, stop_revision=revno)
1357
merge((to_location, -1), (to_location, 0), this_dir=to_location,
1358
check_clean=False, ignore_zero=True)
1359
br_to.set_parent(branch_from.base)