14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
19
17
from .lazy_import import lazy_import
20
18
lazy_import(globals(), """
22
21
from breezy import (
23
22
config as _mod_config,
46
45
from .hooks import Hooks
47
46
from .inter import InterObject
48
47
from .lock import LogicalLockResult
53
from .trace import mutter, mutter_callsite, note, is_quiet
48
from .trace import mutter, mutter_callsite, note, is_quiet, warning
56
51
class UnstackableBranchFormat(errors.BzrError):
364
359
provide a more efficient implementation.
366
361
if len(revno) == 1:
367
return self.get_rev_id(revno[0])
363
return self.get_rev_id(revno[0])
364
except errors.RevisionNotPresent as e:
365
raise errors.GhostRevisionsHaveNoRevno(revno[0], e.revision_id)
368
366
revision_id_to_revno = self.get_revision_id_to_revno_map()
369
367
revision_ids = [revision_id for revision_id, this_revno
370
in viewitems(revision_id_to_revno)
368
in revision_id_to_revno.items()
371
369
if revno == this_revno]
372
370
if len(revision_ids) == 1:
373
371
return revision_ids[0]
672
670
raise errors.UpgradeRequired(self.user_url)
673
671
self.get_config_stack().set('append_revisions_only', enabled)
675
def set_reference_info(self, tree_path, branch_location, file_id=None):
676
"""Set the branch location to use for a tree reference."""
677
raise errors.UnsupportedOperation(self.set_reference_info, self)
679
def get_reference_info(self, path):
680
"""Get the tree_path and branch_location for a tree reference."""
681
raise errors.UnsupportedOperation(self.get_reference_info, self)
683
def fetch(self, from_branch, last_revision=None, limit=None):
673
def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
684
674
"""Copy revisions from from_branch into this branch.
686
676
:param from_branch: Where to copy from.
687
:param last_revision: What revision to stop at (None for at the end
677
:param stop_revision: What revision to stop at (None for at the end
689
679
:param limit: Optional rough limit of revisions to fetch
692
682
with self.lock_write():
693
683
return InterBranch.get(from_branch, self).fetch(
694
last_revision, limit=limit)
684
stop_revision, limit=limit, lossy=lossy)
696
686
def get_bound_location(self):
697
687
"""Return the URL of the branch we are bound to.
783
773
# FIXUP this and get_parent in a future branch format bump:
784
774
# read and rewrite the file. RBC 20060125
785
775
if url is not None:
786
if isinstance(url, text_type):
776
if isinstance(url, str):
788
778
url.encode('ascii')
789
779
except UnicodeEncodeError:
1198
1188
if revno < 1 or revno > self.revno():
1199
1189
raise errors.InvalidRevisionNumber(revno)
1201
def clone(self, to_controldir, revision_id=None, repository_policy=None):
1191
def clone(self, to_controldir, revision_id=None, name=None,
1192
repository_policy=None, tag_selector=None):
1202
1193
"""Clone this branch into to_controldir preserving all semantic values.
1204
1195
Most API users will want 'create_clone_on_transport', which creates a
1207
1198
revision_id: if not None, the revision history in the new branch will
1208
1199
be truncated to end with revision_id.
1210
result = to_controldir.create_branch()
1201
result = to_controldir.create_branch(name=name)
1211
1202
with self.lock_read(), result.lock_write():
1212
1203
if repository_policy is not None:
1213
1204
repository_policy.configure_branch(result)
1214
self.copy_content_into(result, revision_id=revision_id)
1205
self.copy_content_into(
1206
result, revision_id=revision_id, tag_selector=tag_selector)
1217
1209
def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1218
repository=None, lossy=False):
1210
repository=None, lossy=False, tag_selector=None):
1219
1211
"""Create a new line of development from the branch, into to_controldir.
1221
1213
to_controldir controls the branch format.
1232
1224
with self.lock_read(), result.lock_write():
1233
1225
if repository_policy is not None:
1234
1226
repository_policy.configure_branch(result)
1235
self.copy_content_into(result, revision_id=revision_id)
1227
self.copy_content_into(
1228
result, revision_id=revision_id, tag_selector=tag_selector)
1236
1229
master_url = self.get_bound_location()
1237
1230
if master_url is None:
1238
1231
result.set_parent(self.user_url)
1266
1259
destination.set_last_revision_info(revno, revision_id)
1268
def copy_content_into(self, destination, revision_id=None):
1261
def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1269
1262
"""Copy the content of self into destination.
1271
1264
revision_id: if not None, the revision history in the new branch will
1272
1265
be truncated to end with revision_id.
1266
tag_selector: Optional callback that receives a tag name
1267
and should return a boolean to indicate whether a tag should be copied
1274
1269
return InterBranch.get(self, destination).copy_content_into(
1275
revision_id=revision_id)
1270
revision_id=revision_id, tag_selector=tag_selector)
1277
1272
def update_references(self, target):
1278
if not getattr(self._format, 'supports_reference_locations', False):
1280
reference_dict = self._get_all_reference_info()
1281
if len(reference_dict) == 0:
1283
old_base = self.base
1284
new_base = target.base
1285
target_reference_dict = target._get_all_reference_info()
1286
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
1287
branch_location = urlutils.rebase_url(branch_location,
1289
target_reference_dict.setdefault(
1290
tree_path, (branch_location, file_id))
1291
target._set_all_reference_info(target_reference_dict)
1273
if not self._format.supports_reference_locations:
1275
return InterBranch.get(self, target).update_references()
1293
1277
def check(self, refs):
1294
1278
"""Check consistency of the branch.
1329
1313
def create_clone_on_transport(self, to_transport, revision_id=None,
1330
1314
stacked_on=None, create_prefix=False,
1331
use_existing_dir=False, no_tree=None):
1315
use_existing_dir=False, no_tree=None,
1332
1317
"""Create a clone of this branch and its bzrdir.
1334
1319
:param to_transport: The transport to clone onto.
1348
1333
dir_to = self.controldir.clone_on_transport(
1349
1334
to_transport, revision_id=revision_id, stacked_on=stacked_on,
1350
1335
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1336
no_tree=no_tree, tag_selector=tag_selector)
1352
1337
return dir_to.open_branch()
1354
1339
def create_checkout(self, to_location, revision_id=None,
1355
1340
lightweight=False, accelerator_tree=None,
1341
hardlink=False, recurse_nested=True):
1357
1342
"""Create a checkout of a branch.
1359
1344
:param to_location: The url to produce the checkout at
1366
1351
content is different.
1367
1352
:param hardlink: If true, hard-link files from accelerator_tree,
1368
1353
where possible.
1354
:param recurse_nested: Whether to recurse into nested trees
1369
1355
:return: The tree of the created checkout
1371
1357
t = transport.get_transport(to_location)
1406
1392
hardlink=hardlink)
1407
1393
basis_tree = tree.basis_tree()
1408
1394
with basis_tree.lock_read():
1409
for path, file_id in basis_tree.iter_references():
1410
reference_parent = self.reference_parent(path, file_id)
1395
for path in basis_tree.iter_references():
1396
reference_parent = tree.reference_parent(path)
1397
if reference_parent is None:
1398
warning('Branch location for %s unknown.', path)
1411
1400
reference_parent.create_checkout(
1412
1401
tree.abspath(path),
1413
1402
basis_tree.get_reference_revision(path), lightweight)
1416
1405
def reconcile(self, thorough=True):
1417
"""Make sure the data stored in this branch is consistent."""
1418
from breezy.reconcile import BranchReconciler
1419
with self.lock_write():
1420
reconciler = BranchReconciler(self, thorough=thorough)
1421
reconciler.reconcile()
1424
def reference_parent(self, path, file_id=None, possible_transports=None):
1425
"""Return the parent branch for a tree-reference file_id
1427
:param path: The path of the file_id in the tree
1428
:param file_id: Optional file_id of the tree reference
1429
:return: A branch associated with the file_id
1406
"""Make sure the data stored in this branch is consistent.
1408
:return: A `ReconcileResult` object.
1431
# FIXME should provide multiple branches, based on config
1432
return Branch.open(self.controldir.root_transport.clone(path).base,
1433
possible_transports=possible_transports)
1410
raise NotImplementedError(self.reconcile)
1435
1412
def supports_tags(self):
1436
1413
return self._format.supports_tags()
1660
1637
"""True if uncommitted changes can be stored in this branch."""
1640
def stores_revno(self):
1641
"""True if this branch format store revision numbers."""
1664
1645
class BranchHooks(Hooks):
1665
1646
"""A dictionary mapping hook name to a list of callables for branch hooks.
2018
1999
tag_updates = getattr(self, "tag_updates", None)
2019
2000
if not is_quiet():
2020
2001
if self.old_revid != self.new_revid:
2021
note(gettext('Pushed up to revision %d.') % self.new_revno)
2002
if self.new_revno is not None:
2003
note(gettext('Pushed up to revision %d.'),
2006
note(gettext('Pushed up to revision id %s.'),
2007
self.new_revid.decode('utf-8'))
2022
2008
if tag_updates:
2023
2009
note(ngettext('%d tag updated.', '%d tags updated.',
2024
2010
len(tag_updates)) % len(tag_updates))
2074
2060
raise NotImplementedError(klass._get_branch_formats_to_test)
2076
2062
def pull(self, overwrite=False, stop_revision=None,
2077
possible_transports=None, local=False):
2063
possible_transports=None, local=False, tag_selector=None):
2078
2064
"""Mirror source into target branch.
2080
2066
The target branch is considered to be 'local', having low latency.
2084
2070
raise NotImplementedError(self.pull)
2086
2072
def push(self, overwrite=False, stop_revision=None, lossy=False,
2087
_override_hook_source_branch=None):
2073
_override_hook_source_branch=None, tag_selector=None):
2088
2074
"""Mirror the source branch into the target branch.
2090
2076
The source branch is considered to be 'local', having low latency.
2092
2078
raise NotImplementedError(self.push)
2094
def copy_content_into(self, revision_id=None):
2080
def copy_content_into(self, revision_id=None, tag_selector=None):
2095
2081
"""Copy the content of source into target
2097
revision_id: if not None, the revision history in the new branch will
2098
be truncated to end with revision_id.
2084
if not None, the revision history in the new branch will
2085
be truncated to end with revision_id.
2086
:param tag_selector: Optional callback that can decide
2087
to copy or not copy tags.
2100
2089
raise NotImplementedError(self.copy_content_into)
2102
def fetch(self, stop_revision=None, limit=None):
2091
def fetch(self, stop_revision=None, limit=None, lossy=False):
2103
2092
"""Fetch revisions.
2105
2094
:param stop_revision: Last revision to fetch
2106
2095
:param limit: Optional rough limit of revisions to fetch
2096
:return: FetchResult object
2108
2098
raise NotImplementedError(self.fetch)
2100
def update_references(self):
2101
"""Import reference information from source to target.
2103
raise NotImplementedError(self.update_references)
2111
2106
def _fix_overwrite_type(overwrite):
2112
2107
if isinstance(overwrite, bool):
2136
2131
return format._custom_format
2139
def copy_content_into(self, revision_id=None):
2134
def copy_content_into(self, revision_id=None, tag_selector=None):
2140
2135
"""Copy the content of source into target
2142
2137
revision_id: if not None, the revision history in the new branch will
2143
2138
be truncated to end with revision_id.
2145
2140
with self.source.lock_read(), self.target.lock_write():
2146
self.source.update_references(self.target)
2147
2141
self.source._synchronize_history(self.target, revision_id)
2142
self.update_references()
2149
2144
parent = self.source.get_parent()
2150
2145
except errors.InaccessibleParent as e:
2154
2149
self.target.set_parent(parent)
2155
2150
if self.source._push_should_merge_tags():
2156
self.source.tags.merge_to(self.target.tags)
2151
self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2158
def fetch(self, stop_revision=None, limit=None):
2153
def fetch(self, stop_revision=None, limit=None, lossy=False):
2159
2154
if self.target.base == self.source.base:
2161
2156
with self.source.lock_read(), self.target.lock_write():
2170
2165
fetch_spec = fetch_spec_factory.make_fetch_spec()
2171
2166
return self.target.repository.fetch(
2172
2167
self.source.repository,
2173
2169
fetch_spec=fetch_spec)
2175
2171
def _update_revisions(self, stop_revision=None, overwrite=False,
2214
2210
def pull(self, overwrite=False, stop_revision=None,
2215
2211
possible_transports=None, run_hooks=True,
2216
_override_hook_target=None, local=False):
2212
_override_hook_target=None, local=False,
2217
2214
"""Pull from source into self, updating my master if any.
2219
2216
:param run_hooks: Private parameter - if false, this branch
2220
2217
is being called because it's the master of the primary branch,
2221
2218
so it should not run its hooks.
2223
with self.target.lock_write():
2220
with contextlib.ExitStack() as exit_stack:
2221
exit_stack.enter_context(self.target.lock_write())
2224
2222
bound_location = self.target.get_bound_location()
2225
2223
if local and not bound_location:
2226
2224
raise errors.LocalRequiresBoundBranch()
2239
2237
# not pulling from master, so we need to update master.
2240
2238
master_branch = self.target.get_master_branch(
2241
2239
possible_transports)
2242
master_branch.lock_write()
2245
# pull from source into master.
2247
self.source, overwrite, stop_revision, run_hooks=False)
2249
overwrite, stop_revision, _hook_master=master_branch,
2250
run_hooks=run_hooks,
2251
_override_hook_target=_override_hook_target,
2252
merge_tags_to_master=not source_is_master)
2255
master_branch.unlock()
2240
exit_stack.enter_context(master_branch.lock_write())
2242
# pull from source into master.
2244
self.source, overwrite, stop_revision, run_hooks=False,
2245
tag_selector=tag_selector)
2247
overwrite, stop_revision, _hook_master=master_branch,
2248
run_hooks=run_hooks,
2249
_override_hook_target=_override_hook_target,
2250
merge_tags_to_master=not source_is_master,
2251
tag_selector=tag_selector)
2257
2253
def push(self, overwrite=False, stop_revision=None, lossy=False,
2258
_override_hook_source_branch=None):
2254
_override_hook_source_branch=None, tag_selector=None):
2259
2255
"""See InterBranch.push.
2261
2257
This is the basic concrete implementation of push()
2287
2283
with master_branch.lock_write():
2288
2284
# push into the master from the source branch.
2289
2285
master_inter = InterBranch.get(self.source, master_branch)
2290
master_inter._basic_push(overwrite, stop_revision)
2286
master_inter._basic_push(
2287
overwrite, stop_revision, tag_selector=tag_selector)
2291
2288
# and push into the target branch from the source. Note
2292
2289
# that we push from the source branch again, because it's
2293
2290
# considered the highest bandwidth repository.
2294
result = self._basic_push(overwrite, stop_revision)
2291
result = self._basic_push(
2292
overwrite, stop_revision, tag_selector=tag_selector)
2295
2293
result.master_branch = master_branch
2296
2294
result.local_branch = self.target
2299
2297
master_branch = None
2300
2298
# no master branch
2301
result = self._basic_push(overwrite, stop_revision)
2299
result = self._basic_push(
2300
overwrite, stop_revision, tag_selector=tag_selector)
2302
2301
# TODO: Why set master_branch and local_branch if there's no
2303
2302
# binding? Maybe cleaner to just leave them unset? -- mbp
2310
def _basic_push(self, overwrite, stop_revision):
2309
def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2311
2310
"""Basic implementation of push without bound branches or hooks.
2313
2312
Must be called with source read locked and target write locked.
2316
2315
result.source_branch = self.source
2317
2316
result.target_branch = self.target
2318
2317
result.old_revno, result.old_revid = self.target.last_revision_info()
2319
self.source.update_references(self.target)
2320
2318
overwrite = _fix_overwrite_type(overwrite)
2321
2319
if result.old_revid != stop_revision:
2322
2320
# We assume that during 'push' this repository is closer than
2327
2325
if self.source._push_should_merge_tags():
2328
2326
result.tag_updates, result.tag_conflicts = (
2329
2327
self.source.tags.merge_to(
2330
self.target.tags, "tags" in overwrite))
2328
self.target.tags, "tags" in overwrite, selector=tag_selector))
2329
self.update_references()
2331
2330
result.new_revno, result.new_revid = self.target.last_revision_info()
2334
2333
def _pull(self, overwrite=False, stop_revision=None,
2335
2334
possible_transports=None, _hook_master=None, run_hooks=True,
2336
2335
_override_hook_target=None, local=False,
2337
merge_tags_to_master=True):
2336
merge_tags_to_master=True, tag_selector=None):
2338
2337
"""See Branch.pull.
2340
2339
This function is the core worker, used by GenericInterBranch.pull to
2363
2362
with self.source.lock_read():
2364
2363
# We assume that during 'pull' the target repository is closer than
2365
2364
# the source one.
2366
self.source.update_references(self.target)
2367
2365
graph = self.target.repository.get_graph(self.source.repository)
2368
2366
# TODO: Branch formats should have a flag that indicates
2369
2367
# that revno's are expensive, and pull() should honor that flag.
2379
2377
result.tag_updates, result.tag_conflicts = (
2380
2378
self.source.tags.merge_to(
2381
2379
self.target.tags, "tags" in overwrite,
2382
ignore_master=not merge_tags_to_master))
2380
ignore_master=not merge_tags_to_master,
2381
selector=tag_selector))
2382
self.update_references()
2383
2383
result.new_revno, result.new_revid = (
2384
2384
self.target.last_revision_info())
2385
2385
if _hook_master:
2396
def update_references(self):
2397
if not getattr(self.source._format, 'supports_reference_locations', False):
2399
reference_dict = self.source._get_all_reference_info()
2400
if len(reference_dict) == 0:
2402
old_base = self.source.base
2403
new_base = self.target.base
2404
target_reference_dict = self.target._get_all_reference_info()
2405
for tree_path, (branch_location, file_id) in reference_dict.items():
2407
branch_location = urlutils.rebase_url(branch_location,
2409
except urlutils.InvalidRebaseURLs:
2410
# Fall back to absolute URL
2411
branch_location = urlutils.join(old_base, branch_location)
2412
target_reference_dict.setdefault(
2413
tree_path, (branch_location, file_id))
2414
self.target._set_all_reference_info(target_reference_dict)
2397
2417
InterBranch.register_optimiser(GenericInterBranch)