/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/branch.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
16
16
 
17
 
from __future__ import absolute_import
18
 
 
19
17
from .lazy_import import lazy_import
20
18
lazy_import(globals(), """
 
19
import contextlib
21
20
import itertools
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
49
 
from .sixish import (
50
 
    text_type,
51
 
    viewitems,
52
 
    )
53
 
from .trace import mutter, mutter_callsite, note, is_quiet
 
48
from .trace import mutter, mutter_callsite, note, is_quiet, warning
54
49
 
55
50
 
56
51
class UnstackableBranchFormat(errors.BzrError):
364
359
        provide a more efficient implementation.
365
360
        """
366
361
        if len(revno) == 1:
367
 
            return self.get_rev_id(revno[0])
 
362
            try:
 
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)
674
672
 
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)
678
 
 
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)
682
 
 
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.
685
675
 
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
688
678
                              of the branch.
689
679
        :param limit: Optional rough limit of revisions to fetch
690
680
        :return: None
691
681
        """
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)
695
685
 
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):
787
777
                try:
788
778
                    url.encode('ascii')
789
779
                except UnicodeEncodeError:
1198
1188
        if revno < 1 or revno > self.revno():
1199
1189
            raise errors.InvalidRevisionNumber(revno)
1200
1190
 
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.
1203
1194
 
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.
1209
1200
        """
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)
1215
1207
        return result
1216
1208
 
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.
1220
1212
 
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)
1265
1258
                revno = 1
1266
1259
        destination.set_last_revision_info(revno, revision_id)
1267
1260
 
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.
1270
1263
 
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
1273
1268
        """
1274
1269
        return InterBranch.get(self, destination).copy_content_into(
1275
 
            revision_id=revision_id)
 
1270
            revision_id=revision_id, tag_selector=tag_selector)
1276
1271
 
1277
1272
    def update_references(self, target):
1278
 
        if not getattr(self._format, 'supports_reference_locations', False):
1279
 
            return
1280
 
        reference_dict = self._get_all_reference_info()
1281
 
        if len(reference_dict) == 0:
1282
 
            return
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,
1288
 
                                                  old_base, new_base)
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:
 
1274
            return
 
1275
        return InterBranch.get(self, target).update_references()
1292
1276
 
1293
1277
    def check(self, refs):
1294
1278
        """Check consistency of the branch.
1328
1312
 
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,
 
1316
                                  tag_selector=None):
1332
1317
        """Create a clone of this branch and its bzrdir.
1333
1318
 
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,
1351
 
            no_tree=no_tree)
 
1336
            no_tree=no_tree, tag_selector=tag_selector)
1352
1337
        return dir_to.open_branch()
1353
1338
 
1354
1339
    def create_checkout(self, to_location, revision_id=None,
1355
1340
                        lightweight=False, accelerator_tree=None,
1356
 
                        hardlink=False):
 
1341
                        hardlink=False, recurse_nested=True):
1357
1342
        """Create a checkout of a branch.
1358
1343
 
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
1370
1356
        """
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)
 
1399
                    continue
1411
1400
                reference_parent.create_checkout(
1412
1401
                    tree.abspath(path),
1413
1402
                    basis_tree.get_reference_revision(path), lightweight)
1414
1403
        return tree
1415
1404
 
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()
1422
 
            return reconciler
1423
 
 
1424
 
    def reference_parent(self, path, file_id=None, possible_transports=None):
1425
 
        """Return the parent branch for a tree-reference file_id
1426
 
 
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.
 
1407
 
 
1408
        :return: A `ReconcileResult` object.
1430
1409
        """
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)
1434
1411
 
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."""
1661
1638
        return True
1662
1639
 
 
1640
    def stores_revno(self):
 
1641
        """True if this branch format store revision numbers."""
 
1642
        return True
 
1643
 
1663
1644
 
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.'),
 
2004
                         self.new_revno)
 
2005
                else:
 
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)
2075
2061
 
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.
2079
2065
 
