30
30
def find_branch(f, **args):
31
if f and (f.startswith('http://') or f.startswith('https://')):
33
return remotebranch.RemoteBranch(f, **args)
35
return Branch(f, **args)
31
from transport import transport
32
return Branch(transport(f), **args)
38
34
def find_cached_branch(f, cache_root, **args):
39
35
from remotebranch import RemoteBranch
154
150
# This should match a prefix with a function which accepts
155
151
REVISION_NAMESPACES = {}
157
def __init__(self, base, init=False, find_root=True):
153
def __init__(self, transport, init=False):
158
154
"""Create new branch object at a particular location.
160
base -- Base directory for the branch.
156
transport -- A Transport object, defining how to access files.
157
(If a string, transport.transport() will be used to
158
create a Transport object)
162
160
init -- If True, create new control files in a previously
163
161
unversioned directory. If False, the branch must already
166
find_root -- If true and init is false, find the root of the
167
existing branch containing base.
169
164
In the test suite, creation of new trees is tested using the
170
165
`ScratchBranch` class.
172
from bzrlib.store import ImmutableStore
167
from bzrlib.store import CompressedTextStore
169
if isinstance(transport, basestring):
170
from transport import transport as get_transport
171
transport = get_transport(transport)
173
self.transport = transport
174
self.base = os.path.realpath(base)
175
175
self._make_control()
177
self.base = find_branch_root(base)
179
self.base = os.path.realpath(base)
180
if not isdir(self.controlfilename('.')):
181
from errors import NotBranchError
182
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
183
['use "bzr init" to initialize a new working tree',
184
'current bzr can only operate from top-of-tree'])
185
177
self._check_format()
187
self.text_store = ImmutableStore(self.controlfilename('text-store'))
188
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
189
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
192
180
def __str__(self):
193
181
return '%s(%r)' % (self.__class__.__name__, self.base)
259
249
return _relpath(self.base, path)
252
def _rel_controlfilename(self, file_or_path):
253
if isinstance(file_or_path, basestring):
254
file_or_path = [file_or_path]
255
return [bzrlib.BZRDIR] + file_or_path
262
257
def controlfilename(self, file_or_path):
263
258
"""Return location relative to branch."""
264
if isinstance(file_or_path, basestring):
265
file_or_path = [file_or_path]
266
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
259
return self.transport.abspath(self._rel_controlfilename(file_or_path))
269
262
def controlfile(self, file_or_path, mode='r'):
277
270
Controlfiles should almost never be opened in write mode but
278
271
rather should be atomically copied and replaced using atomicfile.
281
fn = self.controlfilename(file_or_path)
283
if mode == 'rb' or mode == 'wb':
284
return file(fn, mode)
285
elif mode == 'r' or mode == 'w':
286
# open in binary mode anyhow so there's no newline translation;
287
# codecs uses line buffering by default; don't want that.
289
return codecs.open(fn, mode + 'b', 'utf-8',
275
relpath = self._rel_controlfilename(file_or_path)
276
#TODO: codecs.open() buffers linewise, so it was overloaded with
277
# a much larger buffer, do we need to do the same for getreader/getwriter?
279
# TODO: Try to use transport.put() rather than branch.controlfile(mode='w')
281
return self.transport.get(relpath)
283
return self.transport.open(relpath)
285
return codecs.getreader('utf-8')(self.transport.get(relpath))
287
return codecs.getwriter(bzrlib.user_encoding)(
288
self.transport.open(relpath), errors='replace')
292
290
raise BzrError("invalid controlfile mode %r" % mode)
296
293
def _make_control(self):
297
294
from bzrlib.inventory import Inventory
298
295
from bzrlib.xml import pack_xml
300
os.mkdir(self.controlfilename([]))
301
self.controlfile('README', 'w').write(
297
self.transport.mkdir(self.controlfilename([]))
298
self.transport.put(self._rel_controlfilename('README'),
302
299
"This is a Bazaar-NG control directory.\n"
303
300
"Do not change any files in this directory.\n")
304
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
301
self.transport.put(self._rel_controlfilename('branch-format'),
305
303
for d in ('text-store', 'inventory-store', 'revision-store'):
306
os.mkdir(self.controlfilename(d))
304
self.transport.mkdir(self._rel_controlfilename(d))
307
305
for f in ('revision-history', 'merged-patches',
308
306
'pending-merged-patches', 'branch-name',
310
308
'pending-merges'):
311
self.controlfile(f, 'w').write('')
309
self.transport.put(self._rel_controlfilename(f), '')
312
310
mutter('created control directory in ' + self.base)
312
# TODO: Try and do this with self.transport.put() instead
314
313
pack_xml(Inventory(), self.controlfile('inventory','w'))
332
331
['use a different bzr version',
333
332
'or remove the .bzr directory and "bzr init" again'])
334
# We know that the format is the currently supported one.
335
# So create the rest of the entries.
337
relpath = self._rel_controlfilename(name)
338
return CompressedTextStore(self.transport.clone(relpath))
340
self.text_store = get_store('text-store')
341
self.revision_store = get_store('revision-store')
342
self.inventory_store = get_store('inventory-store')
337
346
def read_working_inventory(self):
359
368
That is to say, the inventory describing changes underway, that
360
369
will be committed to the next revision.
362
from bzrlib.atomicfile import AtomicFile
363
371
from bzrlib.xml import pack_xml
372
from cStringIO import StringIO
365
373
self.lock_write()
367
f = AtomicFile(self.controlfilename('inventory'), 'wb')
375
# Transport handles atomicity
380
self.transport.put(self._rel_controlfilename('inventory'), sio)
551
559
def append_revision(self, *revision_ids):
552
from bzrlib.atomicfile import AtomicFile
554
560
for revision_id in revision_ids:
555
561
mutter("add {%s} to revision-history" % revision_id)
557
563
rev_history = self.revision_history()
558
564
rev_history.extend(revision_ids)
560
f = AtomicFile(self.controlfilename('revision-history'))
562
for rev_id in rev_history:
568
self.transport.put(self._rel_controlfilename('revision-history'),
569
'\n'.join(rev_history))
569
574
def get_revision(self, revision_id):
833
838
revision_ids = [ f.revision_id for f in revisions]
834
839
count = self.revision_store.copy_multi(other.revision_store,
836
for revision_id in revision_ids:
837
self.append_revision(revision_id)
841
self.append_revision(*revision_ids)
838
842
print "Added %d revisions." % count
1201
1205
These are revisions that have been merged into the working
1202
1206
directory but not yet committed.
1204
cfn = self.controlfilename('pending-merges')
1205
if not os.path.exists(cfn):
1208
cfn = self._rel_controlfilename('pending-merges')
1209
if not self.transport.has(cfn):
1208
1212
for l in self.controlfile('pending-merges', 'r').readlines():