~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: Martin Pool
  • Date: 2005-08-29 05:13:54 UTC
  • Revision ID: mbp@sourcefrog.net-20050829051353-656edb4fe57910a6
- better debug log for loading of plugins

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
     splitpath, \
25
25
     sha_file, appendpath, file_kind
26
26
 
27
 
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId, \
28
 
     DivergedBranches, NotBranchError
 
27
from bzrlib.errors import BzrError, InvalidRevisionNumber, InvalidRevisionId
 
28
import bzrlib.errors
29
29
from bzrlib.textui import show_status
30
30
from bzrlib.revision import Revision
 
31
from bzrlib.xml import unpack_xml
31
32
from bzrlib.delta import compare_trees
32
33
from bzrlib.tree import EmptyTree, RevisionTree
33
 
import bzrlib.xml
34
34
import bzrlib.ui
35
35
 
36
36
 
48
48
 
49
49
 
50
50
def find_branch(f, **args):
51
 
    from bzrlib.transport import transport
52
 
    from bzrlib.local_transport import LocalTransport
53
 
    t = transport(f)
54
 
    # FIXME: This is a hack around transport so that
55
 
    #        We can search the local directories for
56
 
    #        a branch root.
57
 
    if args.has_key('init') and args['init']:
58
 
        # Don't search if we are init-ing
59
 
        return Branch(t, **args)
60
 
    if isinstance(t, LocalTransport):
61
 
        root = find_branch_root(f)
62
 
        if root != f:
63
 
            t = transport(root)
64
 
    return Branch(t, **args)
 
51
    if f and (f.startswith('http://') or f.startswith('https://')):
 
52
        import remotebranch 
 
53
        return remotebranch.RemoteBranch(f, **args)
 
54
    else:
 
55
        return Branch(f, **args)
 
56
 
 
57
 
 
58
def find_cached_branch(f, cache_root, **args):
 
59
    from remotebranch import RemoteBranch
 
60
    br = find_branch(f, **args)
 
61
    def cacheify(br, store_name):
 
62
        from meta_store import CachedStore
 
63
        cache_path = os.path.join(cache_root, store_name)
 
64
        os.mkdir(cache_path)
 
65
        new_store = CachedStore(getattr(br, store_name), cache_path)
 
66
        setattr(br, store_name, new_store)
 
67
 
 
68
    if isinstance(br, RemoteBranch):
 
69
        cacheify(br, 'inventory_store')
 
70
        cacheify(br, 'text_store')
 
71
        cacheify(br, 'revision_store')
 
72
    return br
 
73
 
65
74
 
66
75
def _relpath(base, path):
67
76
    """Return path relative to base, or raise exception.
85
94
        if tail:
86
95
            s.insert(0, tail)
87
96
    else:
 
97
        from errors import NotBranchError
88
98
        raise NotBranchError("path %r is not within branch %r" % (rp, base))
89
99
 
90
100
    return os.sep.join(s)
102
112
    """
103
113
    if f == None:
104
114
        f = os.getcwd()
105
 
    else:
 
115
    elif hasattr(os.path, 'realpath'):
106
116
        f = os.path.realpath(f)
 
117
    else:
 
118
        f = os.path.abspath(f)
107
119
    if not os.path.exists(f):
108
120
        raise BzrError('%r does not exist' % f)
109
121
        
116
128
        head, tail = os.path.split(f)
117
129
        if head == f:
118
130
            # reached the root, whatever that may be
119
 
            raise NotBranchError('%s is not in a branch' % orig_f)
 
131
            raise bzrlib.errors.NotBranchError('%s is not in a branch' % orig_f)
120
132
        f = head
121
133
 
122
134
 
123
135
 
 
136
# XXX: move into bzrlib.errors; subclass BzrError    
 
137
class DivergedBranches(Exception):
 
138
    def __init__(self, branch1, branch2):
 
139
        self.branch1 = branch1
 
140
        self.branch2 = branch2
 
141
        Exception.__init__(self, "These branches have diverged.")
 
142
 
124
143
 
125
144
######################################################################
126
145
# branch objects
145
164
    _lock_mode = None
146
165
    _lock_count = None
147
166
    _lock = None
148
 
    cache_root = None
149
167
    
150
168
    # Map some sort of prefix into a namespace
151
169
    # stuff like "revno:10", "revid:", etc.
152
170
    # This should match a prefix with a function which accepts
153
171
    REVISION_NAMESPACES = {}
154
172
 
155
 
    def __init__(self, transport, init=False):
 
173
    def __init__(self, base, init=False, find_root=True):
156
174
        """Create new branch object at a particular location.
157
175
 
158
 
        transport -- A Transport object, defining how to access files.
159
 
                (If a string, transport.transport() will be used to
160
 
                create a Transport object)
 
176
        base -- Base directory for the branch.
161
177
        
162
178
        init -- If True, create new control files in a previously
163
179
             unversioned directory.  If False, the branch must already
164
180
             be versioned.
165
181
 
 
182
        find_root -- If true and init is false, find the root of the
 
183
             existing branch containing base.
 
184
 
166
185
        In the test suite, creation of new trees is tested using the
167
186
        `ScratchBranch` class.
168
187
        """
169
 
        if isinstance(transport, basestring):
170
 
            from transport import transport as get_transport
171
 
            transport = get_transport(transport)
172
 
 
173
 
        self._transport = transport
 
188
        from bzrlib.store import ImmutableStore
174
189
        if init:
 
