~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/branch.py

  • Committer: John Arbash Meinel
  • Date: 2005-09-15 21:35:53 UTC
  • mfrom: (907.1.57)
  • mto: (1393.2.1)
  • mto: This revision was merged to the branch mainline in revision 1396.
  • Revision ID: john@arbash-meinel.com-20050915213552-a6c83a5ef1e20897
(broken) Transport work is merged in. Tests do not pass yet.

Show diffs side-by-side

added added

removed removed

Lines of Context:
48
48
 
49
49
 
50
50
def find_branch(f, **args):
51
 
    if f and (f.startswith('http://') or f.startswith('https://')):
52
 
        from bzrlib.remotebranch import RemoteBranch
53
 
        return RemoteBranch(f, **args)
54
 
    else:
55
 
        return Branch(f, **args)
56
 
 
57
 
 
58
 
def find_cached_branch(f, cache_root, **args):
59
 
    from bzrlib.remotebranch import RemoteBranch
60
 
    br = find_branch(f, **args)
61
 
    def cacheify(br, store_name):
62
 
        from bzrlib.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
 
 
 
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)
74
65
 
75
66
def _relpath(base, path):
76
67
    """Return path relative to base, or raise exception.
111
102
    """
112
103
    if f == None:
113
104
        f = os.getcwd()
114
 
    elif hasattr(os.path, 'realpath'):
 
105
    else:
115
106
        f = os.path.realpath(f)
116
 
    else:
117
 
        f = os.path.abspath(f)
118
107
    if not os.path.exists(f):
119
108
        raise BzrError('%r does not exist' % f)
120
109
        
156
145
    _lock_mode = None
157
146
    _lock_count = None
158
147
    _lock = None
 
148
    cache_root = None
159
149
    
160
150
    # Map some sort of prefix into a namespace
161
151
    # stuff like "revno:10", "revid:", etc.
162
152
    # This should match a prefix with a function which accepts
163
153
    REVISION_NAMESPACES = {}
164
154
 
165
 
    def __init__(self, base, init=False, find_root=True):
 
155
    def __init__(self, transport, init=False):
166
156
        """Create new branch object at a particular location.
167
157
 
168
 
        base -- Base directory for the branch. May be a file:// url.
 
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)
169
161
        
170
162
        init -- If True, create new control files in a previously
171
163
             unversioned directory.  If False, the branch must already
172
164
             be versioned.
173
165
 
174
 
        find_root -- If true and init is false, find the root of the
175
 
             existing branch containing base.
176
 
 
177
166
        In the test suite, creation of new trees is tested using the
178
167
        `ScratchBranch` class.
179
168
        """
180
 
        from bzrlib.store import ImmutableStore
 
169
        if isinstance(transport, basestring):
 
170
            from transport import transport as get_transport
 
171
            transport = get_transport(transport)
 
172
 
 
173
        self._transport = transport
181
174
        if init:
182
 
            self.base = os.path.realpath(base)
183
175
            self._make_control()
184
 
        elif find_root:
185
 
            self.base = find_branch_root(base)
186
 
        else:
187
 
            if base.startswith("file://"):
188
 
                base = base[7:]
189
 
            self.base = os.path.realpath(base)
190
 
            if not isdir(self.controlfilename('.')):
191
 
                raise NotBranchError("not a bzr branch: %s" % quotefn(base),
192
 
                                     ['use "bzr init" to initialize a new working tree',
193
 
                                      'current bzr can only operate from top-of-tree'])
194
176
        self._check_format()
195
177
 
196
 
        self.text_store = ImmutableStore(self.controlfilename('text-store'))
197
 
        self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
198
 
        self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
199
 
 
200
178
 
201
179
    def __str__(self):
202
 
        return '%s(%r)' % (self.__class__.__name__, self.base)
 
180
        return '%s(%r)' % (self.__class__.__name__, self._transport.base)
203
181
 
204
182
 
205
183
    __repr__ = __str__
211
189
            warn("branch %r was not explicitly unlocked" % self)
212
190
            self._lock.unlock()
213
191
 
 
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
 
 
215
 
214
216
    def lock_write(self):
 
217
        # TODO: Upgrade locking to support using a Transport,
 
218
        # and potentially a remote locking protocol
215
219
        if self._lock_mode:
216
220
            if self._lock_mode != 'w':
