~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 07:21:18 UTC
  • Revision ID: mbp@sourcefrog.net-20050901072118-f609f9e36f7896d9
- tiny refactoring

Show diffs side-by-side

added added

removed removed

Lines of Context:
23
23
from bzrlib.osutils import isdir, quotefn, compact_date, rand_bytes, \
24
24
     splitpath, \
25
25
     sha_file, appendpath, file_kind
 
26
 
26
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
27
28
import bzrlib.errors
28
29
from bzrlib.textui import show_status
30
31
from bzrlib.xml import unpack_xml
31
32
from bzrlib.delta import compare_trees
32
33
from bzrlib.tree import EmptyTree, RevisionTree
33
 
        
 
34
import bzrlib.ui
 
35
 
 
36
 
 
37
 
34
38
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
35
39
## TODO: Maybe include checks for common corruption of newlines, etc?
36
40
 
39
43
# repeatedly to calculate deltas.  We could perhaps have a weakref
40
44
# cache in memory to make this faster.
41
45
 
 
46
# TODO: please move the revision-string syntax stuff out of the branch
 
47
# object; it's clutter
 
48
 
42
49
 
43
50
def find_branch(f, **args):
44
51
    if f and (f.startswith('http://') or f.startswith('https://')):
101
108
    It is not necessary that f exists.
102
109
 
103
110
    Basically we keep looking up until we find the control directory or
104
 
    run into the root."""
 
111
    run into the root.  If there isn't one, raises NotBranchError.
 
112
    """
105
113
    if f == None:
106
114
        f = os.getcwd()
107
115
    elif hasattr(os.path, 'realpath'):
120
128
        head, tail = os.path.split(f)
121
129
        if head == f:
122
130
            # reached the root, whatever that may be
123
 
            raise BzrError('%r is not in a branch' % orig_f)
 
131
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
124
132
        f = head
125
 
    
 
133
 
 
134
 
 
135
 
 
136
# XXX: move into bzrlib.errors; subclass BzrError    
126
137
class DivergedBranches(Exception):
127
138
    def __init__(self, branch1, branch2):
128
139
        self.branch1 = branch1
208
219
            self._lock.unlock()
209
220
 
210
221
 
211
 
 
212
222
    def lock_write(self):
213
223
        if self._lock_mode:
214
224
            if self._lock_mode != 'w':
224
234
            self._lock_count = 1
225
235
 
226
236
 
227
 
 
228
237
    def lock_read(self):
229
238
        if self._lock_mode:
230
239
            assert self._lock_mode in ('r', 'w'), \
237
246
            self._lock_mode = 'r'
238
247
            self._lock_count = 1
239
248
                        
240
 
 
241
 
            
242
249
    def unlock(self):
243
250
        if not self._lock_mode:
244
251
            from errors import LockError
251
258
            self._lock = None
252
259
            self._lock_mode = self._lock_count = None
253
260
 
254
 
 
255
261
    def abspath(self, name):
256
262
        """Return absolute filename for something in the branch"""
257
263
        return os.path.join(self.base, name)
258
264
 
259
 
 
260
265
    def relpath(self, path):
261
266
        """Return path relative to this branch of something inside it.
262
267
 
263
268
        Raises an error if path is not in this branch."""
264
269
        return _relpath(self.base, path)
265
270
 
266
 
 
267
271
    def controlfilename(self, file_or_path):
268
272
        """Return location relative to branch."""
269
273
        if isinstance(file_or_path, basestring):
296
300
        else:
297
301
            raise BzrError("invalid controlfile mode %r" % mode)
298
302
 
299
 
 
300
 
 
301
303
    def _make_control(self):
302
304
        from bzrlib.inventory import Inventory
303
305
        from bzrlib.xml import pack_xml
321
323
        # simplicity.
322
324
        pack_xml(Inventory(), self.controlfile('inventory','w'))
323
325
 
324
 
 
325
326
    def _check_format(self):
