~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-05-30 02:02:37 UTC
  • Revision ID: mbp@sourcefrog.net-20050530020237-50fb99e7a9c1576b
todo

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
     joinpath, sha_string, file_kind, local_time_offset, appendpath
30
30
from store import ImmutableStore
31
31
from revision import Revision
32
 
from errors import bailout, BzrError
 
32
from errors import BzrError
33
33
from textui import show_status
34
34
 
35
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
43
43
        return remotebranch.RemoteBranch(f, **args)
44
44
    else:
45
45
        return Branch(f, **args)
 
46
 
 
47
 
 
48
 
 
49
def with_writelock(method):
 
50
    """Method decorator for functions run with the branch locked."""
 
51
    def d(self, *a, **k):
 
52
        # called with self set to the branch
 
53
        self.lock('w')
 
54
        try:
 
55
            return method(self, *a, **k)
 
56
        finally:
 
57
            self.unlock()
 
58
    return d
 
59
 
 
60
 
 
61
def with_readlock(method):
 
62
    def d(self, *a, **k):
 
63
        self.lock('r')
 
64
        try:
 
65
            return method(self, *a, **k)
 
66
        finally:
 
67
            self.unlock()
 
68
    return d
46
69
        
47
70
 
48
71
def find_branch_root(f=None):
85
108
 
86
109
    base
87
110
        Base directory of the branch.
 
111
 
 
112
    _lock_mode
 
113
        None, or 'r' or 'w'
 
114
 
 
115
    _lock_count
 
116
        If _lock_mode is true, a positive count of the number of times the
 
117
        lock has been taken.
 
118
 
 
119
    _lockfile
 
120
        Open file used for locking.
88
121
    """
89
 
    _lockmode = None
 
122
    base = None
 
123
    _lock_mode = None
 
124
    _lock_count = None
90
125
    
91
 
    def __init__(self, base, init=False, find_root=True, lock_mode='w'):
 
126
    def __init__(self, base, init=False, find_root=True):
92
127
        """Create new branch object at a particular location.
93
128
 
94
129
        base -- Base directory for the branch.
111
146
        else:
112
147
            self.base = os.path.realpath(base)
113
148
            if not isdir(self.controlfilename('.')):
114
 
                bailout("not a bzr branch: %s" % quotefn(base),
115
 
                        ['use "bzr init" to initialize a new working tree',
116
 
                         'current bzr can only operate from top-of-tree'])
 
149
                from errors import NotBranchError
 
150
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
 
151
                                     ['use "bzr init" to initialize a new working tree',
 
152
                                      'current bzr can only operate from top-of-tree'])
117
153
        self._check_format()
118
 
        self.lock(lock_mode)
 
154
        self._lockfile = self.controlfile('branch-lock', 'wb')
119
155
 
120
156
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
121
157
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
129
165
    __repr__ = __str__
130
166
 
131
167
 
132
 
 
133
 
    def lock(self, mode='w'):
134
 
        """Lock the on-disk branch, excluding other processes."""
135
 
        try:
136
 
            import fcntl, errno
137
 
 
138
 
            if mode == 'w':
139
 
                lm = fcntl.LOCK_EX
140
 
                om = os.O_WRONLY | os.O_CREAT
141
 
            elif mode == 'r':
142
 
                lm = fcntl.LOCK_SH
143
 
                om = os.O_RDONLY
 
168
    def __del__(self):
 
169
        if self._lock_mode:
 
170
            from warnings import warn
 
171
            warn("branch %r was not explicitly unlocked" % self)
 
172
            self.unlock()
 
173
 
 
174
 
 
175
    def lock(self, mode):
 
176
        if self._lock_mode:
 
177
            if mode == 'w' and cur_lm == 'r':
 
178
                raise BzrError("can't upgrade to a write lock")
 
179
            
 
180
            assert self._lock_count >= 1
 
181
            self._lock_count += 1
 
182
        else:
 
183
            from bzrlib.lock import lock, LOCK_SH, LOCK_EX
 