217
221
                from bzrlib.errors import LockError
219
223
                                self._lock_mode)
220
224
            self._lock_count += 1
221
225
        else:
222
 
            from bzrlib.lock import WriteLock
223
 
 
224
 
            self._lock = WriteLock(self.controlfilename('branch-lock'))
 
226
            self._lock = self._transport.lock_write(
 
227
                    self._rel_controlfilename('branch-lock'))
225
228
            self._lock_mode = 'w'
226
229
            self._lock_count = 1
227
230
 
232
235
                   "invalid lock mode %r" % self._lock_mode
233
236
            self._lock_count += 1
234
237
        else:
235
 
            from bzrlib.lock import ReadLock
236
 
 
237
 
            self._lock = ReadLock(self.controlfilename('branch-lock'))
 
238
            self._lock = self._transport.lock_read(
 
239
                    self._rel_controlfilename('branch-lock'))
238
240
            self._lock_mode = 'r'
239
241
            self._lock_count = 1
240
242
                        
252
254
 
253
255
    def abspath(self, name):
254
256
        """Return absolute filename for something in the branch"""
255
 
        return os.path.join(self.base, name)
 
257
        return self._transport.abspath(name)
256
258
 
257
259
    def relpath(self, path):
258
260
        """Return path relative to this branch of something inside it.
259
261
 
260
262
        Raises an error if path is not in this branch."""
261
 
        return _relpath(self.base, path)
 
263
        return self._transport.relpath(path)
 
264
 
 
265
 
 
266
    def _rel_controlfilename(self, file_or_path):
 
267
        if isinstance(file_or_path, basestring):
 
268
            file_or_path = [file_or_path]
 
269
        return [bzrlib.BZRDIR] + file_or_path
262
270
 
263
271
    def controlfilename(self, file_or_path):
264
272
        """Return location relative to branch."""
265
 
        if isinstance(file_or_path, basestring):
266
 
            file_or_path = [file_or_path]
267
 
        return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
 
273
        return self._transport.abspath(self._rel_controlfilename(file_or_path))
268
274
 
269
275
 
270
276
    def controlfile(self, file_or_path, mode='r'):
278
284
        Controlfiles should almost never be opened in write mode but
279
285
        rather should be atomically copied and replaced using atomicfile.
280
286
        """
281
 
 
282
 
        fn = self.controlfilename(file_or_path)
283
 
 
284
 
        if mode == 'rb' or mode == 'wb':
285
 
            return file(fn, mode)
286
 
        elif mode == 'r' or mode == 'w':
287
 
            # open in binary mode anyhow so there's no newline translation;
288
 
            # codecs uses line buffering by default; don't want that.
289
 
            import codecs
290
 
            return codecs.open(fn, mode + 'b', 'utf-8',
291
 
                               buffering=60000)
 
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")
292
300
        else:
293
301
            raise BzrError("invalid controlfile mode %r" % mode)
294
302
 
 
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)
 
331
 
295
332
    def _make_control(self):
296
333
        from bzrlib.inventory import Inventory
 
334
        from cStringIO import StringIO
297
335
        
298
 
        os.mkdir(self.controlfilename([]))
299
 
        self.controlfile('README', 'w').write(
300
 
            "This is a Bazaar-NG control directory.\n"
301
 
            "Do not change any files in this directory.\n")
302
 
        self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
303
 
        for d in ('text-store', 'inventory-store', 'revision-store'):
304
 
            os.mkdir(self.controlfilename(d))
305
 
        for f in ('revision-history', 'merged-patches',
306
 
                  'pending-merged-patches', 'branch-name',
307
 
                  'branch-lock',
308
 
                  'pending-merges'):
309
 
            self.controlfile(f, 'w').write('')
310
 
        mutter('created control directory in ' + self.base)
311
 
 
 
336
        # Create an empty inventory
 
337
        sio = StringIO()
312
338
        # if we want per-tree root ids then this is the place to set
313
339
        # them; they're not needed for now and so ommitted for
314
340
        # simplicity.
315
 
        f = self.controlfile('inventory','w')
316
 
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), f)
 
341
        bzrlib.xml.serializer_v4.write_inventory(Inventory(), sio)
317
342
 
 
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)
318
359
 
319
360
    def _check_format(self):
320
361
        """Check this branch format is supported.
