~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-09-01 02:34:38 UTC
  • Revision ID: mbp@sourcefrog.net-20050901023437-bf791a0ef5edae8d
- old docs: clarify that this is not mainly descended from arch anymore

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
import sys, os
 
18
import sys
 
19
import os
19
20
 
20
21
import bzrlib
21
22
from bzrlib.trace import mutter, note
22
 
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, splitpath, \
 
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
 
24
     splitpath, \
23
25
     sha_file, appendpath, file_kind
24
 
from bzrlib.errors import BzrError
 
26
 
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
 
28
import bzrlib.errors
 
29
from bzrlib.textui import show_status
 
30
from bzrlib.revision import Revision
 
31
from bzrlib.xml import unpack_xml
 
32
from bzrlib.delta import compare_trees
 
33
from bzrlib.tree import EmptyTree, RevisionTree
 
34
import bzrlib.ui
 
35
 
 
36
 
25
37
 
26
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
27
39
## TODO: Maybe include checks for common corruption of newlines, etc?
28
40
 
29
41
 
 
42
# TODO: Some operations like log might retrieve the same revisions
 
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
 
44
# cache in memory to make this faster.
 
45
 
 
46
# TODO: please move the revision-string syntax stuff out of the branch
 
47
# object; it's clutter
 
48
 
30
49
 
31
50
def find_branch(f, **args):
32
51
    if f and (f.startswith('http://') or f.startswith('https://')):
89
108
    It is not necessary that f exists.
90
109
 
91
110
    Basically we keep looking up until we find the control directory or
92
 
    run into the root."""
 
111
    run into the root.  If there isn't one, raises NotBranchError.
 
112
    """
93
113
    if f == None:
94
114
        f = os.getcwd()
95
115
    elif hasattr(os.path, 'realpath'):
108
128
        head, tail = os.path.split(f)
109
129
        if head == f:
110
130
            # reached the root, whatever that may be
111
 
            raise BzrError('%r is not in a branch' % orig_f)
 
131
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
112
132
        f = head
113
 
    
 
133
 
 
134
 
 
135
 
 
136
# XXX: move into bzrlib.errors; subclass BzrError    
114
137
class DivergedBranches(Exception):
115
138
    def __init__(self, branch1, branch2):
116
139
        self.branch1 = branch1
118
141
        Exception.__init__(self, "These branches have diverged.")
119
142
 
120
143
 
121
 
class NoSuchRevision(BzrError):
122
 
    def __init__(self, branch, revision):
123
 
        self.branch = branch
124
 
        self.revision = revision
125
 
        msg = "Branch %s has no revision %d" % (branch, revision)
126
 
        BzrError.__init__(self, msg)
127
 
 
128
 
 
129
144
######################################################################
130
145
# branch objects
131
146
 
204
219
            self._lock.unlock()
205
220
 
206
221
 
207
 
 
208
222
    def lock_write(self):
209
223
        if self._lock_mode:
210
224
            if self._lock_mode != 'w':
220
234
            self._lock_count = 1
221
235
 
222
236
 
223
 
 
224
237
    def lock_read(self):
225
238
        if self._lock_mode:
226
239
            assert self._lock_mode in ('r', 'w'), \
233
246
            self._lock_mode = 'r'
234
247
            self._lock_count = 1
235
248
                        
236
 
 
237
 
            
238
249
    def unlock(self):
239
250
        if not self._lock_mode:
240
251
            from errors import LockError
247
258
            self._lock = None
248
259
            self._lock_mode = self._lock_count = None
249
260
 
250
 
 
251
261
    def abspath(self, name):
252
262
        """Return absolute filename for something in the branch"""
253
263
        return os.path.join(self.base, name)
254
264
 
255
 
 
256
265
    def relpath(self, path):
257
266
        """Return path relative to this branch of something inside it.
258
267
 
259
268
        Raises an error if path is not in this branch."""
260
269
        return _relpath(self.base, path)
261
270
 
262
 
 
263
271
    def controlfilename(self, file_or_path):
264
272
        """Return location relative to branch."""
265
273
        if isinstance(file_or_path, basestring):
292
300
        else:
293
301
            raise BzrError("invalid controlfile mode %r" % mode)
294
302
 
295
 
 
296
 
 
297
303
    def _make_control(self):
