~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-08-26 00:10:48 UTC
  • Revision ID: mbp@sourcefrog.net-20050826001048-b84148d3ef567d0d
- fix bzr.dev branch url in tutorial
  thanks to madduck

Show diffs side-by-side

added added

removed removed

Lines of Context:
17
17
 
18
18
import sys
19
19
import os
20
 
from cStringIO import StringIO
21
20
 
22
21
import bzrlib
23
22
from bzrlib.trace import mutter, note
25
24
     splitpath, \
26
25
     sha_file, appendpath, file_kind
27
26
 
28
 
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
29
 
                           NoSuchRevision)
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
 
28
import bzrlib.errors
30
29
from bzrlib.textui import show_status
31
30
from bzrlib.revision import Revision
 
31
from bzrlib.xml import unpack_xml
32
32
from bzrlib.delta import compare_trees
33
33
from bzrlib.tree import EmptyTree, RevisionTree
34
 
from bzrlib.inventory import Inventory
35
 
from bzrlib.weavestore import WeaveStore
36
 
from bzrlib.store import ImmutableStore
37
 
import bzrlib.xml5
38
34
import bzrlib.ui
39
35
 
40
36
 
41
 
INVENTORY_FILEID = '__inventory'
42
 
ANCESTRY_FILEID = '__ancestry'
43
 
 
44
 
 
45
 
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
46
 
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
 
37
 
 
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
47
39
## TODO: Maybe include checks for common corruption of newlines, etc?
48
40
 
49
41
 
50
42
# TODO: Some operations like log might retrieve the same revisions
51
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
52
 
# cache in memory to make this faster.  In general anything can be
53
 
# cached in memory between lock and unlock operations.
 
44
# cache in memory to make this faster.
54
45
 
55
46
# TODO: please move the revision-string syntax stuff out of the branch
56
47
# object; it's clutter
173
164
    _lock_mode = None
174
165
    _lock_count = None
175
166
    _lock = None
176
 
    _inventory_weave = None
177
167
    
178
168
    # Map some sort of prefix into a namespace
179
169
    # stuff like "revno:10", "revid:", etc.
195
185
        In the test suite, creation of new trees is tested using the
196
186
        `ScratchBranch` class.
197
187
        """
 
188
        from bzrlib.store import ImmutableStore
198
189
        if init:
199
190
            self.base = os.path.realpath(base)
200
191
            self._make_control()
209
200
                                      'current bzr can only operate from top-of-tree'])
210
201
        self._check_format()
211
202
 
212
 
        self.weave_store = WeaveStore(self.controlfilename('weaves'))
 
203
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
213
204
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
205
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
214
206
 
215
207
 
216
208
    def __str__(self):
227
219
            self._lock.unlock()
228
220
 
229
221
 
 
222
 
230
223
    def lock_write(self):
231
224
        if self._lock_mode:
232
225
            if self._lock_mode != 'w':
242
235
            self._lock_count = 1
243
236
 
244
237
 
 
238
 
245
239
    def lock_read(self):
246
240
        if self._lock_mode:
247
241
            assert self._lock_mode in ('r', 'w'), \
254
248
            self._lock_mode = 'r'
255
249
            self._lock_count = 1
256
250
                        
 
251
 
 
252
            
257
253
    def unlock(self):
258
254
        if not self._lock_mode:
259
255
            from errors import LockError
266
262
            self._lock = None
267
263
            self._lock_mode = self._lock_count = None
268
264
 
 
265
 
269
266
    def abspath(self, name):
270
267
        """Return absolute filename for something in the branch"""
271
268
        return os.path.join(self.base, name)
272
269
 
 
270
 
273
271
    def relpath(self, path):
274
272
        """Return path relative to this branch of something inside it.
275
273
 