184
            if mode == 'r':
 
185
                m = LOCK_SH
 
186
            elif mode == 'w':
 
187
                m = LOCK_EX
144
188
            else:
145
 
                raise BzrError("invalid locking mode %r" % mode)
146
 
 
147
 
            try:
148
 
                lockfile = os.open(self.controlfilename('branch-lock'), om)
149
 
            except OSError, e:
150
 
                if e.errno == errno.ENOENT:
151
 
                    # might not exist on branches from <0.0.4
152
 
                    self.controlfile('branch-lock', 'w').close()
153
 
                    lockfile = os.open(self.controlfilename('branch-lock'), om)
154
 
                else:
155
 
                    raise e
156
 
            
157
 
            fcntl.lockf(lockfile, lm)
158
 
            def unlock():
159
 
                fcntl.lockf(lockfile, fcntl.LOCK_UN)
160
 
                os.close(lockfile)
161
 
                self._lockmode = None
162
 
            self.unlock = unlock
163
 
            self._lockmode = mode
164
 
        except ImportError:
165
 
            warning("please write a locking method for platform %r" % sys.platform)
166
 
            def unlock():
167
 
                self._lockmode = None
168
 
            self.unlock = unlock
169
 
            self._lockmode = mode
170
 
 
171
 
 
172
 
    def _need_readlock(self):
173
 
        if self._lockmode not in ['r', 'w']:
174
 
            raise BzrError('need read lock on branch, only have %r' % self._lockmode)
175
 
 
176
 
    def _need_writelock(self):
177
 
        if self._lockmode not in ['w']:
178
 
            raise BzrError('need write lock on branch, only have %r' % self._lockmode)
 
189
                raise ValueError('invalid lock mode %r' % mode)
 
190
 
 
191
            lock(self._lockfile, m)
 
192
            self._lock_mode = mode
 
193
            self._lock_count = 1
 
194
 
 
195
 
 
196
    def unlock(self):
 
197
        if not self._lock_mode:
 
198
            raise BzrError('branch %r is not locked' % (self))
 
199
 
 
200
        if self._lock_count > 1:
 
201
            self._lock_count -= 1
 
202
        else:
 
203
            assert self._lock_count == 1
 
204
            from bzrlib.lock import unlock
 
205
            unlock(self._lockfile)
 
206
            self._lock_mode = self._lock_count = None
179
207
 
180
208
 
181
209
    def abspath(self, name):
190
218
        rp = os.path.realpath(path)
191
219
        # FIXME: windows
192
220
        if not rp.startswith(self.base):
193
 
            bailout("path %r is not within branch %r" % (rp, self.base))
 
221
            from errors import NotBranchError
 
222
            raise NotBranchError("path %r is not within branch %r" % (rp, self.base))
194
223
        rp = rp[len(self.base):]
195
224
        rp = rp.lstrip(os.sep)
196
225
        return rp
260
289
        fmt = self.controlfile('branch-format', 'r').read()
261
290
        fmt.replace('\r\n', '')
262
291
        if fmt != BZR_BRANCH_FORMAT:
263
 
            bailout('sorry, branch format %r not supported' % fmt,
264
 
                    ['use a different bzr version',
265
 
                     'or remove the .bzr directory and "bzr init" again'])
266
 
 
267
 
 
 
292
            raise BzrError('sorry, branch format %r not supported' % fmt,
 
293
                           ['use a different bzr version',
 
294
                            'or remove the .bzr directory and "bzr init" again'])
 
295
 
 
296
 
 
297
 
 
298
    @with_readlock
268
299
    def read_working_inventory(self):
269
300
        """Read the working inventory."""
270
 
        self._need_readlock()
271
301
        before = time.time()
272
302
        # ElementTree does its own conversion from UTF-8, so open in
273
303
        # binary.
275
305
        mutter("loaded inventory of %d items in %f"
276
306
               % (len(inv), time.time() - before))
277
307
        return inv