190
            self.base = os.path.realpath(base)
175
191
            self._make_control()
 
192
        elif find_root:
 
193
            self.base = find_branch_root(base)
 
194
        else:
 
195
            self.base = os.path.realpath(base)
 
196
            if not isdir(self.controlfilename('.')):
 
197
                from errors import NotBranchError
 
198
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
 
199
                                     ['use "bzr init" to initialize a new working tree',
 
200
                                      'current bzr can only operate from top-of-tree'])
176
201
        self._check_format()
177
202
 
 
203
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
 
204
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
 
205
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
 
206
 
178
207
 
179
208
    def __str__(self):
180
 
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
 
209
        return '%s(%r)' % (self.__class__.__name__, self.base)
181
210
 
182
211
 
183
212
    __repr__ = __str__
185
214
 
186
215
    def __del__(self):
187
216
        if self._lock_mode or self._lock:
188
 
            from bzrlib.warnings import warn
 
217
            from warnings import warn
189
218
            warn("branch %r was not explicitly unlocked" % self)
190
219
            self._lock.unlock()
191
220
 
192
 
        # TODO: It might be best to do this somewhere else,
193
 
        # but it is nice for a Branch object to automatically
194
 
        # cache it's information.
195
 
        # Alternatively, we could have the Transport objects cache requests
196
 
        # See the earlier discussion about how major objects (like Branch)
197
 
        # should never expect their __del__ function to run.
198
 
        if self.cache_root is not None:
199
 
            #from warnings import warn
200
 
            #warn("branch %r auto-cleanup of cache files" % self)
201
 
            try:
202
 
                import shutil
203
 
                shutil.rmtree(self.cache_root)
204
 
            except:
205
 
                pass
206
 
            self.cache_root = None
207
 
 
208
 
    def _get_base(self):
209
 
        if self._transport:
210
 
            return self._transport.base
211
 
        return None
212
 
 
213
 
    base = property(_get_base)
214
221
 
215
222
 
216
223
    def lock_write(self):
217
 
        # TODO: Upgrade locking to support using a Transport,
218
 
        # and potentially a remote locking protocol
219
224
        if self._lock_mode:
220
225
            if self._lock_mode != 'w':
221
 
                from bzrlib.errors import LockError
 
226
                from errors import LockError
222
227
                raise LockError("can't upgrade to a write lock from %r" %
223
228
                                self._lock_mode)
224
229
            self._lock_count += 1
225
230
        else:
226
 
            self._lock = self._transport.lock_write(
227
 
                    self._rel_controlfilename('branch-lock'))
 
231
            from bzrlib.lock import WriteLock
 
232
 
 
233
            self._lock = WriteLock(self.controlfilename('branch-lock'))
228
234
            self._lock_mode = 'w'
229
235
            self._lock_count = 1
230
236
 
231
237
 
 
238
 
232
239
    def lock_read(self):
233
240
        if self._lock_mode:
234
241
            assert self._lock_mode in ('r', 'w'), \
235
242
                   "invalid lock mode %r" % self._lock_mode
236
243
            self._lock_count += 1
237
244
        else:
238
 
            self._lock = self._transport.lock_read(
239
 
                    self._rel_controlfilename('branch-lock'))
 
245
            from bzrlib.lock import ReadLock
 
246
 
 
247
            self._lock = ReadLock(self.controlfilename('branch-lock'))
240
248
            self._lock_mode = 'r'
241
249
            self._lock_count = 1
242
250
                        
 
251
 
 
252
            
243
253
    def unlock(self):
244
254
        if not self._lock_mode:
245
 
            from bzrlib.errors import LockError
 
255
            from errors import LockError
246
256
            raise LockError('branch %r is not locked' % (self))
247
257
 
248
258
        if self._lock_count > 1:
252
262
            self._lock = None
253
263
            self._lock_mode = self._lock_count = None
254
264
 
 
265
 
255
266
    def abspath(self, name):
256
267
        """Return absolute filename for something in the branch"""
257
 
        return self._transport.abspath(name)
 
268
        return os.path.join(self.base, name)
 
269
 
258
270
 
259
271
    def relpath(self, path):
260
272
        """Return path relative to this branch of something inside it.
261
273
 
262
274
        Raises an error if path is not in this branch."""
263
 
        return self._transport.relpath(path)
264
 
 
265
 
 
266
 
    def _rel_controlfilename(self, file_or_path):
 
275
        return _relpath(self.base, path)
 
276
 
 
277
 
 
278
    def controlfilename(self, file_or_path):
 
279
        """Return location relative to branch."""
267
280
        if isinstance(file_or_path, basestring):
268
281
            file_or_path = [file_or_path]
269
 
        return [bzrlib.BZRDIR] + file_or_path
270
 
 
271
 
    def controlfilename(self, file_or_path):
272
 
        """Return location relative to branch."""
273
 
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
 
282
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
274
283
 
275
284
 
276
285
    def controlfile(self, file_or_path, mode='r'):
284
293
        Controlfiles should almost never be opened in write mode but
285
294
        rather should be atomically copied and replaced using atomicfile.