276
274
        Raises an error if path is not in this branch."""
277
275
        return _relpath(self.base, path)
278
276
 
 
277
 
279
278
    def controlfilename(self, file_or_path):
280
279
        """Return location relative to branch."""
281
280
        if isinstance(file_or_path, basestring):
308
307
        else:
309
308
            raise BzrError("invalid controlfile mode %r" % mode)
310
309
 
 
310
 
 
311
 
311
312
    def _make_control(self):
 
313
        from bzrlib.inventory import Inventory
 
314
        from bzrlib.xml import pack_xml
 
315
        
312
316
        os.mkdir(self.controlfilename([]))
313
317
        self.controlfile('README', 'w').write(
314
318
            "This is a Bazaar-NG control directory.\n"
315
319
            "Do not change any files in this directory.\n")
316
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
317
 
        for d in ('text-store', 'revision-store',
318
 
                  'weaves'):
 
320
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
321
        for d in ('text-store', 'inventory-store', 'revision-store'):
319
322
            os.mkdir(self.controlfilename(d))
320
323
        for f in ('revision-history', 'merged-patches',
321
324
                  'pending-merged-patches', 'branch-name',
327
330
        # if we want per-tree root ids then this is the place to set
328
331
        # them; they're not needed for now and so ommitted for
329
332
        # simplicity.
330
 
        f = self.controlfile('inventory','w')
331
 
        bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
332
 
        
 
333
        pack_xml(Inventory(), self.controlfile('inventory','w'))
333
334
 
334
335
 
335
336
    def _check_format(self):
336
337
        """Check this branch format is supported.
337
338
 
338
 
        The format level is stored, as an integer, in
339
 
        self._branch_format for code that needs to check it later.
 
339
        The current tool only supports the current unstable format.
340
340
 
341
341
        In the future, we might need different in-memory Branch
342
342
        classes to support downlevel branches.  But not yet.
343
343
        """
 
344
        # This ignores newlines so that we can open branches created
 
345
        # on Windows from Linux and so on.  I think it might be better
 
346
        # to always make all internal files in unix format.
344
347
        fmt = self.controlfile('branch-format', 'r').read()
345
 
        if fmt == BZR_BRANCH_FORMAT_5:
346
 
            self._branch_format = 5
347
 
        else:
348
 
            raise BzrError('sorry, branch format "%s" not supported; ' 
349
 
                           'use a different bzr version, '
350
 
                           'or run "bzr upgrade", '
351
 
                           'or remove the .bzr directory and "bzr init" again'
352
 
                           % fmt.rstrip('\n\r'))
 
348
        fmt.replace('\r\n', '')
 
349
        if fmt != BZR_BRANCH_FORMAT:
 
350
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
351
                           ['use a different bzr version',
 
352
                            'or remove the .bzr directory and "bzr init" again'])
353
353
 
354
354
    def get_root_id(self):
355
355
        """Return the id of this branches root"""
370
370
 
371
371
    def read_working_inventory(self):
372
372
        """Read the working inventory."""
 
373
        from bzrlib.inventory import Inventory
 
374
        from bzrlib.xml import unpack_xml
 
375
        from time import time
 
376
        before = time()
373
377
        self.lock_read()
374
378
        try:
375
379
            # ElementTree does its own conversion from UTF-8, so open in
376
380
            # binary.
377
 
            f = self.controlfile('inventory', 'rb')
378
 
            return bzrlib.xml5.serializer_v5.read_inventory(f)
 
381
            inv = unpack_xml(Inventory,
 
382
                             self.controlfile('inventory', 'rb'))
 
383
            mutter("loaded inventory of %d items in %f"
 
384
                   % (len(inv), time() - before))
 
385
            return inv
379
386
        finally:
380
387
            self.unlock()
381
388
            
387
394
        will be committed to the next revision.
388
395
        """
389
396
        from bzrlib.atomicfile import AtomicFile
 
397
        from bzrlib.xml import pack_xml
390
398
        
391
399
        self.lock_write()
392
400
        try:
393
401
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
394
402
            try:
395
 
                bzrlib.xml5.serializer_v5.write_inventory(inv, f)
 
403
                pack_xml(inv, f)
396
404
                f.commit()
397
405
            finally:
398
406
                f.close()