298
304
        from bzrlib.inventory import Inventory
299
305
        from bzrlib.xml import pack_xml
312
318
            self.controlfile(f, 'w').write('')
313
319
        mutter('created control directory in ' + self.base)
314
320
 
 
321
        # if we want per-tree root ids then this is the place to set
 
322
        # them; they're not needed for now and so ommitted for
 
323
        # simplicity.
315
324
        pack_xml(Inventory(), self.controlfile('inventory','w'))
316
325
 
317
 
 
318
326
    def _check_format(self):
319
327
        """Check this branch format is supported.
320
328
 
333
341
                           ['use a different bzr version',
334
342
                            'or remove the .bzr directory and "bzr init" again'])
335
343
 
 
344
    def get_root_id(self):
 
345
        """Return the id of this branches root"""
 
346
        inv = self.read_working_inventory()
 
347
        return inv.root.file_id
336
348
 
 
349
    def set_root_id(self, file_id):
 
350
        inv = self.read_working_inventory()
 
351
        orig_root_id = inv.root.file_id
 
352
        del inv._byid[inv.root.file_id]
 
353
        inv.root.file_id = file_id
 
354
        inv._byid[inv.root.file_id] = inv.root
 
355
        for fid in inv:
 
356
            entry = inv[fid]
 
357
            if entry.parent_id in (None, orig_root_id):
 
358
                entry.parent_id = inv.root.file_id
 
359
        self._write_inventory(inv)
337
360
 
338
361
    def read_working_inventory(self):
339
362
        """Read the working inventory."""
346
369
            # ElementTree does its own conversion from UTF-8, so open in
347
370
            # binary.
348
371
            inv = unpack_xml(Inventory,
349
 
                                  self.controlfile('inventory', 'rb'))
 
372
                             self.controlfile('inventory', 'rb'))
350
373
            mutter("loaded inventory of %d items in %f"
351
374
                   % (len(inv), time() - before))
352
375
            return inv
381
404
                         """Inventory for the working copy.""")
382
405
 
383
406
 
384
 
    def add(self, files, verbose=False, ids=None):
 
407
    def add(self, files, ids=None):
385
408
        """Make files versioned.
386
409
 
387
 
        Note that the command line normally calls smart_add instead.
 
410
        Note that the command line normally calls smart_add instead,
 
411
        which can automatically recurse.
388
412
 
389
413
        This puts the files in the Added state, so that they will be
390
414
        recorded by the next commit.
400
424
        TODO: Perhaps have an option to add the ids even if the files do
401
425
              not (yet) exist.
402
426
 
403
 
        TODO: Perhaps return the ids of the files?  But then again it
404
 
              is easy to retrieve them if they're needed.
405
 
 
406
 
        TODO: Adding a directory should optionally recurse down and
407
 
              add all non-ignored children.  Perhaps do that in a
408
 
              higher-level method.
 
427
        TODO: Perhaps yield the ids and paths as they're added.
409
428
        """
410
 
        from bzrlib.textui import show_status
411
429
        # TODO: Re-adding a file that is removed in the working copy
412
430
        # should probably put it back with the previous ID.
413
431
        if isinstance(files, basestring):
448
466
                    file_id = gen_file_id(f)
449
467
                inv.add_path(f, kind=kind, file_id=file_id)
450
468
 
451
 
                if verbose:
452
 
                    print 'added', quotefn(f)
453
 
 
454
469
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
455
470
 
456
471
            self._write_inventory(inv)
486
501
        is the opposite of add.  Removing it is consistent with most
487
502
        other tools.  Maybe an option.
488
503
        """
489
 
        from bzrlib.textui import show_status
490
504
        ## TODO: Normalize names
491
505
        ## TODO: Remove nested loops; better scalability
492
506
        if isinstance(files, basestring):
521
535
    # FIXME: this doesn't need to be a branch method
522
536
    def set_inventory(self, new_inventory_list):
523
537
        from bzrlib.inventory import Inventory, InventoryEntry
524
 
        inv = Inventory()
 
538
        inv = Inventory(self.get_root_id())
525
539
        for path, file_id, parent, kind in new_inventory_list:
526
540
            name = os.path.basename(path)
527
541
            if name == "":
549
563
        return self.working_tree().unknowns()
550
564
 
551
565
 