278
 
 
 
308
            
279
309
 
280
310
    def _write_inventory(self, inv):
281
311
        """Update the working inventory.
283
313
        That is to say, the inventory describing changes underway, that
284
314
        will be committed to the next revision.
285
315
        """
286
 
        self._need_writelock()
287
316
        ## TODO: factor out to atomicfile?  is rename safe on windows?
288
317
        ## TODO: Maybe some kind of clean/dirty marker on inventory?
289
318
        tmpfname = self.controlfilename('inventory.tmp')
295
324
            os.remove(inv_fname)
296
325
        os.rename(tmpfname, inv_fname)
297
326
        mutter('wrote working inventory')
298
 
 
 
327
            
299
328
 
300
329
    inventory = property(read_working_inventory, _write_inventory, None,
301
330
                         """Inventory for the working copy.""")
302
331
 
303
332
 
 
333
    @with_writelock
304
334
    def add(self, files, verbose=False, ids=None):
305
335
        """Make files versioned.
306
336
 
321
351
               add all non-ignored children.  Perhaps do that in a
322
352
               higher-level method.
323
353
        """
324
 
        self._need_writelock()
325
 
 
326
354
        # TODO: Re-adding a file that is removed in the working copy
327
355
        # should probably put it back with the previous ID.
328
356
        if isinstance(files, types.StringTypes):
335
363
            ids = [None] * len(files)
336
364
        else:
337
365
            assert(len(ids) == len(files))
338
 
        
 
366
 
339
367
        inv = self.read_working_inventory()
340
368
        for f,file_id in zip(files, ids):
341
369
            if is_control_file(f):
342
 
                bailout("cannot add control file %s" % quotefn(f))
 
370
                raise BzrError("cannot add control file %s" % quotefn(f))
343
371
 
344
372
            fp = splitpath(f)
345
373
 
346
374
            if len(fp) == 0:
347
 
                bailout("cannot add top-level %r" % f)
348
 
                
 
375
                raise BzrError("cannot add top-level %r" % f)
 
376
 
349
377
            fullpath = os.path.normpath(self.abspath(f))
350
378
 
351
379
            try:
352
380
                kind = file_kind(fullpath)
353
381
            except OSError:
354
382
                # maybe something better?
355
 
                bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
356
 
            
 
383
                raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
 
384
 
357
385
            if kind != 'file' and kind != 'directory':
358
 
                bailout('cannot add: not a regular file or directory: %s' % quotefn(f))
 
386
                raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
359
387
 
360
388
            if file_id is None:
361
389
                file_id = gen_file_id(f)
363
391
 
364
392
            if verbose:
365
393
                show_status('A', kind, quotefn(f))
366
 
                
 
394
 
367
395
            mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
 
396
 
 
397
        self._write_inventory(inv)
368
398
            
369
 
        self._write_inventory(inv)
370
 
 
371
399
 
372
400
    def print_file(self, file, revno):
373
401
        """Print `file` to stdout."""
374
 
        self._need_readlock()
375
402
        tree = self.revision_tree(self.lookup_revision(revno))
376
403
        # use inventory as it was in that revision
377
404
        file_id = tree.inventory.path2id(file)
378
405
        if not file_id:
379
 
            bailout("%r is not present in revision %d" % (file, revno))
 
406
            raise BzrError("%r is not present in revision %d" % (file, revno))
380
407
        tree.print_file(file_id)
381
 
        
382
 
 
 
408
 
 
409
 
 
410
    @with_writelock
383
411
    def remove(self, files, verbose=False):
384
412
        """Mark nominated files for removal from the inventory.
385
413
 
396
424
        """
397
425
        ## TODO: Normalize names
398
426
        ## TODO: Remove nested loops; better scalability
399
 
        self._need_writelock()
400
 
 
401
427
        if isinstance(files, types.StringTypes):
402
428
            files = [files]
403
 
        
 
429
 
404
430
        tree = self.working_tree()