406
414
                         """Inventory for the working copy.""")
407
415
 
408
416
 
409
 
    def add(self, files, ids=None):
 
417
    def add(self, files, verbose=False, ids=None):
410
418
        """Make files versioned.
411
419
 
412
 
        Note that the command line normally calls smart_add instead,
413
 
        which can automatically recurse.
 
420
        Note that the command line normally calls smart_add instead.
414
421
 
415
422
        This puts the files in the Added state, so that they will be
416
423
        recorded by the next commit.
426
433
        TODO: Perhaps have an option to add the ids even if the files do
427
434
              not (yet) exist.
428
435
 
429
 
        TODO: Perhaps yield the ids and paths as they're added.
 
436
        TODO: Perhaps return the ids of the files?  But then again it
 
437
              is easy to retrieve them if they're needed.
 
438
 
 
439
        TODO: Adding a directory should optionally recurse down and
 
440
              add all non-ignored children.  Perhaps do that in a
 
441
              higher-level method.
430
442
        """
431
443
        # TODO: Re-adding a file that is removed in the working copy
432
444
        # should probably put it back with the previous ID.
468
480
                    file_id = gen_file_id(f)
469
481
                inv.add_path(f, kind=kind, file_id=file_id)
470
482
 
 
483
                if verbose:
 
484
                    print 'added', quotefn(f)
 
485
 
471
486
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
472
487
 
473
488
            self._write_inventory(inv)
583
598
            f.close()
584
599
 
585
600
 
586
 
    def get_revision_xml_file(self, revision_id):
 
601
    def get_revision_xml(self, revision_id):
587
602
        """Return XML file object for revision object."""
588
603
        if not revision_id or not isinstance(revision_id, basestring):
589
604
            raise InvalidRevisionId(revision_id)
598
613
            self.unlock()
599
614
 
600
615
 
601
 
    #deprecated
602
 
    get_revision_xml = get_revision_xml_file
603
 
 
604
 
 
605
616
    def get_revision(self, revision_id):
606
617
        """Return the Revision object for a named revision"""
607
 
        xml_file = self.get_revision_xml_file(revision_id)
 
618
        xml_file = self.get_revision_xml(revision_id)
608
619
 
609
620
        try:
610
 
            r = bzrlib.xml5.serializer_v5.read_revision(xml_file)
 
621
            r = unpack_xml(Revision, xml_file)
611
622
        except SyntaxError, e:
612
623
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
613
624
                                         [revision_id,
651
662
        return bzrlib.osutils.sha_file(self.get_revision_xml(revision_id))
652
663
 
653
664
 
654
 
    def get_ancestry(self, revision_id):
655
 
        """Return a list of revision-ids integrated by a revision.
656
 
        """
657
 
        w = self.weave_store.get_weave(ANCESTRY_FILEID)
658
 
        # strip newlines
659
 
        return [l[:-1] for l in w.get_iter(w.lookup(revision_id))]
660
 
 
661
 
 
662
 
    def get_inventory_weave(self):
663
 
        return self.weave_store.get_weave(INVENTORY_FILEID)
664
 
 
665
 
 
666
 
    def get_inventory(self, revision_id):
667
 
        """Get Inventory object by hash."""
668
 
        # FIXME: The text gets passed around a lot coming from the weave.
669
 
        f = StringIO(self.get_inventory_xml(revision_id))
670
 
        return bzrlib.xml5.serializer_v5.read_inventory(f)
671
 
 
672
 
 
673
 
    def get_inventory_xml(self, revision_id):
 
665
    def get_inventory(self, inventory_id):
 
666
        """Get Inventory object by hash.
 
667
 
 
668
        TODO: Perhaps for this and similar methods, take a revision
 
669
               parameter which can be either an integer revno or a
 
670
               string hash."""
 
671
        from bzrlib.inventory import Inventory
 
672
        from bzrlib.xml import unpack_xml
 
673
 
 
674
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
 
675
 
 
676
 
 
677
    def get_inventory_xml(self, inventory_id):
674
678
        """Get inventory XML as a file object."""
675
 
        try:
676
 
            assert isinstance(revision_id, basestring), type(revision_id)
677
 
            iw = self.get_inventory_weave()
678
 
            return iw.get_text(iw.lookup(revision_id))
679
 
        except IndexError:
680
 
            raise bzrlib.errors.HistoryMissing(self, 'inventory', revision_id)
681
 
 
682
 
 
683
 
    def get_inventory_sha1(self, revision_id):
 
679
        return self.inventory_store[inventory_id]
 
680
            
 
681
 
 
682
    def get_inventory_sha1(self, inventory_id):
684
683
        """Return the sha1 hash of the inventory entry
685
684
        """
686
 
        return self.get_revision(revision_id).inventory_sha1
 
685
        return sha_file(self.get_inventory_xml(inventory_id))
687
686
 
688
687
 
689
688
    def get_revision_inventory(self, revision_id):
690
689
        """Return inventory of a past revision."""
691
 
        # bzr 0.0.6 and later imposes the constraint that the inventory_id
 
690
        # bzr 0.0.6 imposes the constraint that the inventory_id
692
691
        # must be the same as its revision, so this is trivial.
693
692
        if revision_id == None:
 
693
            from bzrlib.inventory import Inventory
694
694
            return Inventory(self.get_root_id())
695
695
        else:
696
696
            return self.get_inventory(revision_id)
716
716
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
717
717
        >>> sb.common_ancestor(sb) == (None, None)
718
718
        True
719
 
        >>> commit.commit(sb, "Committing first revision")
 
719
        >>> commit.commit(sb, "Committing first revision", verbose=False)
720
720
        >>> sb.common_ancestor(sb)[0]
721
721
        1
722
722
        >>> clone = sb.clone()
723
 
        >>> commit.commit(sb, "Committing second revision")
 
723
        >>> commit.commit(sb, "Committing second revision", verbose=False)
724
724
        >>> sb.common_ancestor(sb)[0]
725
725
        2
726
726
        >>> sb.common_ancestor(clone)[0]
727
727
        1
728
 
        >>> commit.commit(clone, "Committing divergent second revision")
 
728
        >>> commit.commit(clone, "Committing divergent second revision", 
 
729
        ...               verbose=False)
729
730
        >>> sb.common_ancestor(clone)[0]
730
731
        1
731
732
        >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
835
836
        ## note("Added %d revisions." % count)
836
837
        pb.clear()
837
838
 
 
839
        
 
840
        
 
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"):
 
845
            inventory_ids = [other.get_revision(r).inventory_id
 
846
                             for r in revision_ids]
 
847
            other.inventory_store.prefetch(inventory_ids)
 
848
 
 
849
        if pb is None:
 
850
            pb = bzrlib.ui.ui_factory.progress_bar()
 
851
                
 
852
        revisions = []
 
853
        needed_texts = set()
 
854
        i = 0
 
855
 
 
856
        failures = set()
 
857
        for i, rev_id in enumerate(revision_ids):
 
858
            pb.update('fetching revision', i+1, len(revision_ids))
 
859
            try:
 
860
                rev = other.get_revision(rev_id)
 
861
            except bzrlib.errors.NoSuchRevision:
 
862
                failures.add(rev_id)
 
863
                continue
 
864
 
 
865
            revisions.append(rev)
 
866
            inv = other.get_inventory(str(rev.inventory_id))
 
867
            for key, entry in inv.iter_entries():
 
868
                if entry.text_id is None:
 
869
                    continue
 
870
                if entry.text_id not in self.text_store:
 
871
                    needed_texts.add(entry.text_id)
 
872
 
 
873
        pb.clear()
 
874
                    
 
875
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
 
876
                                                    needed_texts)
 
877
        #print "Added %d texts." % count 
 
878
        inventory_ids = [ f.inventory_id for f in revisions ]
 
879
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
880
                                                         inventory_ids)
 
881
        #print "Added %d inventories." % count 
 
882
        revision_ids = [ f.revision_id for f in revisions]
 