2080
2066
        The target branch is considered to be 'local', having low latency.
2084
2070
        raise NotImplementedError(self.pull)
2085
2071
 
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.
2089
2075
 
2090
2076
        The source branch is considered to be 'local', having low latency.
2091
2077
        """
2092
2078
        raise NotImplementedError(self.push)
2093
2079
 
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
2096
2082
 
2097
 
        revision_id: if not None, the revision history in the new branch will
2098
 
                     be truncated to end with revision_id.
 
2083
        :param 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.
2099
2088
        """
2100
2089
        raise NotImplementedError(self.copy_content_into)
2101
2090
 
2102
 
    def fetch(self, stop_revision=None, limit=None):
 
2091
    def fetch(self, stop_revision=None, limit=None, lossy=False):
2103
2092
        """Fetch revisions.
2104
2093
 
2105
2094
        :param stop_revision: Last revision to fetch
2106
2095
        :param limit: Optional rough limit of revisions to fetch
 
2096
        :return: FetchResult object
2107
2097
        """
2108
2098
        raise NotImplementedError(self.fetch)
2109
2099
 
 
2100
    def update_references(self):
 
2101
        """Import reference information from source to target.
 
2102
        """
 
2103
        raise NotImplementedError(self.update_references)
 
2104
 
2110
2105
 
2111
2106
def _fix_overwrite_type(overwrite):
2112
2107
    if isinstance(overwrite, bool):
2136
2131
            return format._custom_format
2137
2132
        return format
2138
2133
 
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
2141
2136
 
2142
2137
        revision_id: if not None, the revision history in the new branch will
2143
2138
                     be truncated to end with revision_id.
2144
2139
        """
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()
2148
2143
            try:
2149
2144
                parent = self.source.get_parent()
2150
2145
            except errors.InaccessibleParent as e:
2153
2148
                if parent:
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)
2157
2152
 
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:
2160
2155
            return (0, [])
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,
 
2168
                lossy=lossy,
2173
2169
                fetch_spec=fetch_spec)
2174
2170
 
2175
2171
    def _update_revisions(self, stop_revision=None, overwrite=False,
2213
2209
 
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,
 
2213
             tag_selector=None):
2217
2214
        """Pull from source into self, updating my master if any.
2218
2215
 
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.
2222
2219
        """
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()
2243
 
            try:
2244
 
                if master_branch:
2245
 
                    # pull from source into master.
2246
 
                    master_branch.pull(
2247
 
                        self.source, overwrite, stop_revision, run_hooks=False)
2248
 
                return self._pull(
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)
2253
 
            finally:
2254
 
                if master_branch:
2255
 
                    master_branch.unlock()
 
2240
                exit_stack.enter_context(master_branch.lock_write())
 
2241
            if master_branch:
 
2242
                # pull from source into master.
 
2243
                master_branch.pull(
 
2244
                    self.source, overwrite, stop_revision, run_hooks=False,
 
2245
                    tag_selector=tag_selector)
 
2246
            return self._pull(
 
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)
2256
2252
 
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.
2260
2256
 
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
2297
2295
                    _run_hooks()
2298
2296
            else:
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
2304
2303
                # 20070504
2307
2306
                _run_hooks()
2308
2307
            return result
2309
2308
 
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.
2312
2311
 
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()
2332
2331
        return result
2333
2332
 
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.
2339
2338
 
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:
2393
2393
                    hook(result)
2394
2394
            return result
2395
2395
 
 
2396
    def update_references(self):
 
2397
        if not getattr(self.source._format, 'supports_reference_locations', False):
 
2398
            return
 
2399
        reference_dict = self.source._get_all_reference_info()
 
2400
        if len(reference_dict) == 0:
 
2401
            return
 
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():
 
2406
            try:
 
2407
                branch_location = urlutils.rebase_url(branch_location,
 
2408
                                                      old_base, new_base)
 
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)
 
2415
 
2396
2416
 
2397
2417
InterBranch.register_optimiser(GenericInterBranch)