326
327
        """Check this branch format is supported.
327
328
 
403
404
                         """Inventory for the working copy.""")
404
405
 
405
406
 
406
 
    def add(self, files, verbose=False, ids=None):
 
407
    def add(self, files, ids=None):
407
408
        """Make files versioned.
408
409
 
409
 
        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.
410
412
 
411
413
        This puts the files in the Added state, so that they will be
412
414
        recorded by the next commit.
422
424
        TODO: Perhaps have an option to add the ids even if the files do
423
425
              not (yet) exist.
424
426
 
425
 
        TODO: Perhaps return the ids of the files?  But then again it
426
 
              is easy to retrieve them if they're needed.
427
 
 
428
 
        TODO: Adding a directory should optionally recurse down and
429
 
              add all non-ignored children.  Perhaps do that in a
430
 
              higher-level method.
 
427
        TODO: Perhaps yield the ids and paths as they're added.
431
428
        """
432
429
        # TODO: Re-adding a file that is removed in the working copy
433
430
        # should probably put it back with the previous ID.
469
466
                    file_id = gen_file_id(f)
470
467
                inv.add_path(f, kind=kind, file_id=file_id)
471
468
 
472
 
                if verbose:
473
 
                    print 'added', quotefn(f)
474
 
 
475
469
                mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
476
470
 
477
471
            self._write_inventory(inv)
660
654
        from bzrlib.inventory import Inventory
661
655
        from bzrlib.xml import unpack_xml
662
656
 
663
 
        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]
664
663
            
665
664
 
666
665
    def get_inventory_sha1(self, inventory_id):
667
666
        """Return the sha1 hash of the inventory entry
668
667
        """
669
 
        return sha_file(self.inventory_store[inventory_id])
 
668
        return sha_file(self.get_inventory_xml(inventory_id))
670
669
 
671
670
 
672
671
    def get_revision_inventory(self, revision_id):
758
757
            return None
759
758
 
760
759
 
761
 
    def missing_revisions(self, other, stop_revision=None):
 
760
    def missing_revisions(self, other, stop_revision=None, diverged_ok=False):
762
761
        """
763
762
        If self and other have not diverged, return a list of the revisions
764
763
        present in other, but missing from self.
797
796
        if stop_revision is None:
798
797
            stop_revision = other_len
799
798
        elif stop_revision > other_len:
800
 
            raise NoSuchRevision(self, stop_revision)
 
799
            raise bzrlib.errors.NoSuchRevision(self, stop_revision)
801
800
        
802
801
        return other_history[self_len:stop_revision]
803
802
 
804
803
 
805
804
    def update_revisions(self, other, stop_revision=None):
806
805
        """Pull in all new revisions from other branch.
807
 
        
808
 
        >>> from bzrlib.commit import commit
809
 
        >>> bzrlib.trace.silent = True
810
 
        >>> br1 = ScratchBranch(files=['foo', 'bar'])
811
 
        >>> br1.add('foo')
812
 
        >>> br1.add('bar')
813
 
        >>> commit(br1, "lala!", rev_id="REVISION-ID-1", verbose=False)
814
 
        >>> br2 = ScratchBranch()
815
 
        >>> br2.update_revisions(br1)
816
 
        Added 2 texts.
817
 
        Added 1 inventories.
818
 
        Added 1 revisions.
819
 
        >>> br2.revision_history()
820
 
        [u'REVISION-ID-1']
821
 
        >>> br2.update_revisions(br1)
822
 
        Added 0 texts.
823
 
        Added 0 inventories.
824
 
        Added 0 revisions.
825
 
        >>> br1.text_store.total_size() == br2.text_store.total_size()
826
 
        True
827
806
        """
828
 
        from bzrlib.progress import ProgressBar
829
 
 
830
 
        pb = ProgressBar()
831
 
 
 
807
        from bzrlib.fetch import greedy_fetch
 
808
 
 
809
        pb = bzrlib.ui.ui_factory.progress_bar()
832
810
        pb.update('comparing histories')
 
811
 
833
812
        revision_ids = self.missing_revisions(other, stop_revision)