552
 
    def append_revision(self, revision_id):
 
566
    def append_revision(self, *revision_ids):
553
567
        from bzrlib.atomicfile import AtomicFile
554
568
 
555
 
        mutter("add {%s} to revision-history" % revision_id)
556
 
        rev_history = self.revision_history() + [revision_id]
 
569
        for revision_id in revision_ids:
 
570
            mutter("add {%s} to revision-history" % revision_id)
 
571
 
 
572
        rev_history = self.revision_history()
 
573
        rev_history.extend(revision_ids)
557
574
 
558
575
        f = AtomicFile(self.controlfilename('revision-history'))
559
576
        try:
564
581
            f.close()
565
582
 
566
583
 
 
584
    def get_revision_xml(self, revision_id):
 
585
        """Return XML file object for revision object."""
 
586
        if not revision_id or not isinstance(revision_id, basestring):
 
587
            raise InvalidRevisionId(revision_id)
 
588
 
 
589
        self.lock_read()
 
590
        try:
 
591
            try:
 
592
                return self.revision_store[revision_id]
 
593
            except IndexError:
 
594
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
595
        finally:
 
596
            self.unlock()
 
597
 
 
598
 
567
599
    def get_revision(self, revision_id):
568
600
        """Return the Revision object for a named revision"""
569
 
        from bzrlib.revision import Revision
570
 
        from bzrlib.xml import unpack_xml
 
601
        xml_file = self.get_revision_xml(revision_id)
571
602
 
572
 
        self.lock_read()
573
603
        try:
574
 
            if not revision_id or not isinstance(revision_id, basestring):
575
 
                raise ValueError('invalid revision-id: %r' % revision_id)
576
 
            r = unpack_xml(Revision, self.revision_store[revision_id])
577
 
        finally:
578
 
            self.unlock()
 
604
            r = unpack_xml(Revision, xml_file)
 
605
        except SyntaxError, e:
 
606
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
 
607
                                         [revision_id,
 
608
                                          str(e)])
579
609
            
580
610
        assert r.revision_id == revision_id
581
611
        return r
 
612
 
 
613
 
 
614
    def get_revision_delta(self, revno):
 
615
        """Return the delta for one revision.
 
616
 
 
617
        The delta is relative to its mainline predecessor, or the
 
618
        empty tree for revision 1.
 
619
        """
 
620
        assert isinstance(revno, int)
 
621
        rh = self.revision_history()
 
622
        if not (1 <= revno <= len(rh)):
 
623
            raise InvalidRevisionNumber(revno)
 
624
 
 
625
        # revno is 1-based; list is 0-based
 
626
 
 
627
        new_tree = self.revision_tree(rh[revno-1])
 
628
        if revno == 1:
 
629
            old_tree = EmptyTree()
 
630
        else:
 
631
            old_tree = self.revision_tree(rh[revno-2])
 
632
 
 
633
        return compare_trees(old_tree, new_tree)
 
634
 
582
635
        
583
636
 
584
637
    def get_revision_sha1(self, revision_id):
589
642
        # the revision, (add signatures/remove signatures) and still
590
643
        # have all hash pointers stay consistent.
591
644
        # But for now, just hash the contents.
592
 
        return sha_file(self.revision_store[revision_id])
 
645
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
593
646
 
594
647
 
595
648
    def get_inventory(self, inventory_id):
601
654
        from bzrlib.inventory import Inventory
602
655
        from bzrlib.xml import unpack_xml
603
656
 
604
 
        return unpack_xml(Inventory, self.inventory_store[inventory_id])
 
657
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
 
658
 
 
659
 
 
660
    def get_inventory_xml(self, inventory_id):
 
661
        """Get inventory XML as a file object."""
 
662
        return self.inventory_store[inventory_id]
605
663
            
606
664
 
607
665
    def get_inventory_sha1(self, inventory_id):
608
666
        """Return the sha1 hash of the inventory entry
609
667
        """
610
 
        return sha_file(self.inventory_store[inventory_id])
 
668
        return sha_file(self.get_inventory_xml(inventory_id))
611
669
 
612
670
 
613
671
    def get_revision_inventory(self, revision_id):
616
674
        # must be the same as its revision, so this is trivial.
617
675
        if revision_id == None:
618
676
            from bzrlib.inventory import Inventory