286
295
        """
287
 
        import codecs
288
 
 
289
 
        relpath = self._rel_controlfilename(file_or_path)
290
 
        #TODO: codecs.open() buffers linewise, so it was overloaded with
291
 
        # a much larger buffer, do we need to do the same for getreader/getwriter?
292
 
        if mode == 'rb': 
293
 
            return self._transport.get(relpath)
294
 
        elif mode == 'wb':
295
 
            raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
296
 
        elif mode == 'r':
297
 
            return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
298
 
        elif mode == 'w':
299
 
            raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
 
296
 
 
297
        fn = self.controlfilename(file_or_path)
 
298
 
 
299
        if mode == 'rb' or mode == 'wb':
 
300
            return file(fn, mode)
 
301
        elif mode == 'r' or mode == 'w':
 
302
            # open in binary mode anyhow so there's no newline translation;
 
303
            # codecs uses line buffering by default; don't want that.
 
304
            import codecs
 
305
            return codecs.open(fn, mode + 'b', 'utf-8',
 
306
                               buffering=60000)
300
307
        else:
301
308
            raise BzrError("invalid controlfile mode %r" % mode)
302
309
 
303
 
    def put_controlfile(self, path, f, encode=True):
304
 
        """Write an entry as a controlfile.
305
 
 
306
 
        :param path: The path to put the file, relative to the .bzr control
307
 
                     directory
308
 
        :param f: A file-like or string object whose contents should be copied.
309
 
        :param encode:  If true, encode the contents as utf-8
310
 
        """
311
 
        self.put_controlfiles([(path, f)], encode=encode)
312
 
 
313
 
    def put_controlfiles(self, files, encode=True):
314
 
        """Write several entries as controlfiles.
315
 
 
316
 
        :param files: A list of [(path, file)] pairs, where the path is the directory
317
 
                      underneath the bzr control directory
318
 
        :param encode:  If true, encode the contents as utf-8
319
 
        """
320
 
        import codecs
321
 
        ctrl_files = []
322
 
        for path, f in files:
323
 
            if encode:
324
 
                if isinstance(f, basestring):
325
 
                    f = f.encode('utf-8', 'replace')
326
 
                else:
327
 
                    f = codecs.getwriter('utf-8')(f, errors='replace')
328
 
            path = self._rel_controlfilename(path)
329
 
            ctrl_files.append((path, f))
330
 
        self._transport.put_multi(ctrl_files)
 
310
 
331
311
 
332
312
    def _make_control(self):
333
313
        from bzrlib.inventory import Inventory
334
 
        from cStringIO import StringIO
 
314
        from bzrlib.xml import pack_xml
335
315
        
336
 
        # Create an empty inventory
337
 
        sio = StringIO()
 
316
        os.mkdir(self.controlfilename([]))
 
317
        self.controlfile('README', 'w').write(
 
318
            "This is a Bazaar-NG control directory.\n"
 
319
            "Do not change any files in this directory.\n")
 
320
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
 
321
        for d in ('text-store', 'inventory-store', 'revision-store'):
 
322
            os.mkdir(self.controlfilename(d))
 
323
        for f in ('revision-history', 'merged-patches',
 
324
                  'pending-merged-patches', 'branch-name',
 
325
                  'branch-lock',
 
326
                  'pending-merges'):
 
327
            self.controlfile(f, 'w').write('')
 
328
        mutter('created control directory in ' + self.base)
 
329
 
338
330
        # if we want per-tree root ids then this is the place to set
339
331
        # them; they're not needed for now and so ommitted for
340
332
        # simplicity.
341
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), sio)
 
333
        pack_xml(Inventory(), self.controlfile('inventory','w'))
342
334
 
343
 
        dirs = [[], 'text-store', 'inventory-store', 'revision-store']
344
 
        files = [('README', 
345
 
            "This is a Bazaar-NG control directory.\n"
346
 
            "Do not change any files in this directory.\n"),
347
 
            ('branch-format', BZR_BRANCH_FORMAT),
348
 
            ('revision-history', ''),
349
 
            ('merged-patches', ''),
350
 
            ('pending-merged-patches', ''),
351
 
            ('branch-name', ''),
352
 
            ('branch-lock', ''),
353
 
            ('pending-merges', ''),
354
 
            ('inventory', sio.getvalue())
355
 
        ]
356
 
        self._transport.mkdir_multi([self._rel_controlfilename(d) for d in dirs])
357
 
        self.put_controlfiles(files)
358
 
        mutter('created control directory in ' + self._transport.base)
359
335
 
360
336
    def _check_format(self):
361
337
        """Check this branch format is supported.
369
345
        # on Windows from Linux and so on.  I think it might be better
370
346
        # to always make all internal files in unix format.
371
347
        fmt = self.controlfile('branch-format', 'r').read()
372
 
        fmt = fmt.replace('\r\n', '\n')
 
348
        fmt.replace('\r\n', '')
373
349
        if fmt != BZR_BRANCH_FORMAT:
374
350
            raise BzrError('sorry, branch format %r not supported' % fmt,
375
351
                           ['use a different bzr version',
376
352
                            'or remove the .bzr directory and "bzr init" again'])
377
353
 
378
 
        # We know that the format is the currently supported one.
379
 
        # So create the rest of the entries.
380
 
        from bzrlib.store.compressed_text import CompressedTextStore
381
 
 
382
 
        if self._transport.should_cache():
383
 
            import tempfile
384
 
            self.cache_root = tempfile.mkdtemp(prefix='bzr-cache')
385
 
            mutter('Branch %r using caching in %r' % (self, self.cache_root))
386
 
        else:
387
 
            self.cache_root = None
388
 
 
389
 
        def get_store(name):
390
 
            relpath = self._rel_controlfilename(name)
391
 
            store = CompressedTextStore(self._transport.clone(relpath))
392
 
            if self._transport.should_cache():