834
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):
835
823
        if hasattr(other.revision_store, "prefetch"):
836
824
            other.revision_store.prefetch(revision_ids)
837
825
        if hasattr(other.inventory_store, "prefetch"):
838
826
            inventory_ids = [other.get_revision(r).inventory_id
839
827
                             for r in revision_ids]
840
828
            other.inventory_store.prefetch(inventory_ids)
 
829
 
 
830
        if pb is None:
 
831
            pb = bzrlib.ui.ui_factory.progress_bar()
841
832
                
842
833
        revisions = []
843
834
        needed_texts = set()
844
835
        i = 0
845
 
        for rev_id in revision_ids:
846
 
            i += 1
847
 
            pb.update('fetching revision', i, len(revision_ids))
848
 
            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
 
849
846
            revisions.append(rev)
850
847
            inv = other.get_inventory(str(rev.inventory_id))
851
848
            for key, entry in inv.iter_entries():
856
853
 
857
854
        pb.clear()
858
855
                    
859
 
        count = self.text_store.copy_multi(other.text_store, needed_texts)
860
 
        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 
861
859
        inventory_ids = [ f.inventory_id for f in revisions ]
862
 
        count = self.inventory_store.copy_multi(other.inventory_store, 
863
 
                                                inventory_ids)
864
 
        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 
865
863
        revision_ids = [ f.revision_id for f in revisions]
866
 
        count = self.revision_store.copy_multi(other.revision_store, 
867
 
                                               revision_ids)
868
 
        for revision_id in revision_ids:
869
 
            self.append_revision(revision_id)
870
 
        print "Added %d revisions." % count
871
 
                    
872
 
        
 
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
 
873
872
    def commit(self, *args, **kw):
874
873
        from bzrlib.commit import commit
875
874
        commit(self, *args, **kw)
880
879
        revno, info = self.get_revision_info(revision)
881
880
        return info
882
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
 
883
892
    def get_revision_info(self, revision):
884
893
        """Return (revno, revision id) for revision identifier.
885
894
 
1101
1110
 
1102
1111
            inv.rename(file_id, to_dir_id, to_tail)
1103
1112
 
1104
 
            print "%s => %s" % (from_rel, to_rel)
1105
 
 
1106
1113
            from_abs = self.abspath(from_rel)
1107
1114
            to_abs = self.abspath(to_rel)
1108
1115
            try:
1127
1134
 
1128
1135
        Note that to_name is only the last component of the new name;
1129
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.
1130
1140
        """
 
1141
        result = []
1131
1142
        self.lock_write()
1132
1143
        try:
1133
1144
            ## TODO: Option to move IDs only
1168
1179
            for f in from_paths:
1169
1180
                name_tail = splitpath(f)[-1]
1170
1181
                dest_path = appendpath(to_name, name_tail)
1171
 
                print "%s => %s" % (f, dest_path)
 
1182
                result.append((f, dest_path))
1172
1183
                inv.rename(inv.path2id(f), to_dir_id, name_tail)
1173
1184
                try:
1174
1185
                    os.rename(self.abspath(f), self.abspath(dest_path))
1180
1191
        finally:
1181
1192
            self.unlock()
1182
1193
 
 
1194
        return result
 
1195
 
1183
1196
 
1184
1197
    def revert(self, filenames, old_tree=None, backups=True):
1185
1198
        """Restore selected files to the versions from a previous tree.
1267
1280
            self.unlock()
1268
1281
 
1269
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
 
1270
1317
 
1271
1318
class ScratchBranch(Branch):
1272
1319
    """Special test class: a branch that cleans up after itself.
1314
1361
        os.rmdir(base)
1315
1362
        copytree(self.base, base, symlinks=True)
1316
1363
        return ScratchBranch(base=base)
 
1364
 
 
1365
 
1317
1366
        
1318
1367
    def __del__(self):
1319
1368
        self.destroy()
1389
1438
    """Return a new tree-root file id."""
1390
1439
    return gen_file_id('TREE_ROOT')
1391
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