619
 
            return Inventory()
 
677
            return Inventory(self.get_root_id())
620
678
        else:
621
679
            return self.get_inventory(revision_id)
622
680
 
679
737
                return r+1, my_history[r]
680
738
        return None, None
681
739
 
682
 
    def enum_history(self, direction):
683
 
        """Return (revno, revision_id) for history of branch.
684
 
 
685
 
        direction
686
 
            'forward' is from earliest to latest
687
 
            'reverse' is from latest to earliest
688
 
        """
689
 
        rh = self.revision_history()
690
 
        if direction == 'forward':
691
 
            i = 1
692
 
            for rid in rh:
693
 
                yield i, rid
694
 
                i += 1
695
 
        elif direction == 'reverse':
696
 
            i = len(rh)
697
 
            while i > 0:
698
 
                yield i, rh[i-1]
699
 
                i -= 1
700
 
        else:
701
 
            raise ValueError('invalid history direction', direction)
702
 
 
703
740
 
704
741
    def revno(self):
705
742
        """Return current revision number for this branch.
720
757
            return None
721
758
 
722
759
 
723
 
    def missing_revisions(self, other, stop_revision=None):
 
760
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
724
761
        """
725
762
        If self and other have not diverged, return a list of the revisions
726
763
        present in other, but missing from self.
759
796
        if stop_revision is None:
760
797
            stop_revision = other_len
761
798
        elif stop_revision > other_len:
762
 
            raise NoSuchRevision(self, stop_revision)
 
799
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
763
800
        
764
801
        return other_history[self_len:stop_revision]
765
802
 
766
803
 
767
804
    def update_revisions(self, other, stop_revision=None):
768
805
        """Pull in all new revisions from other branch.
769
 
        
770
 
        >>> from bzrlib.commit import commit
771
 
        >>> bzrlib.trace.silent = True
772
 
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
773
 
        >>> br1.add('foo')
774
 
        >>> br1.add('bar')
775
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
776
 
        >>> br2 = ScratchBranch()
777
 
        >>> br2.update_revisions(br1)
778
 
        Added 2 texts.
779
 
        Added 1 inventories.
780
 
        Added 1 revisions.
781
 
        >>> br2.revision_history()
782
 
        [u'REVISION-ID-1']
783
 
        >>> br2.update_revisions(br1)
784
 
        Added 0 texts.
785
 
        Added 0 inventories.
786
 
        Added 0 revisions.
787
 
        >>> br1.text_store.total_size() == br2.text_store.total_size()
788
 
        True
789
806
        """
790
 
        from bzrlib.progress import ProgressBar
791
 
        try:
792
 
            set
793
 
        except NameError:
794
 
            from sets import Set as set
795
 
 
796
 
        pb = ProgressBar()
797
 
 
 
807
        from bzrlib.fetch import greedy_fetch
 
808
 
 
809
        pb = bzrlib.ui.ui_factory.progress_bar()
798
810
        pb.update('comparing histories')
 
811
 
799
812
        revision_ids = self.missing_revisions(other, stop_revision)
800
813
 
 
814
        if len(revision_ids) > 0:
 
815
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
 
816
        else:
 
817
            count = 0
 
818
        self.append_revision(*revision_ids)
 
819
        ## note("Added %d revisions." % count)
 
820
        pb.clear()
 
821
 
 
822
    def install_revisions(self, other, revision_ids, pb):
801
823
        if hasattr(other.revision_store, "prefetch"):
802
824
            other.revision_store.prefetch(revision_ids)
803
825
        if hasattr(other.inventory_store, "prefetch"):
804
826
            inventory_ids = [other.get_revision(r).inventory_id
805
827
                             for r in revision_ids]
806
828
            other.inventory_store.prefetch(inventory_ids)
 
829
 
 
830
        if pb is None:
 
831
            pb = bzrlib.ui.ui_factory.progress_bar()
807
832
                
808
833
        revisions = []
809
834
        needed_texts = set()
810
835
        i = 0
811
 
        for rev_id in revision_ids:
812
 
            i += 1
813
 
            pb.update('fetching revision', i, len(revision_ids))
814
 
            rev = other.get_revision(rev_id)
 
836
 
 
837
        failures = set()
 
838
        for i, rev_id in enumerate(revision_ids):
 