393
 
                from meta_store import CachedStore
394
 
                cache_path = os.path.join(self.cache_root, name)
395
 
                os.mkdir(cache_path)
396
 
                store = CachedStore(store, cache_path)
397
 
            return store
398
 
 
399
 
        self.text_store = get_store('text-store')
400
 
        self.revision_store = get_store('revision-store')
401
 
        self.inventory_store = get_store('inventory-store')
402
 
 
403
354
    def get_root_id(self):
404
355
        """Return the id of this branches root"""
405
356
        inv = self.read_working_inventory()
420
371
    def read_working_inventory(self):
421
372
        """Read the working inventory."""
422
373
        from bzrlib.inventory import Inventory
 
374
        from bzrlib.xml import unpack_xml
 
375
        from time import time
 
376
        before = time()
423
377
        self.lock_read()
424
378
        try:
425
379
            # ElementTree does its own conversion from UTF-8, so open in
426
380
            # binary.
427
 
            f = self.controlfile('inventory', 'rb')
428
 
            return bzrlib.xml.serializer_v4.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
429
386
        finally:
430
387
            self.unlock()
431
388
            
436
393
        That is to say, the inventory describing changes underway, that
437
394
        will be committed to the next revision.
438
395
        """
439
 
        from cStringIO import StringIO
 
396
        from bzrlib.atomicfile import AtomicFile
 
397
        from bzrlib.xml import pack_xml
 
398
        
440
399
        self.lock_write()
441
400
        try:
442
 
            sio = StringIO()
443
 
            bzrlib.xml.serializer_v4.write_inventory(inv, sio)
444
 
            sio.seek(0)
445
 
            # Transport handles atomicity
446
 
            self.put_controlfile('inventory', sio)
 
401
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
 
402
            try:
 
403
                pack_xml(inv, f)
 
404
                f.commit()
 
405
            finally:
 
406
                f.close()
447
407
        finally:
448
408
            self.unlock()
449
409
        
614
574
 
615
575
 
616
576
    def append_revision(self, *revision_ids):
 
577
        from bzrlib.atomicfile import AtomicFile
 
578
 
617
579
        for revision_id in revision_ids:
618
580
            mutter("add {%s} to revision-history" % revision_id)
619
581
 
620
582
        rev_history = self.revision_history()
621
583
        rev_history.extend(revision_ids)
622
584
 
623
 
        self.lock_write()
 
585
        f = AtomicFile(self.controlfilename('revision-history'))
624
586
        try:
625
 
            self.put_controlfile('revision-history', '\n'.join(rev_history))
 
587
            for rev_id in rev_history:
 
588
                print >>f, rev_id
 
589
            f.commit()
626
590
        finally:
627
 
            self.unlock()
628
 
 
629
 
 
630
 
    def get_revision_xml_file(self, revision_id):
 
591
            f.close()
 
592
 
 
593
 
 
594
    def get_revision_xml(self, revision_id):
631
595
        """Return XML file object for revision object."""
632
596
        if not revision_id or not isinstance(revision_id, basestring):
633
597
            raise InvalidRevisionId(revision_id)
636
600
        try:
637
601
            try:
638
602
                return self.revision_store[revision_id]
639
 
            except (IndexError, KeyError):
 
603
            except IndexError:
640
604
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
641
605
        finally:
642
606
            self.unlock()
643
607
 
644
608
 
645
 
    #deprecated
646
 
    get_revision_xml = get_revision_xml_file
647
 
 
648
 
 
649
609
    def get_revision(self, revision_id):
650
610
        """Return the Revision object for a named revision"""
651
 
        xml_file = self.get_revision_xml_file(revision_id)
 
611
        xml_file = self.get_revision_xml(revision_id)
652
612
 
653
613
        try:
654
 
            r = bzrlib.xml.serializer_v4.read_revision(xml_file)
 
614
            r = unpack_xml(Revision, xml_file)
655
615
        except SyntaxError, e:
656
616
            raise bzrlib.errors.BzrError('failed to unpack revision_xml',
657
617
                                         [revision_id,
683
643
        return compare_trees(old_tree, new_tree)
684
644
 
685
645
        
686
 
    def get_revisions(self, revision_ids, pb=None):
687
 
        """Return the Revision object for a set of named revisions"""
688
 
        from bzrlib.revision import Revision
689
 
        from bzrlib.xml import unpack_xml
690
646
 
691
 
        # TODO: We need to decide what to do here
692
 
        # we cannot use a generator with a try/finally, because
693
 
        # you cannot guarantee that the caller will iterate through
694
 
        # all entries.
695
 
        # in the past, get_inventory wasn't even wrapped in a
696
 
        # try/finally locking block.
697
 
        # We could either lock without the try/finally, or just
698
 
        # not lock at all. We are reading entries that should
699
 
        # never be updated.
700
 
        # I prefer locking with no finally, so that if someone
701
 
        # asks for a list of revisions, but doesn't consume them,
702
 
        # that is their problem, and they will suffer the consequences
703
 
        self.lock_read()
704
 
        for xml_file in self.revision_store.get(revision_ids, pb=pb):
705
 
            try:
706
 
                r = bzrlib.xml.serializer_v4.read_revision(xml_file)
707
 
            except SyntaxError, e:
708
 
                raise bzrlib.errors.BzrError('failed to unpack revision_xml',
709
 
                                             [revision_id,
710
 
                                              str(e)])
711
 
            yield r
712
 
        self.unlock()
713
 
            
714
647
    def get_revision_sha1(self, revision_id):
715
648
        """Hash the stored value of a revision, and return it."""
716
649
        # In the future, revision entries will be signed. At that
727
660
 
728
661
        TODO: Perhaps for this and similar methods, take a revision
729
662
               parameter which can be either an integer revno or a
730
 
               string hash.
731
 
        """
