29
30
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
30
31
NoSuchRevision, HistoryMissing, NotBranchError,
31
32
DivergedBranches, LockError, UnlistableStore,
33
UnlistableBranch, NoSuchFile)
33
34
from bzrlib.textui import show_status
34
35
from bzrlib.revision import Revision, validate_revision_id, is_ancestor
35
36
from bzrlib.delta import compare_trees
36
37
from bzrlib.tree import EmptyTree, RevisionTree
37
38
from bzrlib.inventory import Inventory
38
39
from bzrlib.weavestore import WeaveStore
39
from bzrlib.store import copy_all, ImmutableStore
40
from bzrlib.store import copy_all
41
from bzrlib.store.compressed_text import CompressedTextStore
42
from bzrlib.transport import Transport
83
85
return os.sep.join(s)
86
def find_branch_root(f=None):
87
"""Find the branch root enclosing f, or pwd.
89
f may be a filename or a URL.
91
It is not necessary that f exists.
88
def find_branch_root(t):
89
"""Find the branch root enclosing the transport's base.
91
t is a Transport object.
93
It is not necessary that the base of t exists.
93
95
Basically we keep looking up until we find the control directory or
94
96
run into the root. If there isn't one, raises NotBranchError.
98
elif hasattr(os.path, 'realpath'):
99
f = os.path.realpath(f)
101
f = os.path.abspath(f)
102
if not os.path.exists(f):
103
raise BzrError('%r does not exist' % f)
109
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
111
head, tail = os.path.split(f)
100
if t.has(bzrlib.BZRDIR):
102
new_t = t.clone('..')
103
if new_t.base == t.base:
113
104
# reached the root, whatever that may be
114
raise NotBranchError('%s is not in a branch' % orig_f)
105
raise NotBranchError('%s is not in a branch' % orig_base)
120
109
######################################################################
136
125
"""Open a branch which may be of an old format.
138
127
Only local branches are supported."""
139
return LocalBranch(base, find_root=False, relax_version_check=True)
128
return _Branch(base, relax_version_check=True)
143
132
"""Open an existing branch, rooted at 'base' (url)"""
144
if base and (base.startswith('http://') or base.startswith('https://')):
145
from bzrlib.remotebranch import RemoteBranch
146
return RemoteBranch(base, find_root=False)
148
return LocalBranch(base, find_root=False)
133
t = bzrlib.transport.transport(base)
151
137
def open_containing(url):
154
140
This probes for a branch at url, and searches upwards from there.
156
if url and (url.startswith('http://') or url.startswith('https://')):
157
from bzrlib.remotebranch import RemoteBranch
158
return RemoteBranch(url)
160
return LocalBranch(url)
142
t = bzrlib.transport.transport(url)
143
t = find_branch_root(t)
163
147
def initialize(base):
164
148
"""Create a new branch, rooted at 'base' (url)"""
165
if base and (base.startswith('http://') or base.startswith('https://')):
166
from bzrlib.remotebranch import RemoteBranch
167
return RemoteBranch(base, init=True)
169
return LocalBranch(base, init=True)
149
t = bzrlib.transport.transport(base)
150
return _Branch(t, init=True)
171
152
def setup_caching(self, cache_root):
172
153
"""Subclasses that care about caching should override this, and set
225
206
except UnlistableStore:
226
207
raise UnlistableBranch(from_store)
228
def __init__(self, base, init=False, find_root=True,
209
def __init__(self, transport, init=False,
229
210
relax_version_check=False):
230
211
"""Create new branch object at a particular location.
232
base -- Base directory for the branch. May be a file:// url.
213
transport -- A Transport object, defining how to access files.
214
(If a string, transport.transport() will be used to
215
create a Transport object)
234
217
init -- If True, create new control files in a previously
235
218
unversioned directory. If False, the branch must already
238
find_root -- If true and init is false, find the root of the
239
existing branch containing base.
241
221
relax_version_check -- If true, the usual check for the branch
242
222
version is not applied. This is intended only for
243
223
upgrade/recovery type use; it's not guaranteed that
246
226
In the test suite, creation of new trees is tested using the
247
227
`ScratchBranch` class.
229
assert isinstance(transport, Transport)
230
self._transport = transport
250
self.base = os.path.realpath(base)
251
232
self._make_control()
253
self.base = find_branch_root(base)
255
if base.startswith("file://"):
257
self.base = os.path.realpath(base)
258
if not isdir(self.controlfilename('.')):
259
raise NotBranchError('not a bzr branch: %s' % quotefn(base),
260
['use "bzr init" to initialize a '
262
233
self._check_format(relax_version_check)
263
234
cfn = self.controlfilename
264
235
if self._branch_format == 4:
265
self.inventory_store = ImmutableStore(cfn('inventory-store'))
266
self.text_store = ImmutableStore(cfn('text-store'))
236
self.inventory_store = CompressedTextStore(cfn('inventory-store'))
237
self.text_store = CompressedTextStore(cfn('text-store'))
267
238
elif self._branch_format == 5:
268
239
self.control_weaves = WeaveStore(cfn([]))
269
240
self.weave_store = WeaveStore(cfn('weaves'))
288
258
warn("branch %r was not explicitly unlocked" % self)
289
259
self._lock.unlock()
261
# TODO: It might be best to do this somewhere else,
262
# but it is nice for a Branch object to automatically
263
# cache it's information.
264
# Alternatively, we could have the Transport objects cache requests
265
# See the earlier discussion about how major objects (like Branch)
266
# should never expect their __del__ function to run.
267
if hasattr(self, 'cache_root') and self.cache_root is not None:
270
shutil.rmtree(self.cache_root)
273
self.cache_root = None
277
return self._transport.base
280
base = property(_get_base)
291
283
def lock_write(self):
284
# TODO: Upgrade locking to support using a Transport,
285
# and potentially a remote locking protocol
292
286
if self._lock_mode:
293
287
if self._lock_mode != 'w':
294
288
raise LockError("can't upgrade to a write lock from %r" %
296
290
self._lock_count += 1
298
from bzrlib.lock import WriteLock
300
self._lock = WriteLock(self.controlfilename('branch-lock'))
292
self._lock = self._transport.lock_write(
293
self._rel_controlfilename('branch-lock'))
301
294
self._lock_mode = 'w'
302
295
self._lock_count = 1
328
320
def abspath(self, name):
329
321
"""Return absolute filename for something in the branch"""
330
return os.path.join(self.base, name)
322
return self._transport.abspath(name)
332
324
def relpath(self, path):
333
325
"""Return path relative to this branch of something inside it.
335
327
Raises an error if path is not in this branch."""
336
return _relpath(self.base, path)
328
return self._transport.relpath(path)
331
def _rel_controlfilename(self, file_or_path):
332
if isinstance(file_or_path, basestring):
333
file_or_path = [file_or_path]
334
return [bzrlib.BZRDIR] + file_or_path
338
336
def controlfilename(self, file_or_path):
339
337
"""Return location relative to branch."""
340
if isinstance(file_or_path, basestring):
341
file_or_path = [file_or_path]
342
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
338
return self._transport.abspath(self._rel_controlfilename(file_or_path))
345
341
def controlfile(self, file_or_path, mode='r'):
353
349
Controlfiles should almost never be opened in write mode but
354
350
rather should be atomically copied and replaced using atomicfile.
357
fn = self.controlfilename(file_or_path)
359
if mode == 'rb' or mode == 'wb':
360
return file(fn, mode)
361
elif mode == 'r' or mode == 'w':
362
# open in binary mode anyhow so there's no newline translation;
363
# codecs uses line buffering by default; don't want that.
365
return codecs.open(fn, mode + 'b', 'utf-8',
354
relpath = self._rel_controlfilename(file_or_path)
355
#TODO: codecs.open() buffers linewise, so it was overloaded with
356
# a much larger buffer, do we need to do the same for getreader/getwriter?
358
return self._transport.get(relpath)
360
raise BzrError("Branch.controlfile(mode='wb') is not supported, use put_controlfiles")
362
return codecs.getreader('utf-8')(self._transport.get(relpath), errors='replace')
364
raise BzrError("Branch.controlfile(mode='w') is not supported, use put_controlfiles")
368
366
raise BzrError("invalid controlfile mode %r" % mode)
368
def put_controlfile(self, path, f, encode=True):
369
"""Write an entry as a controlfile.
371
:param path: The path to put the file, relative to the .bzr control
373
:param f: A file-like or string object whose contents should be copied.
374
:param encode: If true, encode the contents as utf-8
376
self.put_controlfiles([(path, f)], encode=encode)
378
def put_controlfiles(self, files, encode=True):
379
"""Write several entries as controlfiles.
381
:param files: A list of [(path, file)] pairs, where the path is the directory
382
underneath the bzr control directory
383
:param encode: If true, encode the contents as utf-8
387
for path, f in files:
389
if isinstance(f, basestring):
390
f = f.encode('utf-8', 'replace')
392
f = codecs.getwriter('utf-8')(f, errors='replace')
393
path = self._rel_controlfilename(path)
394
ctrl_files.append((path, f))
395
self._transport.put_multi(ctrl_files)
370
397
def _make_control(self):
371
os.mkdir(self.controlfilename([]))
372
self.controlfile('README', 'w').write(
373
"This is a Bazaar-NG control directory.\n"
374
"Do not change any files in this directory.\n")
375
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT_5)
376
for d in ('text-store', 'revision-store',
378
os.mkdir(self.controlfilename(d))
379
for f in ('revision-history',
383
self.controlfile(f, 'w').write('')
384
mutter('created control directory in ' + self.base)
398
from bzrlib.inventory import Inventory
400
# Create an empty inventory
386
402
# if we want per-tree root ids then this is the place to set
387
403
# them; they're not needed for now and so ommitted for
389
f = self.controlfile('inventory','w')
390
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), f)
405
bzrlib.xml5.serializer_v5.write_inventory(Inventory(), sio)
407
dirs = [[], 'revision-store', 'weaves']
409
"This is a Bazaar-NG control directory.\n"
410
"Do not change any files in this directory.\n"),
411
('branch-format', BZR_BRANCH_FORMAT_5),
412
('revision-history', ''),
415
('pending-merges', ''),
416
('inventory', sio.getvalue())
418
cfn = self._rel_controlfilename
419
self._transport.mkdir_multi([cfn(d) for d in dirs])
420
self.put_controlfiles(files)
421
mutter('created control directory in ' + self._transport.base)
393
423
def _check_format(self, relax_version_check):
394
424
"""Check this branch format is supported.
1143
def add_pending_merge(self, revision_id):
1144
validate_revision_id(revision_id)
1165
def add_pending_merge(self, *revision_ids):
1145
1166
# TODO: Perhaps should check at this point that the
1146
1167
# history of the revision is actually present?
1168
for rev_id in revision_ids:
1169
validate_revision_id(rev_id)
1147
1171
p = self.pending_merges()
1148
if revision_id in p:
1150
p.append(revision_id)
1151
self.set_pending_merges(p)
1173
for rev_id in revision_ids:
1179
self.set_pending_merges(p)
1154
1181
def set_pending_merges(self, rev_list):
1155
from bzrlib.atomicfile import AtomicFile
1156
1182
self.lock_write()
1158
f = AtomicFile(self.controlfilename('pending-merges'))
1184
self.put_controlfile('pending-merges', '\n'.join(rev_list))