839
            pb.update('fetching revision', i+1, len(revision_ids))
 
840
            try:
 
841
                rev = other.get_revision(rev_id)
 
842
            except bzrlib.errors.NoSuchRevision:
 
843
                failures.add(rev_id)
 
844
                continue
 
845
 
815
846
            revisions.append(rev)
816
847
            inv = other.get_inventory(str(rev.inventory_id))
817
848
            for key, entry in inv.iter_entries():
822
853
 
823
854
        pb.clear()
824
855
                    
825
 
        count = self.text_store.copy_multi(other.text_store, needed_texts)
826
 
        print "Added %d texts." % count 
 
856
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
857
                                                    needed_texts)
 
858
        #print "Added %d texts." % count 
827
859
        inventory_ids = [ f.inventory_id for f in revisions ]
828
 
        count = self.inventory_store.copy_multi(other.inventory_store, 
829
 
                                                inventory_ids)
830
 
        print "Added %d inventories." % count 
 
860
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
861
                                                         inventory_ids)
 
862
        #print "Added %d inventories." % count 
831
863
        revision_ids = [ f.revision_id for f in revisions]
832
 
        count = self.revision_store.copy_multi(other.revision_store, 
833
 
                                               revision_ids)
834
 
        for revision_id in revision_ids:
835
 
            self.append_revision(revision_id)
836
 
        print "Added %d revisions." % count
837
 
                    
838
 
        
 
864
 
 
865
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
866
                                                          revision_ids,
 
867
                                                          permit_failure=True)
 
868
        assert len(cp_fail) == 0 
 
869
        return count, failures
 
870
       
 
871
 
839
872
    def commit(self, *args, **kw):
840
873
        from bzrlib.commit import commit
841
874
        commit(self, *args, **kw)
846
879
        revno, info = self.get_revision_info(revision)
847
880
        return info
848
881
 
 
882
 
 
883
    def revision_id_to_revno(self, revision_id):
 
884
        """Given a revision id, return its revno"""
 
885
        history = self.revision_history()
 
886
        try:
 
887
            return history.index(revision_id) + 1
 
888
        except ValueError:
 
889
            raise bzrlib.errors.NoSuchRevision(self, revision_id)
 
890
 
 
891
 
849
892
    def get_revision_info(self, revision):
850
893
        """Return (revno, revision id) for revision identifier.
851
894
 
948
991
        elif val.lower() == 'tomorrow':
949
992
            dt = today + datetime.timedelta(days=1)
950
993
        else:
 
994
            import re
951
995
            # This should be done outside the function to avoid recompiling it.
952
996
            _date_re = re.compile(
953
997
                    r'(?P<date>(?P<year>\d\d\d\d)-(?P<month>\d\d)-(?P<day>\d\d))?'
1003
1047
 
1004
1048
        `revision_id` may be None for the null revision, in which case
1005
1049
        an `EmptyTree` is returned."""
1006
 
        from bzrlib.tree import EmptyTree, RevisionTree
1007
1050
        # TODO: refactor this to use an existing revision object
1008
1051
        # so we don't need to read it in twice.
1009
1052
        if revision_id == None:
1024
1067
 
1025
1068
        If there are no revisions yet, return an `EmptyTree`.
1026
1069
        """
1027
 
        from bzrlib.tree import EmptyTree, RevisionTree
1028
1070
        r = self.last_patch()
1029
1071
        if r == None:
1030
1072
            return EmptyTree()
1068
1110
 
1069
1111
            inv.rename(file_id, to_dir_id, to_tail)
1070
1112
 
1071
 
            print "%s => %s" % (from_rel, to_rel)
1072
 
 
1073
1113
            from_abs = self.abspath(from_rel)
1074
1114
            to_abs = self.abspath(to_rel)
1075
1115
            try:
1094
1134
 
1095
1135
        Note that to_name is only the last component of the new name;
1096
1136
        this doesn't change the directory.
 
1137
 
 
1138
        This returns a list of (from_path, to_path) pairs for each
 
1139
        entry that is moved.
1097
1140
        """
 
1141
        result = []
1098
1142
        self.lock_write()
1099
1143
        try:
1100
1144
            ## TODO: Option to move IDs only
1135
1179
            for f in from_paths:
1136
1180
                name_tail = splitpath(f)[-1]
