54
from .trace import mutter, mutter_callsite, note, is_quiet, warning
53
from .trace import mutter, mutter_callsite, note, is_quiet
57
56
class UnstackableBranchFormat(errors.BzrError):
676
675
raise errors.UpgradeRequired(self.user_url)
677
676
self.get_config_stack().set('append_revisions_only', enabled)
679
def fetch(self, from_branch, stop_revision=None, limit=None, lossy=False):
678
def set_reference_info(self, tree_path, branch_location, file_id=None):
679
"""Set the branch location to use for a tree reference."""
680
raise errors.UnsupportedOperation(self.set_reference_info, self)
682
def get_reference_info(self, path):
683
"""Get the tree_path and branch_location for a tree reference."""
684
raise errors.UnsupportedOperation(self.get_reference_info, self)
686
def fetch(self, from_branch, last_revision=None, limit=None):
680
687
"""Copy revisions from from_branch into this branch.
682
689
:param from_branch: Where to copy from.
683
:param stop_revision: What revision to stop at (None for at the end
690
:param last_revision: What revision to stop at (None for at the end
685
692
:param limit: Optional rough limit of revisions to fetch
688
695
with self.lock_write():
689
696
return InterBranch.get(from_branch, self).fetch(
690
stop_revision, limit=limit, lossy=lossy)
697
last_revision, limit=limit)
692
699
def get_bound_location(self):
693
700
"""Return the URL of the branch we are bound to.
1194
1201
if revno < 1 or revno > self.revno():
1195
1202
raise errors.InvalidRevisionNumber(revno)
1197
def clone(self, to_controldir, revision_id=None, name=None,
1198
repository_policy=None, tag_selector=None):
1204
def clone(self, to_controldir, revision_id=None, repository_policy=None):
1199
1205
"""Clone this branch into to_controldir preserving all semantic values.
1201
1207
Most API users will want 'create_clone_on_transport', which creates a
1204
1210
revision_id: if not None, the revision history in the new branch will
1205
1211
be truncated to end with revision_id.
1207
result = to_controldir.create_branch(name=name)
1213
result = to_controldir.create_branch()
1208
1214
with self.lock_read(), result.lock_write():
1209
1215
if repository_policy is not None:
1210
1216
repository_policy.configure_branch(result)
1211
self.copy_content_into(
1212
result, revision_id=revision_id, tag_selector=tag_selector)
1217
self.copy_content_into(result, revision_id=revision_id)
1215
1220
def sprout(self, to_controldir, revision_id=None, repository_policy=None,
1216
repository=None, lossy=False, tag_selector=None):
1221
repository=None, lossy=False):
1217
1222
"""Create a new line of development from the branch, into to_controldir.
1219
1224
to_controldir controls the branch format.
1230
1235
with self.lock_read(), result.lock_write():
1231
1236
if repository_policy is not None:
1232
1237
repository_policy.configure_branch(result)
1233
self.copy_content_into(
1234
result, revision_id=revision_id, tag_selector=tag_selector)
1238
self.copy_content_into(result, revision_id=revision_id)
1235
1239
master_url = self.get_bound_location()
1236
1240
if master_url is None:
1237
1241
result.set_parent(self.user_url)
1265
1269
destination.set_last_revision_info(revno, revision_id)
1267
def copy_content_into(self, destination, revision_id=None, tag_selector=None):
1271
def copy_content_into(self, destination, revision_id=None):
1268
1272
"""Copy the content of self into destination.
1270
1274
revision_id: if not None, the revision history in the new branch will
1271
1275
be truncated to end with revision_id.
1272
tag_selector: Optional callback that receives a tag name
1273
and should return a boolean to indicate whether a tag should be copied
1275
1277
return InterBranch.get(self, destination).copy_content_into(
1276
revision_id=revision_id, tag_selector=tag_selector)
1278
revision_id=revision_id)
1278
1280
def update_references(self, target):
1279
if not self._format.supports_reference_locations:
1281
return InterBranch.get(self, target).update_references()
1281
if not getattr(self._format, 'supports_reference_locations', False):
1283
reference_dict = self._get_all_reference_info()
1284
if len(reference_dict) == 0:
1286
old_base = self.base
1287
new_base = target.base
1288
target_reference_dict = target._get_all_reference_info()
1289
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
1290
branch_location = urlutils.rebase_url(branch_location,
1292
target_reference_dict.setdefault(
1293
tree_path, (branch_location, file_id))
1294
target._set_all_reference_info(target_reference_dict)
1283
1296
def check(self, refs):
1284
1297
"""Check consistency of the branch.
1319
1332
def create_clone_on_transport(self, to_transport, revision_id=None,
1320
1333
stacked_on=None, create_prefix=False,
1321
use_existing_dir=False, no_tree=None,
1334
use_existing_dir=False, no_tree=None):
1323
1335
"""Create a clone of this branch and its bzrdir.
1325
1337
:param to_transport: The transport to clone onto.
1339
1351
dir_to = self.controldir.clone_on_transport(
1340
1352
to_transport, revision_id=revision_id, stacked_on=stacked_on,
1341
1353
create_prefix=create_prefix, use_existing_dir=use_existing_dir,
1342
no_tree=no_tree, tag_selector=tag_selector)
1343
1355
return dir_to.open_branch()
1345
1357
def create_checkout(self, to_location, revision_id=None,
1346
1358
lightweight=False, accelerator_tree=None,
1347
hardlink=False, recurse_nested=True):
1348
1360
"""Create a checkout of a branch.
1350
1362
:param to_location: The url to produce the checkout at
1357
1369
content is different.
1358
1370
:param hardlink: If true, hard-link files from accelerator_tree,
1359
1371
where possible.
1360
:param recurse_nested: Whether to recurse into nested trees
1361
1372
:return: The tree of the created checkout
1363
1374
t = transport.get_transport(to_location)
1398
1409
hardlink=hardlink)
1399
1410
basis_tree = tree.basis_tree()
1400
1411
with basis_tree.lock_read():
1401
for path in basis_tree.iter_references():
1402
reference_parent = tree.reference_parent(path)
1403
if reference_parent is None:
1404
warning('Branch location for %s unknown.', path)
1412
for path, file_id in basis_tree.iter_references():
1413
reference_parent = self.reference_parent(path, file_id)
1406
1414
reference_parent.create_checkout(
1407
1415
tree.abspath(path),
1408
1416
basis_tree.get_reference_revision(path), lightweight)
1416
1424
raise NotImplementedError(self.reconcile)
1426
def reference_parent(self, path, file_id=None, possible_transports=None):
1427
"""Return the parent branch for a tree-reference file_id
1429
:param path: The path of the file_id in the tree
1430
:param file_id: Optional file_id of the tree reference
1431
:return: A branch associated with the file_id
1433
# FIXME should provide multiple branches, based on config
1434
return Branch.open(self.controldir.root_transport.clone(path).base,
1435
possible_transports=possible_transports)
1418
1437
def supports_tags(self):
1419
1438
return self._format.supports_tags()
1643
1662
"""True if uncommitted changes can be stored in this branch."""
1646
def stores_revno(self):
1647
"""True if this branch format store revision numbers."""
1651
1666
class BranchHooks(Hooks):
1652
1667
"""A dictionary mapping hook name to a list of callables for branch hooks.
2005
2020
tag_updates = getattr(self, "tag_updates", None)
2006
2021
if not is_quiet():
2007
2022
if self.old_revid != self.new_revid:
2008
if self.new_revno is not None:
2009
note(gettext('Pushed up to revision %d.'),
2012
note(gettext('Pushed up to revision id %s.'),
2013
self.new_revid.decode('utf-8'))
2023
note(gettext('Pushed up to revision %d.') % self.new_revno)
2014
2024
if tag_updates:
2015
2025
note(ngettext('%d tag updated.', '%d tags updated.',
2016
2026
len(tag_updates)) % len(tag_updates))
2066
2076
raise NotImplementedError(klass._get_branch_formats_to_test)
2068
2078
def pull(self, overwrite=False, stop_revision=None,
2069
possible_transports=None, local=False, tag_selector=None):
2079
possible_transports=None, local=False):
2070
2080
"""Mirror source into target branch.
2072
2082
The target branch is considered to be 'local', having low latency.
2076
2086
raise NotImplementedError(self.pull)
2078
2088
def push(self, overwrite=False, stop_revision=None, lossy=False,
2079
_override_hook_source_branch=None, tag_selector=None):
2089
_override_hook_source_branch=None):
2080
2090
"""Mirror the source branch into the target branch.
2082
2092
The source branch is considered to be 'local', having low latency.
2084
2094
raise NotImplementedError(self.push)
2086
def copy_content_into(self, revision_id=None, tag_selector=None):
2096
def copy_content_into(self, revision_id=None):
2087
2097
"""Copy the content of source into target
2090
if not None, the revision history in the new branch will
2091
be truncated to end with revision_id.
2092
:param tag_selector: Optional callback that can decide
2093
to copy or not copy tags.
2099
revision_id: if not None, the revision history in the new branch will
2100
be truncated to end with revision_id.
2095
2102
raise NotImplementedError(self.copy_content_into)
2097
def fetch(self, stop_revision=None, limit=None, lossy=False):
2104
def fetch(self, stop_revision=None, limit=None):
2098
2105
"""Fetch revisions.
2100
2107
:param stop_revision: Last revision to fetch
2101
2108
:param limit: Optional rough limit of revisions to fetch
2102
:return: FetchResult object
2104
2110
raise NotImplementedError(self.fetch)
2106
def update_references(self):
2107
"""Import reference information from source to target.
2109
raise NotImplementedError(self.update_references)
2112
2113
def _fix_overwrite_type(overwrite):
2113
2114
if isinstance(overwrite, bool):
2137
2138
return format._custom_format
2140
def copy_content_into(self, revision_id=None, tag_selector=None):
2141
def copy_content_into(self, revision_id=None):
2141
2142
"""Copy the content of source into target
2143
2144
revision_id: if not None, the revision history in the new branch will
2144
2145
be truncated to end with revision_id.
2146
2147
with self.source.lock_read(), self.target.lock_write():
2148
self.source.update_references(self.target)
2147
2149
self.source._synchronize_history(self.target, revision_id)
2148
self.update_references()
2150
2151
parent = self.source.get_parent()
2151
2152
except errors.InaccessibleParent as e:
2155
2156
self.target.set_parent(parent)
2156
2157
if self.source._push_should_merge_tags():
2157
self.source.tags.merge_to(self.target.tags, selector=tag_selector)
2158
self.source.tags.merge_to(self.target.tags)
2159
def fetch(self, stop_revision=None, limit=None, lossy=False):
2160
def fetch(self, stop_revision=None, limit=None):
2160
2161
if self.target.base == self.source.base:
2162
2163
with self.source.lock_read(), self.target.lock_write():
2171
2172
fetch_spec = fetch_spec_factory.make_fetch_spec()
2172
2173
return self.target.repository.fetch(
2173
2174
self.source.repository,
2175
2175
fetch_spec=fetch_spec)
2177
2177
def _update_revisions(self, stop_revision=None, overwrite=False,
2216
2216
def pull(self, overwrite=False, stop_revision=None,
2217
2217
possible_transports=None, run_hooks=True,
2218
_override_hook_target=None, local=False,
2218
_override_hook_target=None, local=False):
2220
2219
"""Pull from source into self, updating my master if any.
2222
2221
:param run_hooks: Private parameter - if false, this branch
2223
2222
is being called because it's the master of the primary branch,
2224
2223
so it should not run its hooks.
2226
with cleanup.ExitStack() as exit_stack:
2227
exit_stack.enter_context(self.target.lock_write())
2225
with self.target.lock_write():
2228
2226
bound_location = self.target.get_bound_location()
2229
2227
if local and not bound_location:
2230
2228
raise errors.LocalRequiresBoundBranch()
2243
2241
# not pulling from master, so we need to update master.
2244
2242
master_branch = self.target.get_master_branch(
2245
2243
possible_transports)
2246
exit_stack.enter_context(master_branch.lock_write())
2248
# pull from source into master.
2250
self.source, overwrite, stop_revision, run_hooks=False,
2251
tag_selector=tag_selector)
2253
overwrite, stop_revision, _hook_master=master_branch,
2254
run_hooks=run_hooks,
2255
_override_hook_target=_override_hook_target,
2256
merge_tags_to_master=not source_is_master,
2257
tag_selector=tag_selector)
2244
master_branch.lock_write()
2247
# pull from source into master.
2249
self.source, overwrite, stop_revision, run_hooks=False)
2251
overwrite, stop_revision, _hook_master=master_branch,
2252
run_hooks=run_hooks,
2253
_override_hook_target=_override_hook_target,
2254
merge_tags_to_master=not source_is_master)
2257
master_branch.unlock()
2259
2259
def push(self, overwrite=False, stop_revision=None, lossy=False,
2260
_override_hook_source_branch=None, tag_selector=None):
2260
_override_hook_source_branch=None):
2261
2261
"""See InterBranch.push.
2263
2263
This is the basic concrete implementation of push()
2289
2289
with master_branch.lock_write():
2290
2290
# push into the master from the source branch.
2291
2291
master_inter = InterBranch.get(self.source, master_branch)
2292
master_inter._basic_push(
2293
overwrite, stop_revision, tag_selector=tag_selector)
2292
master_inter._basic_push(overwrite, stop_revision)
2294
2293
# and push into the target branch from the source. Note
2295
2294
# that we push from the source branch again, because it's
2296
2295
# considered the highest bandwidth repository.
2297
result = self._basic_push(
2298
overwrite, stop_revision, tag_selector=tag_selector)
2296
result = self._basic_push(overwrite, stop_revision)
2299
2297
result.master_branch = master_branch
2300
2298
result.local_branch = self.target
2303
2301
master_branch = None
2304
2302
# no master branch
2305
result = self._basic_push(
2306
overwrite, stop_revision, tag_selector=tag_selector)
2303
result = self._basic_push(overwrite, stop_revision)
2307
2304
# TODO: Why set master_branch and local_branch if there's no
2308
2305
# binding? Maybe cleaner to just leave them unset? -- mbp
2315
def _basic_push(self, overwrite, stop_revision, tag_selector=None):
2312
def _basic_push(self, overwrite, stop_revision):
2316
2313
"""Basic implementation of push without bound branches or hooks.
2318
2315
Must be called with source read locked and target write locked.
2321
2318
result.source_branch = self.source
2322
2319
result.target_branch = self.target
2323
2320
result.old_revno, result.old_revid = self.target.last_revision_info()
2321
self.source.update_references(self.target)
2324
2322
overwrite = _fix_overwrite_type(overwrite)
2325
2323
if result.old_revid != stop_revision:
2326
2324
# We assume that during 'push' this repository is closer than
2331
2329
if self.source._push_should_merge_tags():
2332
2330
result.tag_updates, result.tag_conflicts = (
2333
2331
self.source.tags.merge_to(
2334
self.target.tags, "tags" in overwrite, selector=tag_selector))
2335
self.update_references()
2332
self.target.tags, "tags" in overwrite))
2336
2333
result.new_revno, result.new_revid = self.target.last_revision_info()
2339
2336
def _pull(self, overwrite=False, stop_revision=None,
2340
2337
possible_transports=None, _hook_master=None, run_hooks=True,
2341
2338
_override_hook_target=None, local=False,
2342
merge_tags_to_master=True, tag_selector=None):
2339
merge_tags_to_master=True):
2343
2340
"""See Branch.pull.
2345
2342
This function is the core worker, used by GenericInterBranch.pull to
2368
2365
with self.source.lock_read():
2369
2366
# We assume that during 'pull' the target repository is closer than
2370
2367
# the source one.
2368
self.source.update_references(self.target)
2371
2369
graph = self.target.repository.get_graph(self.source.repository)
2372
2370
# TODO: Branch formats should have a flag that indicates
2373
2371
# that revno's are expensive, and pull() should honor that flag.
2383
2381
result.tag_updates, result.tag_conflicts = (
2384
2382
self.source.tags.merge_to(
2385
2383
self.target.tags, "tags" in overwrite,
2386
ignore_master=not merge_tags_to_master,
2387
selector=tag_selector))
2388
self.update_references()
2384
ignore_master=not merge_tags_to_master))
2389
2385
result.new_revno, result.new_revid = (
2390
2386
self.target.last_revision_info())
2391
2387
if _hook_master:
2402
def update_references(self):
2403
if not getattr(self.source._format, 'supports_reference_locations', False):
2405
reference_dict = self.source._get_all_reference_info()
2406
if len(reference_dict) == 0:
2408
old_base = self.source.base
2409
new_base = self.target.base
2410
target_reference_dict = self.target._get_all_reference_info()
2411
for tree_path, (branch_location, file_id) in viewitems(reference_dict):
2413
branch_location = urlutils.rebase_url(branch_location,
2415
except urlutils.InvalidRebaseURLs:
2416
# Fall back to absolute URL
2417
branch_location = urlutils.join(old_base, branch_location)
2418
target_reference_dict.setdefault(
2419
tree_path, (branch_location, file_id))
2420
self.target._set_all_reference_info(target_reference_dict)
2423
2399
InterBranch.register_optimiser(GenericInterBranch)