883
 
 
884
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
 
885
                                                          revision_ids,
 
886
                                                          permit_failure=True)
 
887
        assert len(cp_fail) == 0 
 
888
        return count, failures
 
889
       
838
890
 
839
891
    def commit(self, *args, **kw):
840
 
        from bzrlib.commit import Commit
841
 
        Commit().commit(self, *args, **kw)
 
892
        from bzrlib.commit import commit
 
893
        commit(self, *args, **kw)
842
894
        
843
895
 
844
896
    def lookup_revision(self, revision):
845
897
        """Return the revision identifier for a given revision information."""
846
 
        revno, info = self._get_revision_info(revision)
 
898
        revno, info = self.get_revision_info(revision)
847
899
        return info
848
900
 
849
901
 
864
916
        revision can also be a string, in which case it is parsed for something like
865
917
            'date:' or 'revid:' etc.
866
918
        """
867
 
        revno, rev_id = self._get_revision_info(revision)
868
 
        if revno is None:
869
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
870
 
        return revno, rev_id
871
 
 
872
 
    def get_rev_id(self, revno, history=None):
873
 
        """Find the revision id of the specified revno."""
874
 
        if revno == 0:
875
 
            return None
876
 
        if history is None:
877
 
            history = self.revision_history()
878
 
        elif revno <= 0 or revno > len(history):
879
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
880
 
        return history[revno - 1]
881
 
 
882
 
    def _get_revision_info(self, revision):
883
 
        """Return (revno, revision id) for revision specifier.
884
 
 
885
 
        revision can be an integer, in which case it is assumed to be revno
886
 
        (though this will translate negative values into positive ones)
887
 
        revision can also be a string, in which case it is parsed for something
888
 
        like 'date:' or 'revid:' etc.
889
 
 
890
 
        A revid is always returned.  If it is None, the specifier referred to
891
 
        the null revision.  If the revid does not occur in the revision
892
 
        history, revno will be None.