334
375
                           ['use a different bzr version',
335
376
                            'or remove the .bzr directory and "bzr init" again'])
336
377
 
 
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
 
337
403
    def get_root_id(self):
338
404
        """Return the id of this branches root"""
339
405
        inv = self.read_working_inventory()
370
436
        That is to say, the inventory describing changes underway, that
371
437
        will be committed to the next revision.
372
438
        """
373
 
        from bzrlib.atomicfile import AtomicFile
374
 
        
 
439
        from cStringIO import StringIO
375
440
        self.lock_write()
376
441
        try:
377
 
            f = AtomicFile(self.controlfilename('inventory'), 'wb')
378
 
            try:
379
 
                bzrlib.xml.serializer_v4.write_inventory(inv, f)
380
 
                f.commit()
381
 
            finally:
382
 
                f.close()
 
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)
383
447
        finally:
384
448
            self.unlock()
385
449
        
550
614
 
551
615
 
552
616
    def append_revision(self, *revision_ids):
553
 
        from bzrlib.atomicfile import AtomicFile
554
 
 
555
617
        for revision_id in revision_ids:
556
618
            mutter("add {%s} to revision-history" % revision_id)
557
619
 
558
620
        rev_history = self.revision_history()
559
621
        rev_history.extend(revision_ids)
560
622
 
561
 
        f = AtomicFile(self.controlfilename('revision-history'))
 
623
        self.lock_write()
562
624
        try:
563
 
            for rev_id in rev_history:
564
 
                print >>f, rev_id
565
 
            f.commit()
 
625
            self.put_controlfile('revision-history', '\n'.join(rev_history))
566
626
        finally:
567
 
            f.close()
 
627
            self.unlock()
568
628
 
569
629
 
570
630
    def get_revision_xml_file(self, revision_id):
623
683
        return compare_trees(old_tree, new_tree)
624
684
 
625
685
        
 
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
626
690
 
 
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
            
627
714
    def get_revision_sha1(self, revision_id):
628
715
        """Hash the stored value of a revision, and return it."""
629
716
        # In the future, revision entries will be signed. At that
640
727
 
641
728
        TODO: Perhaps for this and similar methods, take a revision
642
729
               parameter which can be either an integer revno or a
643
 
               string hash."""
644
 
        from bzrlib.inventory import Inventory
645
 
 
 
730
               string hash.
 
731
        """
646
732
        f = self.get_inventory_xml_file(inventory_id)
647
733
        return bzrlib.xml.serializer_v4.read_inventory(f)
648
734
 
649
735
 
650
736
    def get_inventory_xml(self, inventory_id):
651
737
        """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?
652
740
        return self.inventory_store[inventory_id]
653
741
 
654
742
    get_inventory_xml_file = get_inventory_xml
655
743
            
 
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()
656
763
 
657
764
    def get_inventory_sha1(self, inventory_id):