732
 
        f = self.get_inventory_xml_file(inventory_id)
733
 
        return bzrlib.xml.serializer_v4.read_inventory(f)
 
663
               string hash."""
 
664
        from bzrlib.inventory import Inventory
 
665
        from bzrlib.xml import unpack_xml
 
666
 
 
667
        return unpack_xml(Inventory, self.get_inventory_xml(inventory_id))
734
668
 
735
669
 
736
670
    def get_inventory_xml(self, inventory_id):
737
671
        """Get inventory XML as a file object."""
738
 
        # Shouldn't this have a read-lock around it?
739
 
        # As well as some sort of trap for missing ids?
740
672
        return self.inventory_store[inventory_id]
741
 
 
742
 
    get_inventory_xml_file = get_inventory_xml
743
673
            
744
 
    def get_inventories(self, inventory_ids, pb=None, ignore_missing=False):
745
 
        """Get Inventory objects by id
746
 
        """
747
 
        from bzrlib.inventory import Inventory
748
 
 
749
 
        # See the discussion in get_revisions for why
750
 
        # we don't use a try/finally block here
751
 
        self.lock_read()
752
 
        for f in self.inventory_store.get(inventory_ids, pb=pb, ignore_missing=ignore_missing):
753
 
            if f is not None:
754
 
                # TODO: Possibly put a try/except around this to handle
755
 
                # read serialization errors
756
 
                r = bzrlib.xml.serializer_v4.read_inventory(f)
757
 
                yield r
758
 
            elif ignore_missing:
759
 
                yield None
760
 
            else:
761
 
                raise bzrlib.errors.NoSuchRevision(self, revision_id)
762
 
        self.unlock()
763
674
 
764
675
    def get_inventory_sha1(self, inventory_id):
765
676
        """Return the sha1 hash of the inventory entry
794
705
 
795
706
    def common_ancestor(self, other, self_revno=None, other_revno=None):
796
707
        """
797
 
        >>> from bzrlib.commit import commit
 
708
        >>> import commit
798
709
        >>> sb = ScratchBranch(files=['foo', 'foo~'])
799
710
        >>> sb.common_ancestor(sb) == (None, None)
800
711
        True
801
 
        >>> commit(sb, "Committing first revision", verbose=False)
 
712
        >>> commit.commit(sb, "Committing first revision", verbose=False)
802
713
        >>> sb.common_ancestor(sb)[0]
803
714
        1
804
715
        >>> clone = sb.clone()
805
 
        >>> commit(sb, "Committing second revision", verbose=False)
 
716
        >>> commit.commit(sb, "Committing second revision", verbose=False)
806
717
        >>> sb.common_ancestor(sb)[0]
807
718
        2
808
719
        >>> sb.common_ancestor(clone)[0]
809
720
        1
810
 
        >>> commit(clone, "Committing divergent second revision", 
 
721
        >>> commit.commit(clone, "Committing divergent second revision", 
811
722
        ...               verbose=False)
812
723
        >>> sb.common_ancestor(clone)[0]
813
724
        1
904
815
        """Pull in all new revisions from other branch.