893
 
        """
894
 
        
895
919
        if revision is None:
896
920
            return 0, None
897
921
        revno = None
901
925
            pass
902
926
        revs = self.revision_history()
903
927
        if isinstance(revision, int):
 
928
            if revision == 0:
 
929
                return 0, None
 
930
            # Mabye we should do this first, but we don't need it if revision == 0
904
931
            if revision < 0:
905
932
                revno = len(revs) + revision + 1
906
933
            else:
907
934
                revno = revision
908
 
            rev_id = self.get_rev_id(revno, revs)
909
935
        elif isinstance(revision, basestring):
910
936
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
911
937
                if revision.startswith(prefix):
912
 
                    result = func(self, revs, revision)
913
 
                    if len(result) > 1:
914
 
                        revno, rev_id = result
915
 
                    else:
916
 
                        revno = result[0]
917
 
                        rev_id = self.get_rev_id(revno, revs)
 
938
                    revno = func(self, revs, revision)
918
939
                    break
919
940
            else:
920
 
                raise BzrError('No namespace registered for string: %r' %
921
 
                               revision)
922
 
        else:
923
 
            raise TypeError('Unhandled revision type %s' % revision)
 
941
                raise BzrError('No namespace registered for string: %r' % revision)
924
942
 
925
 
        if revno is None:
926
 
            if rev_id is None:
927
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
928
 
        return revno, rev_id
 
943
        if revno is None or revno <= 0 or revno > len(revs):
 
944
            raise BzrError("no such revision %s" % revision)
 
945
        return revno, revs[revno-1]
929
946
 
930
947
    def _namespace_revno(self, revs, revision):
931
948
        """Lookup a revision by revision number"""
932
949
        assert revision.startswith('revno:')
933
950
        try:
934
 
            return (int(revision[6:]),)
 
951
            return int(revision[6:])
935
952
        except ValueError:
936
953
            return None
937
954
    REVISION_NAMESPACES['revno:'] = _namespace_revno
938
955
 
939
956
    def _namespace_revid(self, revs, revision):
940
957
        assert revision.startswith('revid:')
941
 
        rev_id = revision[len('revid:'):]
942
958
        try:
943
 
            return revs.index(rev_id) + 1, rev_id
 
959
            return revs.index(revision[6:]) + 1
944
960
        except ValueError:
945
 
            return None, rev_id
 
961
            return None
946
962
    REVISION_NAMESPACES['revid:'] = _namespace_revid
947
963
 
948
964
    def _namespace_last(self, revs, revision):
950
966
        try:
951
967
            offset = int(revision[5:])
952
968
        except ValueError:
953
 
            return (None,)
 
969
            return None
954
970
        else:
955
971
            if offset <= 0:
956
972
                raise BzrError('You must supply a positive value for --revision last:XXX')
957
 
            return (len(revs) - offset + 1,)
 
973
            return len(revs) - offset + 1
958
974
    REVISION_NAMESPACES['last:'] = _namespace_last
959
975
 
960
976
    def _namespace_tag(self, revs, revision):
1035
1051
                # TODO: Handle timezone.
1036
1052
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1037
1053
                if first >= dt and (last is None or dt >= last):
1038
 
                    return (i+1,)
 
1054
                    return i+1
1039
1055
        else:
1040
1056
            for i in range(len(revs)):
1041
1057
                r = self.get_revision(revs[i])
1042
1058
                # TODO: Handle timezone.
1043
1059
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1044
1060
                if first <= dt and (last is None or dt <= last):
1045
 
                    return (i+1,)
 
1061
                    return i+1
1046
1062
    REVISION_NAMESPACES['date:'] = _namespace_date
1047
1063
 
1048
1064
    def revision_tree(self, revision_id):
1056
1072
            return EmptyTree()
1057
1073
        else:
1058
1074
            inv = self.get_revision_inventory(revision_id)
1059
 
            return RevisionTree(self.weave_store, inv, revision_id)
 
1075
            return RevisionTree(self.text_store, inv)
1060
1076
 
1061
1077
 
1062
1078
    def working_tree(self):
1070
1086
 
1071
1087
        If there are no revisions yet, return an `EmptyTree`.
1072
1088
        """
1073
 
        return self.revision_tree(self.last_patch())
 
1089
        r = self.last_patch()
 
1090
        if r == None:
 
1091
            return EmptyTree()
 
1092
        else:
 
1093
            return RevisionTree(self.text_store, self.get_revision_inventory(r))
 
1094
 
1074
1095
 
1075
1096
 
1076
1097
    def rename_one(self, from_rel, to_rel):
1108
1129
 
1109
1130
            inv.rename(file_id, to_dir_id, to_tail)
1110
1131
 
 
1132
            print "%s => %s" % (from_rel, to_rel)
 
1133
 
1111
1134
            from_abs = self.abspath(from_rel)
1112
1135
            to_abs = self.abspath(to_rel)
1113
1136
            try:
1132
1155
 
1133
1156
        Note that to_name is only the last component of the new name;
1134
1157
        this doesn't change the directory.
1135
 
 
1136
 
        This returns a list of (from_path, to_path) pairs for each
1137
 
        entry that is moved.
1138
1158
        """
1139
 
        result = []
1140
1159
        self.lock_write()
1141
1160
        try:
1142
1161
            ## TODO: Option to move IDs only
1177
1196
            for f in from_paths:
1178
1197
                name_tail = splitpath(f)[-1]
1179
1198
                dest_path = appendpath(to_name, name_tail)
1180
 
                result.append((f, dest_path))
 
1199
                print "%s => %s" % (f, dest_path)
1181
1200
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1182
1201
                try:
1183
1202
                    os.rename(self.abspath(f), self.abspath(dest_path))
1189
1208
        finally:
1190
1209
            self.unlock()
1191
1210
 
1192
 
        return result
1193
 
 
1194
1211
 
1195
1212
    def revert(self, filenames, old_tree=None, backups=True):
1196
1213
        """Restore selected files to the versions from a previous tree.
1278
1295
            self.unlock()