1137
1181
                dest_path = appendpath(to_name, name_tail)
1138
 
                print "%s => %s" % (f, dest_path)
 
1182
                result.append((f, dest_path))
1139
1183
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1140
1184
                try:
1141
1185
                    os.rename(self.abspath(f), self.abspath(dest_path))
1147
1191
        finally:
1148
1192
            self.unlock()
1149
1193
 
 
1194
        return result
 
1195
 
1150
1196
 
1151
1197
    def revert(self, filenames, old_tree=None, backups=True):
1152
1198
        """Restore selected files to the versions from a previous tree.
1234
1280
            self.unlock()
1235
1281
 
1236
1282
 
 
1283
    def get_parent(self):
 
1284
        """Return the parent location of the branch.
 
1285
 
 
1286
        This is the default location for push/pull/missing.  The usual
 
1287
        pattern is that the user can override it by specifying a
 
1288
        location.
 
1289
        """
 
1290
        import errno
 
1291
        _locs = ['parent', 'pull', 'x-pull']
 
1292
        for l in _locs:
 
1293
            try:
 
1294
                return self.controlfile(l, 'r').read().strip('\n')
 
1295
            except IOError, e:
 
1296
                if e.errno != errno.ENOENT:
 
1297
                    raise
 
1298
        return None
 
1299
 
 
1300
 
 
1301
    def set_parent(self, url):
 
1302
        # TODO: Maybe delete old location files?
 
1303
        from bzrlib.atomicfile import AtomicFile
 
1304
        self.lock_write()
 
1305
        try:
 
1306
            f = AtomicFile(self.controlfilename('parent'))
 
1307
            try:
 
1308
                f.write(url + '\n')
 
1309
                f.commit()
 
1310
            finally:
 
1311
                f.close()
 
1312
        finally:
 
1313
            self.unlock()
 
1314
 
 
1315
        
 
1316
 
1237
1317
 
1238
1318
class ScratchBranch(Branch):
1239
1319
    """Special test class: a branch that cleans up after itself.
1281
1361
        os.rmdir(base)
1282
1362
        copytree(self.base, base, symlinks=True)
1283
1363
        return ScratchBranch(base=base)
 
1364
 
 
1365
 
1284
1366
        
1285
1367
    def __del__(self):
1286
1368
        self.destroy()
1350
1432
 
1351
1433
    s = hexlify(rand_bytes(8))
1352
1434
    return '-'.join((name, compact_date(time()), s))
 
1435
 
 
1436
 
 
1437
def gen_root_id():
 
1438
    """Return a new tree-root file id."""
 
1439
    return gen_file_id('TREE_ROOT')
 
1440
 
 
1441
 
 
1442
def pull_loc(branch):
 
1443
    # TODO: Should perhaps just make attribute be 'base' in
 
1444
    # RemoteBranch and Branch?
 
1445
    if hasattr(branch, "baseurl"):
 
1446
        return branch.baseurl
 
1447
    else:
 
1448
        return branch.base
 
1449
 
 
1450
 
 
1451
def copy_branch(branch_from, to_location, revision=None):
 
1452
    """Copy branch_from into the existing directory to_location.
 
1453
 
 
1454
    revision
 
1455
        If not None, only revisions up to this point will be copied.
 
1456
        The head of the new branch will be that revision.
 
1457
 
 
1458
    to_location
 
1459
        The name of a local directory that exists but is empty.
 
1460
    """
 
1461
    from bzrlib.merge import merge
 
1462
    from bzrlib.branch import Branch
 
1463
 
 
1464
    assert isinstance(branch_from, Branch)
 
1465
    assert isinstance(to_location, basestring)
 
1466
    
 
1467
    br_to = Branch(to_location, init=True)
 
1468
    br_to.set_root_id(branch_from.get_root_id())
 
1469
    if revision is None:
 
1470
        revno = branch_from.revno()
 
1471
    else:
 
1472
        revno, rev_id = branch_from.get_revision_info(revision)
 
1473
    br_to.update_revisions(branch_from, stop_revision=revno)
 
1474
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
 
1475
          check_clean=False, ignore_zero=True)
 
1476
    
 
1477
    from_location = pull_loc(branch_from)
 
1478
    br_to.set_parent(pull_loc(branch_from))
 
1479