905
816
        """
906
817
        from bzrlib.fetch import greedy_fetch
907
 
        from bzrlib.revision import get_intervening_revisions
908
818
 
909
819
        pb = bzrlib.ui.ui_factory.progress_bar()
910
820
        pb.update('comparing histories')
911
 
        if stop_revision is None:
912
 
            other_revision = other.last_patch()
 
821
 
 
822
        revision_ids = self.missing_revisions(other, stop_revision)
 
823
 
 
824
        if len(revision_ids) > 0:
 
825
            count = greedy_fetch(self, other, revision_ids[-1], pb)[0]
913
826
        else:
914
 
            other_revision = other.lookup_revision(stop_revision)
915
 
        count = greedy_fetch(self, other, other_revision, pb)[0]
916
 
        try:
917
 
            revision_ids = self.missing_revisions(other, stop_revision)
918
 
        except DivergedBranches, e:
919
 
            try:
920
 
                revision_ids = get_intervening_revisions(self.last_patch(), 
921
 
                                                         other_revision, self)
922
 
                assert self.last_patch() not in revision_ids
923
 
            except bzrlib.errors.NotAncestor:
924
 
                raise e
925
 
 
 
827
            count = 0
926
828
        self.append_revision(*revision_ids)
 
829
        ## note("Added %d revisions." % count)
927
830
        pb.clear()
928
831
 
 
832
        
 
833
        
929
834
    def install_revisions(self, other, revision_ids, pb):
930
 
        # We are going to iterate this many times, so make sure
931
 
        # that it is a list, and not a generator
932
 
        revision_ids = list(revision_ids)
933
835
        if hasattr(other.revision_store, "prefetch"):
934
836
            other.revision_store.prefetch(revision_ids)
935
837
        if hasattr(other.inventory_store, "prefetch"):
 
838
            inventory_ids = [other.get_revision(r).inventory_id
 
839
                             for r in revision_ids]
936
840
            other.inventory_store.prefetch(inventory_ids)
937
841
 
938
842
        if pb is None:
939
843
            pb = bzrlib.ui.ui_factory.progress_bar()
940
844
                
941
 
        # This entire next section is generally done
942
 
        # with either generators, or bulk updates
943
 
        inventories = other.get_inventories(revision_ids, ignore_missing=True)
 
845
        revisions = []
944
846
        needed_texts = set()
 
847
        i = 0
945
848
 
946
849
        failures = set()
947
 
        good_revisions = set()
948
 
        for i, (inv, rev_id) in enumerate(zip(inventories, revision_ids)):
 
850
        for i, rev_id in enumerate(revision_ids):
949
851
            pb.update('fetching revision', i+1, len(revision_ids))
950
 
 
951
 
            # We don't really need to get the revision here, because
952
 
            # the only thing we needed was the inventory_id, which now
953
 
            # is (by design) identical to the revision_id
954
 
            # try:
955
 
            #     rev = other.get_revision(rev_id)
956
 
            # except bzrlib.errors.NoSuchRevision:
957
 
            #     failures.add(rev_id)
958
 
            #     continue
959
 
 
960
 
            if inv is None:
 
852
            try:
 
853
                rev = other.get_revision(rev_id)
 
854
            except bzrlib.errors.NoSuchRevision:
961
855
                failures.add(rev_id)
962
856
                continue
963
 
            else:
964
 
                good_revisions.add(rev_id)
965
857
 
966
 
            text_ids = []
 
858
            revisions.append(rev)
 
859
            inv = other.get_inventory(str(rev.inventory_id))
967
860
            for key, entry in inv.iter_entries():
968
861
                if entry.text_id is None:
969
862
                    continue
970
 
                text_ids.append(entry.text_id)
971
 
 
972
 
            has_ids = self.text_store.has(text_ids)
973
 
            for has, text_id in zip(has_ids, text_ids):
974
 
                if not has:
975
 
                    needed_texts.add(text_id)
 
863
                if entry.text_id not in self.text_store:
 
864
                    needed_texts.add(entry.text_id)
976
865
 
977
866
        pb.clear()
978
867
                    
979
868
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
980
869
                                                    needed_texts)
981
870
        #print "Added %d texts." % count 
982
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
983
 
                                                         good_revisions)
 
871
        inventory_ids = [ f.inventory_id for f in revisions ]
 
872
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
 
873
                                                         inventory_ids)
984
874
        #print "Added %d inventories." % count 
 
875
        revision_ids = [ f.revision_id for f in revisions]
 
876
 
985
877
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
986
 
                                                          good_revisions,
 
878
                                                          revision_ids,
987
879
                                                          permit_failure=True)
988
880
        assert len(cp_fail) == 0 
989
881
        return count, failures
996
888
 
997
889
    def lookup_revision(self, revision):
998
890
        """Return the revision identifier for a given revision information."""
999
 
        revno, info = self._get_revision_info(revision)
 
891
        revno, info = self.get_revision_info(revision)
1000
892
        return info
1001
893
 
1002
894
 
1017
909
        revision can also be a string, in which case it is parsed for something like
1018
910
            'date:' or 'revid:' etc.
1019
911
        """
1020
 
        revno, rev_id = self._get_revision_info(revision)
1021
 
        if revno is None:
1022
 
            raise bzrlib.errors.NoSuchRevision(self, revision)
1023
 
        return revno, rev_id
1024
 
 
1025
 
    def get_rev_id(self, revno, history=None):
1026
 
        """Find the revision id of the specified revno."""
1027
 
        if revno == 0:
1028
 
            return None
1029
 
        if history is None:
1030
 
            history = self.revision_history()
1031
 
        elif revno <= 0 or revno > len(history):
1032
 
            raise bzrlib.errors.NoSuchRevision(self, revno)
1033
 
        return history[revno - 1]
1034
 
 
1035
 
    def _get_revision_info(self, revision):
1036
 
        """Return (revno, revision id) for revision specifier.
1037
 
 
1038
 
        revision can be an integer, in which case it is assumed to be revno
1039
 
        (though this will translate negative values into positive ones)
1040
 
        revision can also be a string, in which case it is parsed for something
1041
 
        like 'date:' or 'revid:' etc.
1042
 
 
1043
 
        A revid is always returned.  If it is None, the specifier referred to
1044
 
        the null revision.  If the revid does not occur in the revision
1045
 
        history, revno will be None.
1046
 
        """
1047
 
        
1048
912
        if revision is None:
1049
913
            return 0, None
1050
914
        revno = None
1054
918
            pass
1055
919
        revs = self.revision_history()
1056
920
        if isinstance(revision, int):
 
921
            if revision == 0:
 
922
                return 0, None
 
923
            # Mabye we should do this first, but we don't need it if revision == 0
1057
924
            if revision < 0:
1058
925
                revno = len(revs) + revision + 1
1059
926
            else:
1060
927
                revno = revision
1061
 
            rev_id = self.get_rev_id(revno, revs)
1062
928
        elif isinstance(revision, basestring):
1063
929
            for prefix, func in Branch.REVISION_NAMESPACES.iteritems():
1064
930
                if revision.startswith(prefix):
1065
 
                    result = func(self, revs, revision)
1066
 
                    if len(result) > 1:
1067
 
                        revno, rev_id = result
1068
 
                    else:
1069
 
                        revno = result[0]
1070
 
                        rev_id = self.get_rev_id(revno, revs)
 
931
                    revno = func(self, revs, revision)
1071
932
                    break
1072
933
            else:
1073
 
                raise BzrError('No namespace registered for string: %r' %
1074
 
                               revision)
1075
 
        else:
1076
 
            raise TypeError('Unhandled revision type %s' % revision)
 
934
                raise BzrError('No namespace registered for string: %r' % revision)
1077
935
 
1078
 
        if revno is None:
1079
 
            if rev_id is None:
1080
 
                raise bzrlib.errors.NoSuchRevision(self, revision)
1081
 
        return revno, rev_id
 
936
        if revno is None or revno <= 0 or revno > len(revs):
 
937
            raise BzrError("no such revision %s" % revision)
 
938
        return revno, revs[revno-1]
1082
939
 
1083
940
    def _namespace_revno(self, revs, revision):
1084
941
        """Lookup a revision by revision number"""
1085
942
        assert revision.startswith('revno:')
1086
943
        try:
1087
 
            return (int(revision[6:]),)
 
944
            return int(revision[6:])
1088
945
        except ValueError:
1089
946
            return None
1090
947
    REVISION_NAMESPACES['revno:'] = _namespace_revno
1091
948
 
1092
949
    def _namespace_revid(self, revs, revision):
1093
950
        assert revision.startswith('revid:')
1094
 
        rev_id = revision[len('revid:'):]
1095
951
        try:
1096
 
            return revs.index(rev_id) + 1, rev_id
 
952
            return revs.index(revision[6:]) + 1
1097
953
        except ValueError:
1098
 
            return None, rev_id
 
954
            return None
1099
955
    REVISION_NAMESPACES['revid:'] = _namespace_revid
1100
956
 
1101
957
    def _namespace_last(self, revs, revision):
1103
959
        try:
1104
960
            offset = int(revision[5:])
1105
961
        except ValueError:
1106
 
            return (None,)
 
962
            return None
1107
963
        else:
1108
964
            if offset <= 0:
1109
965
                raise BzrError('You must supply a positive value for --revision last:XXX')
1110
 
            return (len(revs) - offset + 1,)
 
966
            return len(revs) - offset + 1
1111
967
    REVISION_NAMESPACES['last:'] = _namespace_last
1112
968
 
1113
969
    def _namespace_tag(self, revs, revision):
1188
1044
                # TODO: Handle timezone.
1189
1045
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1190
1046
                if first >= dt and (last is None or dt >= last):
1191
 
                    return (i+1,)
 
1047
                    return i+1
1192
1048
        else:
1193
1049
            for i in range(len(revs)):
1194
1050
                r = self.get_revision(revs[i])
1195
1051
                # TODO: Handle timezone.
1196
1052
                dt = datetime.datetime.fromtimestamp(r.timestamp)
1197
1053
                if first <= dt and (last is None or dt <= last):
1198
 
                    return (i+1,)
 
1054
                    return i+1
1199
1055
    REVISION_NAMESPACES['date:'] = _namespace_date
1200
1056
 
1201
 
 
1202
 
    def _namespace_ancestor(self, revs, revision):
1203
 
        from revision import common_ancestor, MultipleRevisionSources
1204
 
        other_branch = find_branch(_trim_namespace('ancestor', revision))
1205
 
        revision_a = self.last_patch()
1206
 
        revision_b = other_branch.last_patch()
1207
 
        for r, b in ((revision_a, self), (revision_b, other_branch)):
1208
 
            if r is None:
1209
 
                raise bzrlib.errors.NoCommits(b)
1210
 
        revision_source = MultipleRevisionSources(self, other_branch)
1211
 
        result = common_ancestor(revision_a, revision_b, revision_source)
1212
 
        try:
1213
 
            revno = self.revision_id_to_revno(result)
1214
 
        except bzrlib.errors.NoSuchRevision:
1215
 
            revno = None
1216
 
        return revno,result
1217
 
        
1218
 
 
1219
 
    REVISION_NAMESPACES['ancestor:'] = _namespace_ancestor
1220
 
 
1221
1057
    def revision_tree(self, revision_id):
1222
1058
        """Return Tree for a revision on this branch.
1223
1059
 
1234
1070
 
1235
1071
    def working_tree(self):
1236
1072
        """Return a `Tree` for the working copy."""
1237
 
        from bzrlib.workingtree import WorkingTree
1238
 
        # TODO: In the future, WorkingTree should utilize Transport
1239
 
        return WorkingTree(self._transport.base, self.read_working_inventory())
 
1073
        from workingtree import WorkingTree
 
1074
        return WorkingTree(self.base, self.read_working_inventory())
1240
1075
 
1241
1076
 
1242
1077
    def basis_tree(self):
1421
1256
        These are revisions that have been merged into the working
1422
1257
        directory but not yet committed.
1423
1258
        """
1424
 
        cfn = self._rel_controlfilename('pending-merges')
1425
 
        if not self._transport.has(cfn):
 
1259
        cfn = self.controlfilename('pending-merges')
 
1260
        if not os.path.exists(cfn):
1426
1261
            return []
1427
1262
        p = []
1428
1263
        for l in self.controlfile('pending-merges', 'r').readlines():
1430
1265
        return p
1431
1266
 
1432
1267
 
1433
 
    def add_pending_merge(self, *revision_ids):
 
1268
    def add_pending_merge(self, revision_id):
1434
1269
        from bzrlib.revision import validate_revision_id
1435
1270
 
1436
 
        for rev_id in revision_ids:
1437
 
            validate_revision_id(rev_id)
 
1271
        validate_revision_id(revision_id)