405
431
        inv = tree.inventory
406
432
 
408
434
        for f in files:
409
435
            fid = inv.path2id(f)
410
436
            if not fid:
411
 
                bailout("cannot remove unversioned file %s" % quotefn(f))
 
437
                raise BzrError("cannot remove unversioned file %s" % quotefn(f))
412
438
            mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
413
439
            if verbose:
414
440
                # having remove it, it must be either ignored or unknown
421
447
 
422
448
        self._write_inventory(inv)
423
449
 
 
450
 
424
451
    def set_inventory(self, new_inventory_list):
425
452
        inv = Inventory()
426
453
        for path, file_id, parent, kind in new_inventory_list:
471
498
 
472
499
    def get_revision(self, revision_id):
473
500
        """Return the Revision object for a named revision"""
474
 
        self._need_readlock()
475
501
        r = Revision.read_xml(self.revision_store[revision_id])
476
502
        assert r.revision_id == revision_id
477
503
        return r
483
509
        TODO: Perhaps for this and similar methods, take a revision
484
510
               parameter which can be either an integer revno or a
485
511
               string hash."""
486
 
        self._need_readlock()
487
512
        i = Inventory.read_xml(self.inventory_store[inventory_id])
488
513
        return i
489
514
 
490
515
 
491
516
    def get_revision_inventory(self, revision_id):
492
517
        """Return inventory of a past revision."""
493
 
        self._need_readlock()
494
518
        if revision_id == None:
495
519
            return Inventory()
496
520
        else:
497
521
            return self.get_inventory(self.get_revision(revision_id).inventory_id)
498
522
 
499
523
 
 
524
    @with_readlock
500
525
    def revision_history(self):
501
526
        """Return sequence of revision hashes on to this branch.
502
527
 
503
528
        >>> ScratchBranch().revision_history()
504
529
        []
505
530
        """
506
 
        self._need_readlock()
507
531
        return [l.rstrip('\r\n') for l in self.controlfile('revision-history', 'r').readlines()]
508
532
 
509
533
 
573
597
        an `EmptyTree` is returned."""
574
598
        # TODO: refactor this to use an existing revision object
575
599
        # so we don't need to read it in twice.
576
 
        self._need_readlock()
577
600
        if revision_id == None:
578
601
            return EmptyTree()
579
602
        else:
600
623
 
601
624
 
602
625
 
 
626
    @with_writelock
603
627
    def rename_one(self, from_rel, to_rel):
604
628
        """Rename one file.
605
629
 
606
630
        This can change the directory or the filename or both.
607
631
        """
608
 
        self._need_writelock()
609
632
        tree = self.working_tree()
610
633
        inv = tree.inventory
611
634
        if not tree.has_filename(from_rel):
612
 
            bailout("can't rename: old working file %r does not exist" % from_rel)
 
635
            raise BzrError("can't rename: old working file %r does not exist" % from_rel)
613
636
        if tree.has_filename(to_rel):
614
 
            bailout("can't rename: new working file %r already exists" % to_rel)
615
 
            
 
637
            raise BzrError("can't rename: new working file %r already exists" % to_rel)
 
638
 
616
639
        file_id = inv.path2id(from_rel)
617
640
        if file_id == None:
618
 
            bailout("can't rename: old name %r is not versioned" % from_rel)
 
641
            raise BzrError("can't rename: old name %r is not versioned" % from_rel)
619
642
 
620
643
        if inv.path2id(to_rel):
621
 
            bailout("can't rename: new name %r is already versioned" % to_rel)
 
644
            raise BzrError("can't rename: new name %r is already versioned" % to_rel)
622
645
 
623
646
        to_dir, to_tail = os.path.split(to_rel)
624
647
        to_dir_id = inv.path2id(to_dir)
625
648
        if to_dir_id == None and to_dir != '':
626
 
            bailout("can't determine destination directory id for %r" % to_dir)
 
649
            raise BzrError("can't determine destination directory id for %r" % to_dir)
627
650
 
