1
# Copyright (C) 2005 Canonical Ltd
1
# Copyright (C) 2005, 2006 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
import sys, os, os.path, random, time, sha, sets, types, re, shutil, tempfile
19
import traceback, socket, fnmatch, difflib, time
20
from binascii import hexlify
18
from copy import deepcopy
19
from cStringIO import StringIO
24
from unittest import TestSuite
25
from warnings import warn
23
from inventory import Inventory
24
from trace import mutter, note
25
from tree import Tree, EmptyTree, RevisionTree
26
from inventory import InventoryEntry, Inventory
27
from osutils import isdir, quotefn, isfile, uuid, sha_file, username, \
28
format_date, compact_date, pumpfile, user_email, rand_bytes, splitpath, \
29
joinpath, sha_file, sha_string, file_kind, local_time_offset, appendpath
30
from store import ImmutableStore
31
from revision import Revision
32
from errors import BzrError
33
from textui import show_status
35
BZR_BRANCH_FORMAT = "Bazaar-NG branch, format 0.0.4\n"
36
## TODO: Maybe include checks for common corruption of newlines, etc?
40
def find_branch(f, **args):
41
if f and (f.startswith('http://') or f.startswith('https://')):
43
return remotebranch.RemoteBranch(f, **args)
45
return Branch(f, **args)
49
def _relpath(base, path):
50
"""Return path relative to base, or raise exception.
52
The path may be either an absolute path or a path relative to the
53
current working directory.
55
Lifted out of Branch.relpath for ease of testing.
57
os.path.commonprefix (python2.4) has a bad bug that it works just
58
on string prefixes, assuming that '/u' is a prefix of '/u2'. This
59
avoids that problem."""
60
rp = os.path.abspath(path)
64
while len(head) >= len(base):
67
head, tail = os.path.split(head)
71
from errors import NotBranchError
72
raise NotBranchError("path %r is not within branch %r" % (rp, base))
77
def find_branch_root(f=None):
78
"""Find the branch root enclosing f, or pwd.
80
f may be a filename or a URL.
82
It is not necessary that f exists.
84
Basically we keep looking up until we find the control directory or
88
elif hasattr(os.path, 'realpath'):
89
f = os.path.realpath(f)
91
f = os.path.abspath(f)
92
if not os.path.exists(f):
93
raise BzrError('%r does not exist' % f)
99
if os.path.exists(os.path.join(f, bzrlib.BZRDIR)):
101
head, tail = os.path.split(f)
103
# reached the root, whatever that may be
104
raise BzrError('%r is not in a branch' % orig_f)
107
class DivergedBranches(Exception):
108
def __init__(self, branch1, branch2):
109
self.branch1 = branch1
110
self.branch2 = branch2
111
Exception.__init__(self, "These branches have diverged.")
114
class NoSuchRevision(BzrError):
115
def __init__(self, branch, revision):
117
self.revision = revision
118
msg = "Branch %s has no revision %d" % (branch, revision)
119
BzrError.__init__(self, msg)
28
import bzrlib.bzrdir as bzrdir
29
from bzrlib.config import TreeConfig
30
from bzrlib.decorators import needs_read_lock, needs_write_lock
31
from bzrlib.delta import compare_trees
32
import bzrlib.errors as errors
33
from bzrlib.errors import (BzrError, InvalidRevisionNumber, InvalidRevisionId,
34
NoSuchRevision, HistoryMissing, NotBranchError,
35
DivergedBranches, LockError,
36
UninitializableFormat,
38
UnlistableBranch, NoSuchFile, NotVersionedError,
40
import bzrlib.inventory as inventory
41
from bzrlib.inventory import Inventory
42
from bzrlib.lockable_files import LockableFiles, TransportLock
43
from bzrlib.lockdir import LockDir
44
from bzrlib.osutils import (isdir, quotefn,
45
rename, splitpath, sha_file,
46
file_kind, abspath, normpath, pathjoin,
49
from bzrlib.textui import show_status
50
from bzrlib.trace import mutter, note
51
from bzrlib.tree import EmptyTree, RevisionTree
52
from bzrlib.repository import Repository
53
from bzrlib.revision import (
54
get_intervening_revisions,
59
from bzrlib.store import copy_all
60
from bzrlib.symbol_versioning import *
61
import bzrlib.transactions as transactions
62
from bzrlib.transport import Transport, get_transport
63
from bzrlib.tree import EmptyTree, RevisionTree
68
BZR_BRANCH_FORMAT_4 = "Bazaar-NG branch, format 0.0.4\n"
69
BZR_BRANCH_FORMAT_5 = "Bazaar-NG branch, format 5\n"
70
BZR_BRANCH_FORMAT_6 = "Bazaar-NG branch, format 6\n"
73
# TODO: Maybe include checks for common corruption of newlines, etc?
75
# TODO: Some operations like log might retrieve the same revisions
76
# repeatedly to calculate deltas. We could perhaps have a weakref
77
# cache in memory to make this faster. In general anything can be
78
# cached in memory between lock and unlock operations. .. nb thats
79
# what the transaction identity map provides
122
82
######################################################################
126
86
"""Branch holding a history of revisions.
129
Base directory of the branch.
135
If _lock_mode is true, a positive count of the number of times the
139
Lock object from bzrlib.lock.
89
Base directory/url of the branch.
91
# this is really an instance variable - FIXME move it there
146
def __init__(self, base, init=False, find_root=True):
147
"""Create new branch object at a particular location.
149
base -- Base directory for the branch.
151
init -- If True, create new control files in a previously
152
unversioned directory. If False, the branch must already
155
find_root -- If true and init is false, find the root of the
156
existing branch containing base.
158
In the test suite, creation of new trees is tested using the
159
`ScratchBranch` class.
162
self.base = os.path.realpath(base)
165
self.base = find_branch_root(base)
167
self.base = os.path.realpath(base)
168
if not isdir(self.controlfilename('.')):
169
from errors import NotBranchError
170
raise NotBranchError("not a bzr branch: %s" % quotefn(base),
171
['use "bzr init" to initialize a new working tree',
172
'current bzr can only operate from top-of-tree'])
175
self.text_store = ImmutableStore(self.controlfilename('text-store'))
176
self.revision_store = ImmutableStore(self.controlfilename('revision-store'))
177
self.inventory_store = ImmutableStore(self.controlfilename('inventory-store'))
181
return '%s(%r)' % (self.__class__.__name__, self.base)
188
if self._lock_mode or self._lock:
189
from warnings import warn
190
warn("branch %r was not explicitly unlocked" % self)
95
def __init__(self, *ignored, **ignored_too):
96
raise NotImplementedError('The Branch class is abstract')
99
@deprecated_method(zero_eight)
100
def open_downlevel(base):
101
"""Open a branch which may be of an old format."""
102
return Branch.open(base, _unsupported=True)
105
def open(base, _unsupported=False):
106
"""Open the repository rooted at base.
108
For instance, if the repository is at URL/.bzr/repository,
109
Repository.open(URL) -> a Repository instance.
111
control = bzrdir.BzrDir.open(base, _unsupported)
112
return control.open_branch(_unsupported)
115
def open_containing(url):
116
"""Open an existing branch which contains url.
118
This probes for a branch at url, and searches upwards from there.
120
Basically we keep looking up until we find the control directory or
121
run into the root. If there isn't one, raises NotBranchError.
122
If there is one and it is either an unrecognised format or an unsupported
123
format, UnknownFormatError or UnsupportedFormatError are raised.
124
If there is one, it is returned, along with the unused portion of url.
126
control, relpath = bzrdir.BzrDir.open_containing(url)
127
return control.open_branch(), relpath
130
@deprecated_function(zero_eight)
131
def initialize(base):
132
"""Create a new working tree and branch, rooted at 'base' (url)
134
NOTE: This will soon be deprecated in favour of creation
137
return bzrdir.BzrDir.create_standalone_workingtree(base).branch
139
def setup_caching(self, cache_root):
140
"""Subclasses that care about caching should override this, and set
141
up cached stores located under cache_root.
143
# seems to be unused, 2006-01-13 mbp
144
warn('%s is deprecated' % self.setup_caching)
145
self.cache_root = cache_root
148
cfg = self.tree_config()
149
return cfg.get_option(u"nickname", default=self.base.split('/')[-2])
151
def _set_nick(self, nick):
152
cfg = self.tree_config()
153
cfg.set_option(nick, "nickname")
154
assert cfg.get_option("nickname") == nick
156
nick = property(_get_nick, _set_nick)
195
158
def lock_write(self):
197
if self._lock_mode != 'w':
198
from errors import LockError
199
raise LockError("can't upgrade to a write lock from %r" %
201
self._lock_count += 1
203
from bzrlib.lock import WriteLock
205
self._lock = WriteLock(self.controlfilename('branch-lock'))
206
self._lock_mode = 'w'
159
raise NotImplementedError('lock_write is abstract')
211
161
def lock_read(self):
213
assert self._lock_mode in ('r', 'w'), \
214
"invalid lock mode %r" % self._lock_mode
215
self._lock_count += 1
217
from bzrlib.lock import ReadLock
219
self._lock = ReadLock(self.controlfilename('branch-lock'))
220
self._lock_mode = 'r'
162
raise NotImplementedError('lock_read is abstract')
225
164
def unlock(self):
226
if not self._lock_mode:
227
from errors import LockError
228
raise LockError('branch %r is not locked' % (self))
230
if self._lock_count > 1:
231
self._lock_count -= 1
235
self._lock_mode = self._lock_count = None
165
raise NotImplementedError('unlock is abstract')
167
def peek_lock_mode(self):
168
"""Return lock mode for the Branch: 'r', 'w' or None"""
169
raise NotImplementedError(self.peek_lock_mode)
238
171
def abspath(self, name):
239
"""Return absolute filename for something in the branch"""
240
return os.path.join(self.base, name)
243
def relpath(self, path):
244
"""Return path relative to this branch of something inside it.
246
Raises an error if path is not in this branch."""
247
return _relpath(self.base, path)
250
def controlfilename(self, file_or_path):
251
"""Return location relative to branch."""
252
if isinstance(file_or_path, types.StringTypes):
253
file_or_path = [file_or_path]
254
return os.path.join(self.base, bzrlib.BZRDIR, *file_or_path)
257
def controlfile(self, file_or_path, mode='r'):
258
"""Open a control file for this branch.
260
There are two classes of file in the control directory: text
261
and binary. binary files are untranslated byte streams. Text
262
control files are stored with Unix newlines and in UTF-8, even
263
if the platform or locale defaults are different.
265
Controlfiles should almost never be opened in write mode but
266
rather should be atomically copied and replaced using atomicfile.
269
fn = self.controlfilename(file_or_path)
271
if mode == 'rb' or mode == 'wb':
272
return file(fn, mode)
273
elif mode == 'r' or mode == 'w':
274
# open in binary mode anyhow so there's no newline translation;
275
# codecs uses line buffering by default; don't want that.
277
return codecs.open(fn, mode + 'b', 'utf-8',
280
raise BzrError("invalid controlfile mode %r" % mode)
284
def _make_control(self):
285
os.mkdir(self.controlfilename([]))
286
self.controlfile('README', 'w').write(
287
"This is a Bazaar-NG control directory.\n"
288
"Do not change any files in this directory.\n")
289
self.controlfile('branch-format', 'w').write(BZR_BRANCH_FORMAT)
290
for d in ('text-store', 'inventory-store', 'revision-store'):
291
os.mkdir(self.controlfilename(d))
292
for f in ('revision-history', 'merged-patches',
293
'pending-merged-patches', 'branch-name',
295
self.controlfile(f, 'w').write('')
296
mutter('created control directory in ' + self.base)
297
Inventory().write_xml(self.controlfile('inventory','w'))
300
def _check_format(self):
301
"""Check this branch format is supported.
303
The current tool only supports the current unstable format.
305
In the future, we might need different in-memory Branch
306
classes to support downlevel branches. But not yet.
308
# This ignores newlines so that we can open branches created
309
# on Windows from Linux and so on. I think it might be better
310
# to always make all internal files in unix format.
311
fmt = self.controlfile('branch-format', 'r').read()
312
fmt.replace('\r\n', '')
313
if fmt != BZR_BRANCH_FORMAT:
314
raise BzrError('sorry, branch format %r not supported' % fmt,
315
['use a different bzr version',
316
'or remove the .bzr directory and "bzr init" again'])
320
def read_working_inventory(self):
321
"""Read the working inventory."""
323
# ElementTree does its own conversion from UTF-8, so open in
327
inv = Inventory.read_xml(self.controlfile('inventory', 'rb'))
328
mutter("loaded inventory of %d items in %f"
329
% (len(inv), time.time() - before))
335
def _write_inventory(self, inv):
336
"""Update the working inventory.
338
That is to say, the inventory describing changes underway, that
339
will be committed to the next revision.
341
## TODO: factor out to atomicfile? is rename safe on windows?
342
## TODO: Maybe some kind of clean/dirty marker on inventory?
343
tmpfname = self.controlfilename('inventory.tmp')
344
tmpf = file(tmpfname, 'wb')
347
inv_fname = self.controlfilename('inventory')
348
if sys.platform == 'win32':
350
os.rename(tmpfname, inv_fname)
351
mutter('wrote working inventory')
354
inventory = property(read_working_inventory, _write_inventory, None,
355
"""Inventory for the working copy.""")
358
def add(self, files, verbose=False, ids=None):
359
"""Make files versioned.
361
Note that the command line normally calls smart_add instead.
363
This puts the files in the Added state, so that they will be
364
recorded by the next commit.
367
List of paths to add, relative to the base of the tree.
370
If set, use these instead of automatically generated ids.
371
Must be the same length as the list of files, but may
372
contain None for ids that are to be autogenerated.
374
TODO: Perhaps have an option to add the ids even if the files do
377
TODO: Perhaps return the ids of the files? But then again it
378
is easy to retrieve them if they're needed.
380
TODO: Adding a directory should optionally recurse down and
381
add all non-ignored children. Perhaps do that in a
384
# TODO: Re-adding a file that is removed in the working copy
385
# should probably put it back with the previous ID.
386
if isinstance(files, types.StringTypes):
387
assert(ids is None or isinstance(ids, types.StringTypes))
393
ids = [None] * len(files)
395
assert(len(ids) == len(files))
399
inv = self.read_working_inventory()
400
for f,file_id in zip(files, ids):
401
if is_control_file(f):
402
raise BzrError("cannot add control file %s" % quotefn(f))
407
raise BzrError("cannot add top-level %r" % f)
409
fullpath = os.path.normpath(self.abspath(f))
412
kind = file_kind(fullpath)
414
# maybe something better?
415
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
417
if kind != 'file' and kind != 'directory':
418
raise BzrError('cannot add: not a regular file or directory: %s' % quotefn(f))
421
file_id = gen_file_id(f)
422
inv.add_path(f, kind=kind, file_id=file_id)
425
show_status('A', kind, quotefn(f))
427
mutter("add file %s file_id:{%s} kind=%r" % (f, file_id, kind))
429
self._write_inventory(inv)
434
def print_file(self, file, revno):
172
"""Return absolute filename for something in the branch
174
XXX: Robert Collins 20051017 what is this used for? why is it a branch
175
method and not a tree method.
177
raise NotImplementedError('abspath is abstract')
179
def bind(self, other):
180
"""Bind the local branch the other branch.
182
:param other: The branch to bind to
185
raise errors.UpgradeRequired(self.base)
188
def fetch(self, from_branch, last_revision=None, pb=None):
189
"""Copy revisions from from_branch into this branch.
191
:param from_branch: Where to copy from.
192
:param last_revision: What revision to stop at (None for at the end
194
:param pb: An optional progress bar to use.
196
Returns the copied revision count and the failed revisions in a tuple:
199
if self.base == from_branch.base:
202
nested_pb = bzrlib.ui.ui_factory.nested_progress_bar()
207
from_branch.lock_read()
209
if last_revision is None:
210
pb.update('get source history')
211
from_history = from_branch.revision_history()
213
last_revision = from_history[-1]
215
# no history in the source branch
216
last_revision = NULL_REVISION
217
return self.repository.fetch(from_branch.repository,
218
revision_id=last_revision,
221
if nested_pb is not None:
225
def get_bound_location(self):
226
"""Return the URL of the rbanch we are bound to.
228
Older format branches cannot bind, please be sure to use a metadir
233
def get_master_branch(self):
234
"""Return the branch we are bound to.
236
:return: Either a Branch, or None
240
def get_root_id(self):
241
"""Return the id of this branches root"""
242
raise NotImplementedError('get_root_id is abstract')
244
def print_file(self, file, revision_id):
435
245
"""Print `file` to stdout."""
438
tree = self.revision_tree(self.lookup_revision(revno))
439
# use inventory as it was in that revision
440
file_id = tree.inventory.path2id(file)
442
raise BzrError("%r is not present in revision %d" % (file, revno))
443
tree.print_file(file_id)
448
def remove(self, files, verbose=False):
449
"""Mark nominated files for removal from the inventory.
451
This does not remove their text. This does not run on
453
TODO: Refuse to remove modified files unless --force is given?
455
TODO: Do something useful with directories.
457
TODO: Should this remove the text or not? Tough call; not
458
removing may be useful and the user can just use use rm, and
459
is the opposite of add. Removing it is consistent with most
460
other tools. Maybe an option.
462
## TODO: Normalize names
463
## TODO: Remove nested loops; better scalability
464
if isinstance(files, types.StringTypes):
470
tree = self.working_tree()
473
# do this before any modifications
477
raise BzrError("cannot remove unversioned file %s" % quotefn(f))
478
mutter("remove inventory entry %s {%s}" % (quotefn(f), fid))
480
# having remove it, it must be either ignored or unknown
481
if tree.is_ignored(f):
485
show_status(new_status, inv[fid].kind, quotefn(f))
488
self._write_inventory(inv)
493
# FIXME: this doesn't need to be a branch method
494
def set_inventory(self, new_inventory_list):
496
for path, file_id, parent, kind in new_inventory_list:
497
name = os.path.basename(path)
500
inv.add(InventoryEntry(file_id, name, kind, parent))
501
self._write_inventory(inv)
505
"""Return all unknown files.
507
These are files in the working directory that are not versioned or
508
control files or ignored.
510
>>> b = ScratchBranch(files=['foo', 'foo~'])
511
>>> list(b.unknowns())
514
>>> list(b.unknowns())
517
>>> list(b.unknowns())
520
return self.working_tree().unknowns()
523
def append_revision(self, revision_id):
524
mutter("add {%s} to revision-history" % revision_id)
525
rev_history = self.revision_history()
527
tmprhname = self.controlfilename('revision-history.tmp')
528
rhname = self.controlfilename('revision-history')
530
f = file(tmprhname, 'wt')
531
rev_history.append(revision_id)
532
f.write('\n'.join(rev_history))
536
if sys.platform == 'win32':
538
os.rename(tmprhname, rhname)
542
def get_revision(self, revision_id):
543
"""Return the Revision object for a named revision"""
544
if not revision_id or not isinstance(revision_id, basestring):
545
raise ValueError('invalid revision-id: %r' % revision_id)
546
r = Revision.read_xml(self.revision_store[revision_id])
547
assert r.revision_id == revision_id
550
def get_revision_sha1(self, revision_id):
551
"""Hash the stored value of a revision, and return it."""
552
# In the future, revision entries will be signed. At that
553
# point, it is probably best *not* to include the signature
554
# in the revision hash. Because that lets you re-sign
555
# the revision, (add signatures/remove signatures) and still
556
# have all hash pointers stay consistent.
557
# But for now, just hash the contents.
558
return sha_file(self.revision_store[revision_id])
561
def get_inventory(self, inventory_id):
562
"""Get Inventory object by hash.
564
TODO: Perhaps for this and similar methods, take a revision
565
parameter which can be either an integer revno or a
567
i = Inventory.read_xml(self.inventory_store[inventory_id])
570
def get_inventory_sha1(self, inventory_id):
571
"""Return the sha1 hash of the inventory entry
573
return sha_file(self.inventory_store[inventory_id])
576
def get_revision_inventory(self, revision_id):
577
"""Return inventory of a past revision."""
578
if revision_id == None:
581
return self.get_inventory(self.get_revision(revision_id).inventory_id)
246
raise NotImplementedError('print_file is abstract')
248
def append_revision(self, *revision_ids):
249
raise NotImplementedError('append_revision is abstract')
251
def set_revision_history(self, rev_history):
252
raise NotImplementedError('set_revision_history is abstract')
584
254
def revision_history(self):
585
"""Return sequence of revision hashes on to this branch.
587
>>> ScratchBranch().revision_history()
592
return [l.rstrip('\r\n') for l in
593
self.controlfile('revision-history', 'r').readlines()]
598
def common_ancestor(self, other, self_revno=None, other_revno=None):
601
>>> sb = ScratchBranch(files=['foo', 'foo~'])
602
>>> sb.common_ancestor(sb) == (None, None)
604
>>> commit.commit(sb, "Committing first revision", verbose=False)
605
>>> sb.common_ancestor(sb)[0]
607
>>> clone = sb.clone()
608
>>> commit.commit(sb, "Committing second revision", verbose=False)
609
>>> sb.common_ancestor(sb)[0]
611
>>> sb.common_ancestor(clone)[0]
613
>>> commit.commit(clone, "Committing divergent second revision",
615
>>> sb.common_ancestor(clone)[0]
617
>>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
619
>>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
621
>>> clone2 = sb.clone()
622
>>> sb.common_ancestor(clone2)[0]
624
>>> sb.common_ancestor(clone2, self_revno=1)[0]
626
>>> sb.common_ancestor(clone2, other_revno=1)[0]
629
my_history = self.revision_history()
630
other_history = other.revision_history()
631
if self_revno is None:
632
self_revno = len(my_history)
633
if other_revno is None:
634
other_revno = len(other_history)
635
indices = range(min((self_revno, other_revno)))
638
if my_history[r] == other_history[r]:
639
return r+1, my_history[r]
642
def enum_history(self, direction):
643
"""Return (revno, revision_id) for history of branch.
646
'forward' is from earliest to latest
647
'reverse' is from latest to earliest
649
rh = self.revision_history()
650
if direction == 'forward':
655
elif direction == 'reverse':
661
raise ValueError('invalid history direction', direction)
255
"""Return sequence of revision hashes on to this branch."""
256
raise NotImplementedError('revision_history is abstract')
665
259
"""Return current revision number for this branch.
897
384
Note that to_name is only the last component of the new name;
898
385
this doesn't change the directory.
902
## TODO: Option to move IDs only
903
assert not isinstance(from_paths, basestring)
904
tree = self.working_tree()
906
to_abs = self.abspath(to_name)
907
if not isdir(to_abs):
908
raise BzrError("destination %r is not a directory" % to_abs)
909
if not tree.has_filename(to_name):
910
raise BzrError("destination %r not in working directory" % to_abs)
911
to_dir_id = inv.path2id(to_name)
912
if to_dir_id == None and to_name != '':
913
raise BzrError("destination %r is not a versioned directory" % to_name)
914
to_dir_ie = inv[to_dir_id]
915
if to_dir_ie.kind not in ('directory', 'root_directory'):
916
raise BzrError("destination %r is not a directory" % to_abs)
918
to_idpath = inv.get_idpath(to_dir_id)
921
if not tree.has_filename(f):
922
raise BzrError("%r does not exist in working tree" % f)
923
f_id = inv.path2id(f)
925
raise BzrError("%r is not versioned" % f)
926
name_tail = splitpath(f)[-1]
927
dest_path = appendpath(to_name, name_tail)
928
if tree.has_filename(dest_path):
929
raise BzrError("destination %r already exists" % dest_path)
930
if f_id in to_idpath:
931
raise BzrError("can't move %r to a subdirectory of itself" % f)
933
# OK, so there's a race here, it's possible that someone will
934
# create a file in this interval and then the rename might be
935
# left half-done. But we should have caught most problems.
938
name_tail = splitpath(f)[-1]
939
dest_path = appendpath(to_name, name_tail)
940
print "%s => %s" % (f, dest_path)
941
inv.rename(inv.path2id(f), to_dir_id, name_tail)
943
os.rename(self.abspath(f), self.abspath(dest_path))
945
raise BzrError("failed to rename %r to %r: %s" % (f, dest_path, e[1]),
946
["rename rolled back"])
948
self._write_inventory(inv)
954
class ScratchBranch(Branch):
955
"""Special test class: a branch that cleans up after itself.
957
>>> b = ScratchBranch()
965
def __init__(self, files=[], dirs=[], base=None):
966
"""Make a test branch.
968
This creates a temporary directory and runs init-tree in it.
970
If any files are listed, they are created in the working copy.
974
base = tempfile.mkdtemp()
976
Branch.__init__(self, base, init=init)
978
os.mkdir(self.abspath(d))
387
This returns a list of (from_path, to_path) pairs for each
390
raise NotImplementedError('move is abstract')
392
def get_parent(self):
393
"""Return the parent location of the branch.
395
This is the default location for push/pull/missing. The usual
396
pattern is that the user can override it by specifying a
399
raise NotImplementedError('get_parent is abstract')
401
def get_push_location(self):
402
"""Return the None or the location to push this branch to."""
403
raise NotImplementedError('get_push_location is abstract')
405
def set_push_location(self, location):
406
"""Set a new push location for this branch."""
407
raise NotImplementedError('set_push_location is abstract')
409
def set_parent(self, url):
410
raise NotImplementedError('set_parent is abstract')
414
"""Synchronise this branch with the master branch if any.
416
:return: None or the last_revision pivoted out during the update.
420
def check_revno(self, revno):
422
Check whether a revno corresponds to any revision.
423
Zero (the NULL revision) is considered valid.
426
self.check_real_revno(revno)
981
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
986
>>> orig = ScratchBranch(files=["file1", "file2"])
987
>>> clone = orig.clone()
988
>>> os.path.samefile(orig.base, clone.base)
990
>>> os.path.isfile(os.path.join(clone.base, "file1"))
993
base = tempfile.mkdtemp()
995
shutil.copytree(self.base, base, symlinks=True)
996
return ScratchBranch(base=base)
428
def check_real_revno(self, revno):
430
Check whether a revno corresponds to a real revision.
431
Zero (the NULL revision) is considered invalid
433
if revno < 1 or revno > self.revno():
434
raise InvalidRevisionNumber(revno)
437
def clone(self, *args, **kwargs):
438
"""Clone this branch into to_bzrdir preserving all semantic values.
440
revision_id: if not None, the revision history in the new branch will
441
be truncated to end with revision_id.
443
# for API compatability, until 0.8 releases we provide the old api:
444
# def clone(self, to_location, revision=None, basis_branch=None, to_branch_format=None):
445
# after 0.8 releases, the *args and **kwargs should be changed:
446
# def clone(self, to_bzrdir, revision_id=None):
447
if (kwargs.get('to_location', None) or
448
kwargs.get('revision', None) or
449
kwargs.get('basis_branch', None) or
450
(len(args) and isinstance(args[0], basestring))):
451
# backwards compatability api:
452
warn("Branch.clone() has been deprecated for BzrDir.clone() from"
453
" bzrlib 0.8.", DeprecationWarning, stacklevel=3)
456
basis_branch = args[2]
458
basis_branch = kwargs.get('basis_branch', None)
460
basis = basis_branch.bzrdir
465
revision_id = args[1]
467
revision_id = kwargs.get('revision', None)
472
# no default to raise if not provided.
473
url = kwargs.get('to_location')
474
return self.bzrdir.clone(url,
475
revision_id=revision_id,
476
basis=basis).open_branch()
478
# generate args by hand
480
revision_id = args[1]
482
revision_id = kwargs.get('revision_id', None)
486
# no default to raise if not provided.
487
to_bzrdir = kwargs.get('to_bzrdir')
488
result = self._format.initialize(to_bzrdir)
489
self.copy_content_into(result, revision_id=revision_id)
493
def sprout(self, to_bzrdir, revision_id=None):
494
"""Create a new line of development from the branch, into to_bzrdir.
496
revision_id: if not None, the revision history in the new branch will
497
be truncated to end with revision_id.
499
result = self._format.initialize(to_bzrdir)
500
self.copy_content_into(result, revision_id=revision_id)
501
result.set_parent(self.bzrdir.root_transport.base)
505
def copy_content_into(self, destination, revision_id=None):
506
"""Copy the content of self into destination.
508
revision_id: if not None, the revision history in the new branch will
509
be truncated to end with revision_id.
511
new_history = self.revision_history()
512
if revision_id is not None:
514
new_history = new_history[:new_history.index(revision_id) + 1]
516
rev = self.repository.get_revision(revision_id)
517
new_history = rev.get_history(self.repository)[1:]
518
destination.set_revision_history(new_history)
519
parent = self.get_parent()
521
destination.set_parent(parent)
524
class BranchFormat(object):
525
"""An encapsulation of the initialization and open routines for a format.
527
Formats provide three things:
528
* An initialization routine,
532
Formats are placed in an dict by their format string for reference
533
during branch opening. Its not required that these be instances, they
534
can be classes themselves with class methods - it simply depends on
535
whether state is needed for a given format or not.
537
Once a format is deprecated, just deprecate the initialize and open
538
methods on the format class. Do not deprecate the object, as the
539
object will be created every time regardless.
542
_default_format = None
543
"""The default format used for new branches."""
546
"""The known formats."""
549
def find_format(klass, a_bzrdir):
550
"""Return the format for the branch object in a_bzrdir."""
552
transport = a_bzrdir.get_branch_transport(None)
553
format_string = transport.get("format").read()
554
return klass._formats[format_string]
556
raise NotBranchError(path=transport.base)
558
raise errors.UnknownFormatError(format_string)
561
def get_default_format(klass):
562
"""Return the current default format."""
563
return klass._default_format
565
def get_format_string(self):
566
"""Return the ASCII format string that identifies this format."""
567
raise NotImplementedError(self.get_format_string)
569
def initialize(self, a_bzrdir):
570
"""Create a branch of this format in a_bzrdir."""
571
raise NotImplementedError(self.initialized)
573
def is_supported(self):
574
"""Is this format supported?
576
Supported formats can be initialized and opened.
577
Unsupported formats may not support initialization or committing or
578
some other features depending on the reason for not being supported.
582
def open(self, a_bzrdir, _found=False):
583
"""Return the branch object for a_bzrdir
585
_found is a private parameter, do not use it. It is used to indicate
586
if format probing has already be done.
588
raise NotImplementedError(self.open)
591
def register_format(klass, format):
592
klass._formats[format.get_format_string()] = format
595
def set_default_format(klass, format):
596
klass._default_format = format
599
def unregister_format(klass, format):
600
assert klass._formats[format.get_format_string()] is format
601
del klass._formats[format.get_format_string()]
604
return self.get_format_string().rstrip()
607
class BzrBranchFormat4(BranchFormat):
608
"""Bzr branch format 4.
611
- a revision-history file.
612
- a branch-lock lock file [ to be shared with the bzrdir ]
615
def initialize(self, a_bzrdir):
616
"""Create a branch of this format in a_bzrdir."""
617
mutter('creating branch in %s', a_bzrdir.transport.base)
618
branch_transport = a_bzrdir.get_branch_transport(self)
619
utf8_files = [('revision-history', ''),
622
control_files = LockableFiles(branch_transport, 'branch-lock',
624
control_files.create_lock()
625
control_files.lock_write()
627
for file, content in utf8_files:
628
control_files.put_utf8(file, content)
630
control_files.unlock()
631
return self.open(a_bzrdir, _found=True)
634
super(BzrBranchFormat4, self).__init__()
635
self._matchingbzrdir = bzrdir.BzrDirFormat6()
637
def open(self, a_bzrdir, _found=False):
638
"""Return the branch object for a_bzrdir
640
_found is a private parameter, do not use it. It is used to indicate
641
if format probing has already be done.
644
# we are being called directly and must probe.
645
raise NotImplementedError
646
return BzrBranch(_format=self,
647
_control_files=a_bzrdir._control_files,
649
_repository=a_bzrdir.open_repository())
652
return "Bazaar-NG branch format 4"
655
class BzrBranchFormat5(BranchFormat):
656
"""Bzr branch format 5.
659
- a revision-history file.
661
- a lock dir guarding the branch itself
662
- all of this stored in a branch/ subdirectory
663
- works with shared repositories.
665
This format is new in bzr 0.8.
668
def get_format_string(self):
669
"""See BranchFormat.get_format_string()."""
670
return "Bazaar-NG branch format 5\n"
672
def initialize(self, a_bzrdir):
673
"""Create a branch of this format in a_bzrdir."""
674
mutter('creating branch %r in %s', self, a_bzrdir.transport.base)
675
branch_transport = a_bzrdir.get_branch_transport(self)
676
utf8_files = [('revision-history', ''),
679
control_files = LockableFiles(branch_transport, 'lock', LockDir)
680
control_files.create_lock()
681
control_files.lock_write()
682
control_files.put_utf8('format', self.get_format_string())
684
for file, content in utf8_files:
685
control_files.put_utf8(file, content)
687
control_files.unlock()
688
return self.open(a_bzrdir, _found=True, )
691
super(BzrBranchFormat5, self).__init__()
692
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
694
def open(self, a_bzrdir, _found=False):
695
"""Return the branch object for a_bzrdir
697
_found is a private parameter, do not use it. It is used to indicate
698
if format probing has already be done.
701
format = BranchFormat.find_format(a_bzrdir)
702
assert format.__class__ == self.__class__
703
transport = a_bzrdir.get_branch_transport(None)
704
control_files = LockableFiles(transport, 'lock', LockDir)
705
return BzrBranch5(_format=self,
706
_control_files=control_files,
708
_repository=a_bzrdir.find_repository())
711
return "Bazaar-NG Metadir branch format 5"
714
class BranchReferenceFormat(BranchFormat):
715
"""Bzr branch reference format.
717
Branch references are used in implementing checkouts, they
718
act as an alias to the real branch which is at some other url.
725
def get_format_string(self):
726
"""See BranchFormat.get_format_string()."""
727
return "Bazaar-NG Branch Reference Format 1\n"
729
def initialize(self, a_bzrdir, target_branch=None):
730
"""Create a branch of this format in a_bzrdir."""
731
if target_branch is None:
732
# this format does not implement branch itself, thus the implicit
733
# creation contract must see it as uninitializable
734
raise errors.UninitializableFormat(self)
735
mutter('creating branch reference in %s', a_bzrdir.transport.base)
736
branch_transport = a_bzrdir.get_branch_transport(self)
737
# FIXME rbc 20060209 one j-a-ms encoding branch lands this str() cast is not needed.
738
branch_transport.put('location', StringIO(str(target_branch.bzrdir.root_transport.base)))
739
branch_transport.put('format', StringIO(self.get_format_string()))
740
return self.open(a_bzrdir, _found=True)
743
super(BranchReferenceFormat, self).__init__()
744
self._matchingbzrdir = bzrdir.BzrDirMetaFormat1()
746
def _make_reference_clone_function(format, a_branch):
747
"""Create a clone() routine for a branch dynamically."""
748
def clone(to_bzrdir, revision_id=None):
749
"""See Branch.clone()."""
750
return format.initialize(to_bzrdir, a_branch)
751
# cannot obey revision_id limits when cloning a reference ...
752
# FIXME RBC 20060210 either nuke revision_id for clone, or
753
# emit some sort of warning/error to the caller ?!
756
def open(self, a_bzrdir, _found=False):
757
"""Return the branch that the branch reference in a_bzrdir points at.
759
_found is a private parameter, do not use it. It is used to indicate
760
if format probing has already be done.
763
format = BranchFormat.find_format(a_bzrdir)
764
assert format.__class__ == self.__class__
765
transport = a_bzrdir.get_branch_transport(None)
766
real_bzrdir = bzrdir.BzrDir.open(transport.get('location').read())
767
result = real_bzrdir.open_branch()
768
# this changes the behaviour of result.clone to create a new reference
769
# rather than a copy of the content of the branch.
770
# I did not use a proxy object because that needs much more extensive
771
# testing, and we are only changing one behaviour at the moment.
772
# If we decide to alter more behaviours - i.e. the implicit nickname
773
# then this should be refactored to introduce a tested proxy branch
774
# and a subclass of that for use in overriding clone() and ....
776
result.clone = self._make_reference_clone_function(result)
780
# formats which have no format string are not discoverable
781
# and not independently creatable, so are not registered.
782
__default_format = BzrBranchFormat5()
783
BranchFormat.register_format(__default_format)
784
BranchFormat.register_format(BranchReferenceFormat())
785
BranchFormat.set_default_format(__default_format)
786
_legacy_formats = [BzrBranchFormat4(),
789
class BzrBranch(Branch):
790
"""A branch stored in the actual filesystem.
792
Note that it's "local" in the context of the filesystem; it doesn't
793
really matter if it's on an nfs/smb/afs/coda/... share, as long as
794
it's writable, and can be accessed via the normal filesystem API.
797
def __init__(self, transport=DEPRECATED_PARAMETER, init=DEPRECATED_PARAMETER,
798
relax_version_check=DEPRECATED_PARAMETER, _format=None,
799
_control_files=None, a_bzrdir=None, _repository=None):
800
"""Create new branch object at a particular location.
802
transport -- A Transport object, defining how to access files.
804
init -- If True, create new control files in a previously
805
unversioned directory. If False, the branch must already
808
relax_version_check -- If true, the usual check for the branch
809
version is not applied. This is intended only for
810
upgrade/recovery type use; it's not guaranteed that
811
all operations will work on old format branches.
814
self.bzrdir = bzrdir.BzrDir.open(transport.base)
816
self.bzrdir = a_bzrdir
817
self._transport = self.bzrdir.transport.clone('..')
818
self._base = self._transport.base
819
self._format = _format
820
if _control_files is None:
821
raise BzrBadParameterMissing('_control_files')
822
self.control_files = _control_files
823
if deprecated_passed(init):
824
warn("BzrBranch.__init__(..., init=XXX): The init parameter is "
825
"deprecated as of bzr 0.8. Please use Branch.create().",
829
# this is slower than before deprecation, oh well never mind.
831
self._initialize(transport.base)
832
self._check_format(_format)
833
if deprecated_passed(relax_version_check):
834
warn("BzrBranch.__init__(..., relax_version_check=XXX_: The "
835
"relax_version_check parameter is deprecated as of bzr 0.8. "
836
"Please use BzrDir.open_downlevel, or a BzrBranchFormat's "
840
if (not relax_version_check
841
and not self._format.is_supported()):
842
raise errors.UnsupportedFormatError(
843
'sorry, branch format %r not supported' % fmt,
844
['use a different bzr version',
845
'or remove the .bzr directory'
846
' and "bzr init" again'])
847
if deprecated_passed(transport):
848
warn("BzrBranch.__init__(transport=XXX...): The transport "
849
"parameter is deprecated as of bzr 0.8. "
850
"Please use Branch.open, or bzrdir.open_branch().",
853
self.repository = _repository
856
return '%s(%r)' % (self.__class__.__name__, self.base)
998
860
def __del__(self):
1002
"""Destroy the test branch, removing the scratch directory."""
1005
mutter("delete ScratchBranch %s" % self.base)
1006
shutil.rmtree(self.base)
1008
# Work around for shutil.rmtree failing on Windows when
1009
# readonly files are encountered
1010
mutter("hit exception in destroying ScratchBranch: %s" % e)
1011
for root, dirs, files in os.walk(self.base, topdown=False):
1013
os.chmod(os.path.join(root, name), 0700)
1014
shutil.rmtree(self.base)
861
# TODO: It might be best to do this somewhere else,
862
# but it is nice for a Branch object to automatically
863
# cache it's information.
864
# Alternatively, we could have the Transport objects cache requests
865
# See the earlier discussion about how major objects (like Branch)
866
# should never expect their __del__ function to run.
867
# XXX: cache_root seems to be unused, 2006-01-13 mbp
868
if hasattr(self, 'cache_root') and self.cache_root is not None:
870
shutil.rmtree(self.cache_root)
873
self.cache_root = None
878
base = property(_get_base, doc="The URL for the root of this branch.")
880
def _finish_transaction(self):
881
"""Exit the current transaction."""
882
return self.control_files._finish_transaction()
884
def get_transaction(self):
885
"""Return the current active transaction.
887
If no transaction is active, this returns a passthrough object
888
for which all data is immediately flushed and no caching happens.
890
# this is an explicit function so that we can do tricky stuff
891
# when the storage in rev_storage is elsewhere.
892
# we probably need to hook the two 'lock a location' and
893
# 'have a transaction' together more delicately, so that
894
# we can have two locks (branch and storage) and one transaction
895
# ... and finishing the transaction unlocks both, but unlocking
896
# does not. - RBC 20051121
897
return self.control_files.get_transaction()
899
def _set_transaction(self, transaction):
900
"""Set a new active transaction."""
901
return self.control_files._set_transaction(transaction)
903
def abspath(self, name):
904
"""See Branch.abspath."""
905
return self.control_files._transport.abspath(name)
907
def _check_format(self, format):
908
"""Identify the branch format if needed.
910
The format is stored as a reference to the format object in
911
self._format for code that needs to check it later.
913
The format parameter is either None or the branch format class
914
used to open this branch.
916
FIXME: DELETE THIS METHOD when pre 0.8 support is removed.
919
format = BzrBranchFormat.find_format(self.bzrdir)
920
self._format = format
921
mutter("got branch format %s", self._format)
924
def get_root_id(self):
925
"""See Branch.get_root_id."""
926
tree = self.repository.revision_tree(self.last_revision())
927
return tree.inventory.root.file_id
929
def lock_write(self):
930
# TODO: test for failed two phase locks. This is known broken.
931
self.control_files.lock_write()
932
self.repository.lock_write()
935
# TODO: test for failed two phase locks. This is known broken.
936
self.control_files.lock_read()
937
self.repository.lock_read()
940
# TODO: test for failed two phase locks. This is known broken.
941
self.repository.unlock()
942
self.control_files.unlock()
944
def peek_lock_mode(self):
945
if self.control_files._lock_count == 0:
948
return self.control_files._lock_mode
951
def print_file(self, file, revision_id):
952
"""See Branch.print_file."""
953
return self.repository.print_file(file, revision_id)
956
def append_revision(self, *revision_ids):
957
"""See Branch.append_revision."""
958
for revision_id in revision_ids:
959
mutter("add {%s} to revision-history" % revision_id)
960
rev_history = self.revision_history()
961
rev_history.extend(revision_ids)
962
self.set_revision_history(rev_history)
965
def set_revision_history(self, rev_history):
966
"""See Branch.set_revision_history."""
967
self.control_files.put_utf8(
968
'revision-history', '\n'.join(rev_history))
969
transaction = self.get_transaction()
970
history = transaction.map.find_revision_history()
971
if history is not None:
972
# update the revision history in the identity map.
973
history[:] = list(rev_history)
974
# this call is disabled because revision_history is
975
# not really an object yet, and the transaction is for objects.
976
# transaction.register_dirty(history)
978
transaction.map.add_revision_history(rev_history)
979
# this call is disabled because revision_history is
980
# not really an object yet, and the transaction is for objects.
981
# transaction.register_clean(history)
983
def get_revision_delta(self, revno):
984
"""Return the delta for one revision.
986
The delta is relative to its mainline predecessor, or the
987
empty tree for revision 1.
989
assert isinstance(revno, int)
990
rh = self.revision_history()
991
if not (1 <= revno <= len(rh)):
992
raise InvalidRevisionNumber(revno)
994
# revno is 1-based; list is 0-based
996
new_tree = self.repository.revision_tree(rh[revno-1])
998
old_tree = EmptyTree()
1000
old_tree = self.repository.revision_tree(rh[revno-2])
1001
return compare_trees(old_tree, new_tree)
1004
def revision_history(self):
1005
"""See Branch.revision_history."""
1006
transaction = self.get_transaction()
1007
history = transaction.map.find_revision_history()
1008
if history is not None:
1009
mutter("cache hit for revision-history in %s", self)
1010
return list(history)
1011
history = [l.rstrip('\r\n') for l in
1012
self.control_files.get_utf8('revision-history').readlines()]
1013
transaction.map.add_revision_history(history)
1014
# this call is disabled because revision_history is
1015
# not really an object yet, and the transaction is for objects.
1016
# transaction.register_clean(history, precious=True)
1017
return list(history)
1019
def update_revisions(self, other, stop_revision=None):
1020
"""See Branch.update_revisions."""
1021
if stop_revision is None:
1022
stop_revision = other.last_revision()
1023
### Should this be checking is_ancestor instead of revision_history?
1024
if (stop_revision is not None and
1025
stop_revision in self.revision_history()):
1027
self.fetch(other, stop_revision)
1028
pullable_revs = self.pullable_revisions(other, stop_revision)
1029
if len(pullable_revs) > 0:
1030
self.append_revision(*pullable_revs)
1032
def pullable_revisions(self, other, stop_revision):
1033
other_revno = other.revision_id_to_revno(stop_revision)
1035
return self.missing_revisions(other, other_revno)
1036
except DivergedBranches, e:
1038
pullable_revs = get_intervening_revisions(self.last_revision(),
1041
assert self.last_revision() not in pullable_revs
1042
return pullable_revs
1043
except bzrlib.errors.NotAncestor:
1044
if is_ancestor(self.last_revision(), stop_revision, self):
1049
def basis_tree(self):
1050
"""See Branch.basis_tree."""
1051
return self.repository.revision_tree(self.last_revision())
1053
@deprecated_method(zero_eight)
1054
def working_tree(self):
1055
"""Create a Working tree object for this branch."""
1056
from bzrlib.workingtree import WorkingTree
1057
from bzrlib.transport.local import LocalTransport
1058
if (self.base.find('://') != -1 or
1059
not isinstance(self._transport, LocalTransport)):
1060
raise NoWorkingTree(self.base)
1061
return self.bzrdir.open_workingtree()
1064
def pull(self, source, overwrite=False, stop_revision=None):
1065
"""See Branch.pull."""
1068
old_count = len(self.revision_history())
1070
self.update_revisions(source,stop_revision)
1071
except DivergedBranches:
1075
self.set_revision_history(source.revision_history())
1076
new_count = len(self.revision_history())
1077
return new_count - old_count
1081
def get_parent(self):
1082
"""See Branch.get_parent."""
1084
_locs = ['parent', 'pull', 'x-pull']
1087
return self.control_files.get_utf8(l).read().strip('\n')
1092
def get_push_location(self):
1093
"""See Branch.get_push_location."""
1094
config = bzrlib.config.BranchConfig(self)
1095
push_loc = config.get_user_option('push_location')
1098
def set_push_location(self, location):
1099
"""See Branch.set_push_location."""
1100
config = bzrlib.config.LocationConfig(self.base)
1101
config.set_user_option('push_location', location)
1104
def set_parent(self, url):
1105
"""See Branch.set_parent."""
1106
# TODO: Maybe delete old location files?
1107
# URLs should never be unicode, even on the local fs,
1108
# FIXUP this and get_parent in a future branch format bump:
1109
# read and rewrite the file, and have the new format code read
1110
# using .get not .get_utf8. RBC 20060125
1111
self.control_files.put_utf8('parent', url + '\n')
1113
def tree_config(self):
1114
return TreeConfig(self)
1117
class BzrBranch5(BzrBranch):
1118
"""A format 5 branch. This supports new features over plan branches.
1120
It has support for a master_branch which is the data for bound branches.
1128
super(BzrBranch5, self).__init__(_format=_format,
1129
_control_files=_control_files,
1131
_repository=_repository)
1134
def pull(self, source, overwrite=False, stop_revision=None):
1135
"""Updates branch.pull to be bound branch aware."""
1136
bound_location = self.get_bound_location()
1137
if source.base != bound_location:
1138
# not pulling from master, so we need to update master.
1139
master_branch = self.get_master_branch()
1141
master_branch.pull(source)
1142
source = master_branch
1143
return super(BzrBranch5, self).pull(source, overwrite, stop_revision)
1145
def get_bound_location(self):
1147
return self.control_files.get_utf8('bound').read()[:-1]
1148
except errors.NoSuchFile:
1152
def get_master_branch(self):
1153
"""Return the branch we are bound to.
1155
:return: Either a Branch, or None
1157
This could memoise the branch, but if thats done
1158
it must be revalidated on each new lock.
1159
So for now we just dont memoise it.
1160
# RBC 20060304 review this decision.
1162
bound_loc = self.get_bound_location()
1166
return Branch.open(bound_loc)
1167
except (errors.NotBranchError, errors.ConnectionError), e:
1168
raise errors.BoundBranchConnectionFailure(
1172
def set_bound_location(self, location):
1173
"""Set the target where this branch is bound to.
1175
:param location: URL to the target branch
1178
self.control_files.put_utf8('bound', location+'\n')
1181
self.control_files._transport.delete('bound')
1187
def bind(self, other):
1188
"""Bind the local branch the other branch.
1190
:param other: The branch to bind to
1193
# TODO: jam 20051230 Consider checking if the target is bound
1194
# It is debatable whether you should be able to bind to
1195
# a branch which is itself bound.
1196
# Committing is obviously forbidden,
1197
# but binding itself may not be.
1198
# Since we *have* to check at commit time, we don't
1199
# *need* to check here
1202
# we are now equal to or a suffix of other.
1204
# Since we have 'pulled' from the remote location,
1205
# now we should try to pull in the opposite direction
1206
# in case the local tree has more revisions than the
1208
# There may be a different check you could do here
1209
# rather than actually trying to install revisions remotely.
1210
# TODO: capture an exception which indicates the remote branch
1212
# If it is up-to-date, this probably should not be a failure
1214
# lock other for write so the revision-history syncing cannot race
1218
# if this does not error, other now has the same last rev we do
1219
# it can only error if the pull from other was concurrent with
1220
# a commit to other from someone else.
1222
# until we ditch revision-history, we need to sync them up:
1223
self.set_revision_history(other.revision_history())
1224
# now other and self are up to date with each other and have the
1225
# same revision-history.
1229
self.set_bound_location(other.base)
1233
"""If bound, unbind"""
1234
return self.set_bound_location(None)
1238
"""Synchronise this branch with the master branch if any.
1240
:return: None or the last_revision that was pivoted out during the
1243
master = self.get_master_branch()
1244
if master is not None:
1245
old_tip = self.last_revision()
1246
self.pull(master, overwrite=True)
1247
if old_tip in self.repository.get_ancestry(self.last_revision()):
1253
class BranchTestProviderAdapter(object):
1254
"""A tool to generate a suite testing multiple branch formats at once.
1256
This is done by copying the test once for each transport and injecting
1257
the transport_server, transport_readonly_server, and branch_format
1258
classes into each copy. Each copy is also given a new id() to make it
1262
def __init__(self, transport_server, transport_readonly_server, formats):
1263
self._transport_server = transport_server
1264
self._transport_readonly_server = transport_readonly_server
1265
self._formats = formats
1267
def adapt(self, test):
1268
result = TestSuite()
1269
for branch_format, bzrdir_format in self._formats:
1270
new_test = deepcopy(test)
1271
new_test.transport_server = self._transport_server
1272
new_test.transport_readonly_server = self._transport_readonly_server
1273
new_test.bzrdir_format = bzrdir_format
1274
new_test.branch_format = branch_format
1275
def make_new_test_id():
1276
new_id = "%s(%s)" % (new_test.id(), branch_format.__class__.__name__)
1277
return lambda: new_id
1278
new_test.id = make_new_test_id()
1279
result.addTest(new_test)
1019
1283
######################################################################
1023
def is_control_file(filename):
1024
## FIXME: better check
1025
filename = os.path.normpath(filename)
1026
while filename != '':
1027
head, tail = os.path.split(filename)
1028
## mutter('check %r for control file' % ((head, tail), ))
1029
if tail == bzrlib.BZRDIR:
1031
if filename == head:
1038
def gen_file_id(name):
1039
"""Return new file id.
1041
This should probably generate proper UUIDs, but for the moment we
1042
cope with just randomness because running uuidgen every time is
1046
# get last component
1047
idx = name.rfind('/')
1049
name = name[idx+1 : ]
1050
idx = name.rfind('\\')
1052
name = name[idx+1 : ]
1054
# make it not a hidden file
1055
name = name.lstrip('.')
1057
# remove any wierd characters; we don't escape them but rather
1058
# just pull them out
1059
name = re.sub(r'[^\w.]', '', name)
1061
s = hexlify(rand_bytes(8))
1062
return '-'.join((name, compact_date(time.time()), s))
1287
@deprecated_function(zero_eight)
1288
def ScratchBranch(*args, **kwargs):
1289
"""See bzrlib.bzrdir.ScratchDir."""
1290
d = ScratchDir(*args, **kwargs)
1291
return d.open_branch()
1294
@deprecated_function(zero_eight)
1295
def is_control_file(*args, **kwargs):
1296
"""See bzrlib.workingtree.is_control_file."""
1297
return bzrlib.workingtree.is_control_file(*args, **kwargs)