658
765
        """Return the sha1 hash of the inventory entry
820
927
        pb.clear()
821
928
 
822
929
    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)
823
933
        if hasattr(other.revision_store, "prefetch"):
824
934
            other.revision_store.prefetch(revision_ids)
825
935
        if hasattr(other.inventory_store, "prefetch"):
826
 
            inventory_ids = []
827
 
            for rev_id in revision_ids:
828
 
                try:
829
 
                    revision = other.get_revision(rev_id).inventory_id
830
 
                    inventory_ids.append(revision)
831
 
                except bzrlib.errors.NoSuchRevision:
832
 
                    pass
833
936
            other.inventory_store.prefetch(inventory_ids)
834
937
 
835
938
        if pb is None:
836
939
            pb = bzrlib.ui.ui_factory.progress_bar()
837
940
                
838
 
        revisions = []
 
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)
839
944
        needed_texts = set()
840
 
        i = 0
841
945
 
842
946
        failures = set()
843
 
        for i, rev_id in enumerate(revision_ids):
 
947
        good_revisions = set()
 
948
        for i, (inv, rev_id) in enumerate(zip(inventories, revision_ids)):
844
949
            pb.update('fetching revision', i+1, len(revision_ids))
845
 
            try:
846
 
                rev = other.get_revision(rev_id)
847
 
            except bzrlib.errors.NoSuchRevision:
 
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:
848
961
                failures.add(rev_id)
849
962
                continue
 
963
            else:
 
964
                good_revisions.add(rev_id)
850
965
 
851
 
            revisions.append(rev)
852
 
            inv = other.get_inventory(str(rev.inventory_id))
 
966
            text_ids = []
853
967
            for key, entry in inv.iter_entries():
854
968
                if entry.text_id is None:
855
969
                    continue
856
 
                if entry.text_id not in self.text_store:
857
 
                    needed_texts.add(entry.text_id)
 
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)
858
976
 
859
977
        pb.clear()
860
978
                    
861
979
        count, cp_fail = self.text_store.copy_multi(other.text_store, 
862
980
                                                    needed_texts)
863
981
        #print "Added %d texts." % count 
864
 
        inventory_ids = [ f.inventory_id for f in revisions ]
865
 
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store, 
866
 
                                                         inventory_ids)
 
982
        count, cp_fail = self.inventory_store.copy_multi(other.inventory_store,
 
983
                                                         good_revisions)
867
984
        #print "Added %d inventories." % count 
868
 
        revision_ids = [ f.revision_id for f in revisions]
869
 
 
870
985
        count, cp_fail = self.revision_store.copy_multi(other.revision_store, 
871
 
                                                          revision_ids,
 
986
                                                          good_revisions,
872
987
                                                          permit_failure=True)
873
988
        assert len(cp_fail) == 0 
874
989
        return count, failures
1120
1235
    def working_tree(self):
1121
1236
        """Return a `Tree` for the working copy."""
1122
1237
        from bzrlib.workingtree import WorkingTree
1123
 
        return WorkingTree(self.base, self.read_working_inventory())
 
1238
        # TODO: In the future, WorkingTree should utilize Transport
 
1239
        return WorkingTree(self._transport.base, self.read_working_inventory())
1124
1240
 
1125
1241
 
1126
1242
    def basis_tree(self):
1305
1421
        These are revisions that have been merged into the working
1306
1422
        directory but not yet committed.
1307
1423
        """
1308
 
        cfn = self.controlfilename('pending-merges')
1309
 
        if not os.path.exists(cfn):
 
1424
        cfn = self._rel_controlfilename('pending-merges')
 
1425
        if not self._transport.has(cfn):
1310
1426
            return []
1311
1427
        p = []
1312
1428
        for l in self.controlfile('pending-merges', 'r').readlines():
1314
1430
        return p
1315
1431
 
1316
1432
 
1317
 
    def add_pending_merge(self, revision_id):
 
1433
    def add_pending_merge(self, *revision_ids):
1318
1434
        from bzrlib.revision import validate_revision_id
1319
1435
 
1320
 
        validate_revision_id(revision_id)
 
1436
        for rev_id in revision_ids:
 
1437
            validate_revision_id(rev_id)
1321
1438
 
1322
1439
        p = self.pending_merges()
1323
 
        if revision_id in p:
1324
 
            return
1325
 
        p.append(revision_id)
1326
 
        self.set_pending_merges(p)
1327
 
 
 
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)
1328
1448
 
1329
1449
    def set_pending_merges(self, rev_list):
1330
 
        from bzrlib.atomicfile import AtomicFile
1331
1450
        self.lock_write()
1332
1451
        try:
1333
 
            f = AtomicFile(self.controlfilename('pending-merges'))
1334
 
            try:
1335
 
                for l in rev_list:
1336
 
                    print >>f, l
1337
 
                f.commit()
1338
 
            finally:
1339
 
                f.close()
 
1452
            self.put_controlfile('pending-merges', '\n'.join(rev_list))
1340
1453
        finally:
1341
1454
            self.unlock()
1342
1455
 
1417
1530
            init = True
1418
1531
        Branch.__init__(self, base, init=init)
1419
1532
        for d in dirs:
1420
 
            os.mkdir(self.abspath(d))
 
1533
            self._transport.mkdir(d)
1421
1534
            
1422
1535
        for f in files:
1423
 
            file(os.path.join(self.base, f), 'w').write('content of %s' % f)
 
1536
            self._transport.put(f, 'content of %s' % f)
1424
1537
 
1425
1538
 
1426
1539
    def clone(self):
1459
1572
                for name in files:
1460
1573
                    os.chmod(os.path.join(root, name), 0700)
1461
1574
            rmtree(self.base)
1462
 
        self.base = None
 
1575
        self._transport = None
1463
1576
 
1464
1577
    
1465
1578