1279
1296
 
1280
1297
 
1281
 
    def get_parent(self):
1282
 
        """Return the parent location of the branch.
1283
 
 
1284
 
        This is the default location for push/pull/missing.  The usual
1285
 
        pattern is that the user can override it by specifying a
1286
 
        location.
1287
 
        """
1288
 
        import errno
1289
 
        _locs = ['parent', 'pull', 'x-pull']
1290
 
        for l in _locs:
1291
 
            try:
1292
 
                return self.controlfile(l, 'r').read().strip('\n')
1293
 
            except IOError, e:
1294
 
                if e.errno != errno.ENOENT:
1295
 
                    raise
1296
 
        return None
1297
 
 
1298
 
 
1299
 
    def set_parent(self, url):
1300
 
        # TODO: Maybe delete old location files?
1301
 
        from bzrlib.atomicfile import AtomicFile
1302
 
        self.lock_write()
1303
 
        try:
1304
 
            f = AtomicFile(self.controlfilename('parent'))
1305
 
            try:
1306
 
                f.write(url + '\n')
1307
 
                f.commit()
1308
 
            finally:
1309
 
                f.close()
1310
 
        finally:
1311
 
            self.unlock()
1312
 
 
1313
 
    def check_revno(self, revno):
1314
 
        """\
1315
 
        Check whether a revno corresponds to any revision.
1316
 
        Zero (the NULL revision) is considered valid.
1317
 
        """
1318
 
        if revno != 0:
1319
 
            self.check_real_revno(revno)
1320
 
            
1321
 
    def check_real_revno(self, revno):
1322
 
        """\
1323
 
        Check whether a revno corresponds to a real revision.
1324
 
        Zero (the NULL revision) is considered invalid
1325
 
        """
1326
 
        if revno < 1 or revno > self.revno():
1327
 
            raise InvalidRevisionNumber(revno)
1328
 
        
1329
 
        
1330
 
 
1331
1298
 
1332
1299
class ScratchBranch(Branch):
1333
1300
    """Special test class: a branch that cleans up after itself.
1375
1342
        os.rmdir(base)
1376
1343
        copytree(self.base, base, symlinks=True)
1377
1344
        return ScratchBranch(base=base)
1378
 
 
1379
 
 
1380
1345
        
1381
1346
    def __del__(self):
1382
1347
        self.destroy()
1452
1417
    """Return a new tree-root file id."""
1453
1418
    return gen_file_id('TREE_ROOT')
1454
1419
 
1455
 
 
1456
 
def pull_loc(branch):
1457
 
    # TODO: Should perhaps just make attribute be 'base' in
1458
 
    # RemoteBranch and Branch?
1459
 
    if hasattr(branch, "baseurl"):
1460
 
        return branch.baseurl
1461
 
    else:
1462
 
        return branch.base
1463
 
 
1464
 
 
1465
 
def copy_branch(branch_from, to_location, revision=None):
1466
 
    """Copy branch_from into the existing directory to_location.
1467
 
 
1468
 
    revision
1469
 
        If not None, only revisions up to this point will be copied.
1470
 
        The head of the new branch will be that revision.
1471
 
 
1472
 
    to_location
1473
 
        The name of a local directory that exists but is empty.
1474
 
    """
1475
 
    from bzrlib.merge import merge
1476
 
    from bzrlib.branch import Branch
1477
 
 
1478
 
    assert isinstance(branch_from, Branch)
1479
 
    assert isinstance(to_location, basestring)
1480
 
    
1481
 
    br_to = Branch(to_location, init=True)
1482
 
    br_to.set_root_id(branch_from.get_root_id())
1483
 
    if revision is None:
1484
 
        revno = branch_from.revno()
1485
 
    else:
1486
 
        revno, rev_id = branch_from.get_revision_info(revision)
1487
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1488
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1489
 
          check_clean=False, ignore_zero=True)
1490
 
    
1491
 
    from_location = pull_loc(branch_from)
1492
 
    br_to.set_parent(pull_loc(branch_from))
1493