1438
1272
 
1439
1273
        p = self.pending_merges()
1440
 
        updated = False
1441
 
        for rev_id in revision_ids:
1442
 
            if rev_id in p:
1443
 
                continue
1444
 
            p.append(rev_id)
1445
 
            updated = True
1446
 
        if updated:
1447
 
            self.set_pending_merges(p)
 
1274
        if revision_id in p:
 
1275
            return
 
1276
        p.append(revision_id)
 
1277
        self.set_pending_merges(p)
 
1278
 
1448
1279
 
1449
1280
    def set_pending_merges(self, rev_list):
1450
 
        self.lock_write()
1451
 
        try:
1452
 
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1453
 
        finally:
1454
 
            self.unlock()
1455
 
 
1456
 
 
1457
 
    def get_parent(self):
1458
 
        """Return the parent location of the branch.
1459
 
 
1460
 
        This is the default location for push/pull/missing.  The usual
1461
 
        pattern is that the user can override it by specifying a
1462
 
        location.
1463
 
        """
1464
 
        import errno
1465
 
        _locs = ['parent', 'pull', 'x-pull']
1466
 
        for l in _locs:
1467
 
            try:
1468
 
                return self.controlfile(l, 'r').read().strip('\n')
1469
 
            except IOError, e:
1470
 
                if e.errno != errno.ENOENT:
1471
 
                    raise
1472
 
        return None
1473
 
 
1474
 
 
1475
 
    def set_parent(self, url):
1476
 
        # TODO: Maybe delete old location files?
1477
1281
        from bzrlib.atomicfile import AtomicFile
1478
1282
        self.lock_write()
1479
1283
        try:
1480
 
            f = AtomicFile(self.controlfilename('parent'))
 
1284
            f = AtomicFile(self.controlfilename('pending-merges'))
1481
1285
            try:
1482
 
                f.write(url + '\n')
 
1286
                for l in rev_list:
 
1287
                    print >>f, l
1483
1288
                f.commit()
1484
1289
            finally:
1485
1290
                f.close()
1486
1291
        finally:
1487
1292
            self.unlock()
1488
1293
 
1489
 
    def check_revno(self, revno):
1490
 
        """\
1491
 
        Check whether a revno corresponds to any revision.
1492
 
        Zero (the NULL revision) is considered valid.
1493
 
        """
1494
 
        if revno != 0:
1495
 
            self.check_real_revno(revno)
1496
 
            
1497
 
    def check_real_revno(self, revno):
1498
 
        """\
1499
 
        Check whether a revno corresponds to a real revision.
1500
 
        Zero (the NULL revision) is considered invalid
1501
 
        """
1502
 
        if revno < 1 or revno > self.revno():
1503
 
            raise InvalidRevisionNumber(revno)
1504
 
        
1505
 
        
1506
1294
 
1507
1295
 
1508
1296
class ScratchBranch(Branch):
1530
1318
            init = True
1531
1319
        Branch.__init__(self, base, init=init)
1532
1320
        for d in dirs:
1533
 
            self._transport.mkdir(d)
 
1321
            os.mkdir(self.abspath(d))
1534
1322
            
1535
1323
        for f in files:
1536
 
            self._transport.put(f, 'content of %s' % f)
 
1324
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
1537
1325
 
1538
1326
 
1539
1327
    def clone(self):
1551
1339
        os.rmdir(base)
1552
1340
        copytree(self.base, base, symlinks=True)
1553
1341
        return ScratchBranch(base=base)
1554
 
 
1555
 
 
1556
1342
        
1557
1343
    def __del__(self):
1558
1344
        self.destroy()
1572
1358
                for name in files:
1573
1359
                    os.chmod(os.path.join(root, name), 0700)
1574
1360
            rmtree(self.base)
1575
 
        self._transport = None
 
1361
        self.base = None
1576
1362
 
1577
1363
    
1578
1364
 
1628
1414
    """Return a new tree-root file id."""
1629
1415
    return gen_file_id('TREE_ROOT')
1630
1416
 
1631
 
 
1632
 
def copy_branch(branch_from, to_location, revision=None):
1633
 
    """Copy branch_from into the existing directory to_location.
1634
 
 
1635
 
    revision
1636
 
        If not None, only revisions up to this point will be copied.
1637
 
        The head of the new branch will be that revision.
1638
 
 
1639
 
    to_location
1640
 
        The name of a local directory that exists but is empty.
1641
 
    """
1642
 
    from bzrlib.merge import merge
1643
 
 
1644
 
    assert isinstance(branch_from, Branch)
1645
 
    assert isinstance(to_location, basestring)
1646
 
    
1647
 
    br_to = Branch(to_location, init=True)
1648
 
    br_to.set_root_id(branch_from.get_root_id())
1649
 
    if revision is None:
1650
 
        revno = branch_from.revno()
1651
 
    else:
1652
 
        revno, rev_id = branch_from.get_revision_info(revision)
1653
 
    br_to.update_revisions(branch_from, stop_revision=revno)
1654
 
    merge((to_location, -1), (to_location, 0), this_dir=to_location,
1655
 
          check_clean=False, ignore_zero=True)
1656
 
    br_to.set_parent(branch_from.base)
1657
 
    return br_to
1658
 
 
1659
 
def _trim_namespace(namespace, spec):
1660
 
    full_namespace = namespace + ':'
1661
 
    assert spec.startswith(full_namespace)
1662
 
    return spec[len(full_namespace):]