628
651
        mutter("rename_one:")
629
652
        mutter("  file_id    {%s}" % file_id)
631
654
        mutter("  to_rel     %r" % to_rel)
632
655
        mutter("  to_dir     %r" % to_dir)
633
656
        mutter("  to_dir_id  {%s}" % to_dir_id)
634
 
            
 
657
 
635
658
        inv.rename(file_id, to_dir_id, to_tail)
636
659
 
637
660
        print "%s => %s" % (from_rel, to_rel)
638
 
        
 
661
 
639
662
        from_abs = self.abspath(from_rel)
640
663
        to_abs = self.abspath(to_rel)
641
664
        try:
642
665
            os.rename(from_abs, to_abs)
643
666
        except OSError, e:
644
 
            bailout("failed to rename %r to %r: %s"
 
667
            raise BzrError("failed to rename %r to %r: %s"
645
668
                    % (from_abs, to_abs, e[1]),
646
669
                    ["rename rolled back"])
647
670
 
648
671
        self._write_inventory(inv)
649
 
            
650
 
 
651
 
 
 
672
 
 
673
 
 
674
 
 
675
    @with_writelock
652
676
    def move(self, from_paths, to_name):
653
677
        """Rename files.
654
678
 
660
684
        Note that to_name is only the last component of the new name;
661
685
        this doesn't change the directory.
662
686
        """
663
 
        self._need_writelock()
664
687
        ## TODO: Option to move IDs only
665
688
        assert not isinstance(from_paths, basestring)
666
689
        tree = self.working_tree()
667
690
        inv = tree.inventory
668
691
        to_abs = self.abspath(to_name)
669
692
        if not isdir(to_abs):
670
 
            bailout("destination %r is not a directory" % to_abs)
 
693
            raise BzrError("destination %r is not a directory" % to_abs)
671
694
        if not tree.has_filename(to_name):
672
 
            bailout("destination %r not in working directory" % to_abs)
 
695
            raise BzrError("destination %r not in working directory" % to_abs)
673
696
        to_dir_id = inv.path2id(to_name)
674
697
        if to_dir_id == None and to_name != '':
675
 
            bailout("destination %r is not a versioned directory" % to_name)
 
698
            raise BzrError("destination %r is not a versioned directory" % to_name)
676
699
        to_dir_ie = inv[to_dir_id]
677
700
        if to_dir_ie.kind not in ('directory', 'root_directory'):
678
 
            bailout("destination %r is not a directory" % to_abs)
 
701
            raise BzrError("destination %r is not a directory" % to_abs)
679
702
 
680
703
        to_idpath = inv.get_idpath(to_dir_id)
681
704
 
682
705
        for f in from_paths:
683
706
            if not tree.has_filename(f):
684
 
                bailout("%r does not exist in working tree" % f)
 
707
                raise BzrError("%r does not exist in working tree" % f)
685
708
            f_id = inv.path2id(f)
686
709
            if f_id == None:
687
 
                bailout("%r is not versioned" % f)
 
710
                raise BzrError("%r is not versioned" % f)
688
711
            name_tail = splitpath(f)[-1]
689
712
            dest_path = appendpath(to_name, name_tail)
690
713
            if tree.has_filename(dest_path):
691
 
                bailout("destination %r already exists" % dest_path)
 
714
                raise BzrError("destination %r already exists" % dest_path)
692
715
            if f_id in to_idpath:
693
 
                bailout("can't move %r to a subdirectory of itself" % f)
 
716
                raise BzrError("can't move %r to a subdirectory of itself" % f)
694
717
 
695
718
        # OK, so there's a race here, it's possible that someone will
696
719
        # create a file in this interval and then the rename might be
704
727
            try:
705
728
                os.rename(self.abspath(f), self.abspath(dest_path))
706
729
            except OSError, e:
707
 
                bailout("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
 
730
                raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
708
731
                        ["rename rolled back"])
709
732
 
710
733
        self._write_inventory(inv)