Merge lp:~spiv/bzr/sprout-does-not-reopen-repo into lp:bzr

Proposed by Andrew Bennetts
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 5570
Proposed branch: lp:~spiv/bzr/sprout-does-not-reopen-repo
Merge into: lp:bzr
Diff against target: 550 lines (+133/-47)
10 files modified
bzrlib/branch.py (+45/-22)
bzrlib/bzrdir.py (+7/-3)
bzrlib/controldir.py (+26/-5)
bzrlib/remote.py (+27/-11)
bzrlib/tests/blackbox/test_branch.py (+1/-1)
bzrlib/tests/test_branch.py (+1/-1)
bzrlib/tests/test_bzrdir.py (+7/-0)
bzrlib/tests/test_foreign.py (+4/-2)
doc/en/release-notes/bzr-2.3.txt (+12/-0)
doc/en/whats-new/whats-new-in-2.3.txt (+3/-2)
To merge this branch: bzr merge lp:~spiv/bzr/sprout-does-not-reopen-repo
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
John A Meinel Needs Information
Review via email: mp+41037@code.launchpad.net

Commit message

Avoid reopening (and relocking) the same branches/repositories in ControlDir.sprout.

Description of the change

This is a step towards bug 309682. It's not quite in a finished state, but I would appreciate feedback.

The idea here is pretty simple: ControlDir.sprout shouldn't open the same repository multiple times. This patch achieves that, and sure enough it causes the worst HPSS ratchet to improve by another notch.

There are a couple of dissatisfying aspects, though:

 1. sprout calls branch.sprout after opening the result repository. So this
    patch adds a new param (repository/found_repository) to Branch.sprout,
    Branch.create_branch, BranchFormat.open, etc. This touches a lot of
    signatures. Perhaps something more radical is called for, like a single
    API to acquire or create both a branch and a repo?

 2. For some reason, when stacking, the same fallback is added twice, which
    confuses _unstack. (Maybe this only happens for RemoteBranches?) I'm
    not sure exactly why this happens, but the hack to simply ignore
    duplicate _activate_fallback_location calls doesn't feel right. This
    is an XXX in the patch.

 3. It's at least theoretically possible that the repository object we've
    already acquired locally doesn't have the same location as the one
    suggested by the server (e.g. if the server has a bug, or there's a race
    with another client changing something, or perhaps it can even happen
    legitimately?). What ought to happen in this case? I suppose this is
    already an issue, which the code currently ignores because of the
    independent open_repository calls. This is an XXX and AssertionError
    in the patch.

Comments on these or any other parts of this patch are very welcome.

To post a comment you must log in.
Revision history for this message
John A Meinel (jameinel) wrote :

Sorry about the delay on this. In general, I really like the idea of this patch.

I would at least mutter a warning about the double fallback, we don't have to display it on the screen. It would be nice to fix it, but you don't have to get everything right on the first attempt.

I really don't like the proliferation of "no, really, use this object" parameters. I think the problem is that BzrDir.sprout() wants to be the master, but it still defers some of the work to Branch/Repo/WorkingTree. I don't have a better answer, though. I know you mentioned this, too.

I'd personally really like to see a:
  X.open_write_locked(create=True)

Or something along those lines.

I think part of the confusion is also that if you got something which is a Branch, and you think there is a tree there, the standard use is:
 branch.bzrdir.open_workingtree()

However, that *reopens* the Branch and Repository.

We also have a lot of code that does stuff like:
  a) If there is a WT, do X
  b) If not, but there is a Branch, do Y
  c) If not, but there is a Repository, do Z

It would be nice to simplify a lot of that if we could. That is some of what "BzrDir.open_containing_tree_or_branch" is supposed to be. Though many times I want "open_containing_tree_or_direct_branch" (don't recurse for a branch, but you can recurse for a Tree).

This is potentially disruptive to bzr-svn, because it adds a new parameter that BzrDir will be passing down the stack. As such, adding a cleaner new api may be an easier way to get what we want. It would let us add a compatability shim for objects that don't implement the new interface, and could be cleaner overall.

Then again, the practical side of me says, you've done the work, it works, even if it isn't perfect, it is better than what we have.

review: Needs Information
Revision history for this message
Vincent Ladeuil (vila) wrote :

I would almost agree with John about the practical aspect (it works, let's land).

On the other hand, you have already spent a good amount of time on this which will be necessary again if we want to provide a cleaner solution.

But...

267 registry,
268 + repository,
269 revision as _mod_revision,

seems weird. I'd like jelmer's feedback here, but I'm pretty sure we pull controldir out of bzrdir to make sure it was isolated from bzr specifics and having to reintroduce repository here sounds like a step backward.

Also,

10 + # This fallback is already configured. This probably only
11 + # happens because BzrDir.sprout is a horrible mess. To avoid
12 + # confusing _unstack we don't add this a second time.
13 + # XXX: log a warning, maybe?
14 + return

is a sign that you're close to nailing the issue and I think you should ;)

On the overall, we have a 'repository' attribute on the branch object, so having to provide it should only occur when we are creating this object. Do we have that many different ways to create branches that you end up modifying so many signatures ? Doesn't it reveal a deeper issue ?

review: Needs Information
Revision history for this message
Andrew Bennetts (spiv) wrote :
Download full text (3.3 KiB)

Vincent Ladeuil wrote:
> Review: Needs Information
> I would almost agree with John about the practical aspect (it works, let's land).
>
> On the other hand, you have already spent a good amount of time on this which will be necessary again if we want to provide a cleaner solution.
>
> But...
>
> 267 registry,
> 268 + repository,
> 269 revision as _mod_revision,
>
> seems weird. I'd like jelmer's feedback here, but I'm pretty sure we pull
> controldir out of bzrdir to make sure it was isolated from bzr specifics and
> having to reintroduce repository here sounds like a step backward.

That seems to be a leftover from an earlier version of this patch. It's
not actually used. I've removed that import (and gardened a couple of
others noticed by pyflakes). Thanks for spotting that.

> Also,
>
> 10 + # This fallback is already configured. This probably only
> 11 + # happens because BzrDir.sprout is a horrible mess. To avoid
> 12 + # confusing _unstack we don't add this a second time.
> 13 + # XXX: log a warning, maybe?
> 14 + return
>
> is a sign that you're close to nailing the issue and I think you should ;)

If I thought I was close I'd have nailed it already!

> On the overall, we have a 'repository' attribute on the branch object, so
> having to provide it should only occur when we are creating this object. Do we
> have that many different ways to create branches that you end up modifying so
> many signatures ? Doesn't it reveal a deeper issue ?

I agree, I think there's a deeper issue here, as my cover letter tried
to say. I'm not sure what a good way solution is.

Regarding the number of ways to create branches, here's one way to count
that, based on this diff:

 * 8 definitions of BranchFormat.initialize
 * 4 definitions of ControlDir.create_branch
 * 1 definition of Branch.sprout

That doesn't count definitions in test code.

Plus there's open methods, which create branch objects, which are also
affected:

 * 3 definitions of BranchFormat.open

So yes, we do have 16 different ways to create a branch object, hence
why I touch so many methods. I don't see an obvious solution to that.

A possible approach might be to have the low-level
BranchFormat.open/initialize methods be designed to return Branches
without the repository object being set, so returning only partially
initialized Branch objects. Then have higher-level APIs (along the
lines of the existing open_containg_tree_or_branch? or something new
here too?) that assemble these into fully initialized objects with
.repository attributes filled in. There's the related consideration
that parts of the code want to open and/or create some combination of
branch/repo/tree in one logical step (and ideally one network
roundtrip), but the current design opens each of the bzrdir, repository,
and branch separately. I think this is part of the cause of the double
add_fallback ugliness that's an XXX for that matter.

So my suspicion is a fairly fundamental redesign of how we open/create
these things would be help a lot... but getting it right may take some
effort. And it's likely to be way more disruptive to bzr-svn and
friends than a new keyword param! Until someone has a concrete ...

Read more...

Revision history for this message
Jelmer Vernooij (jelmer) wrote :

I've only reviewed this for possible issues with foreign VCS implementations, and it seems fine in that regard (bearing in mind the new repository= parameter).

Revision history for this message
Vincent Ladeuil (vila) wrote :

Thanks, Jelmer !

Ok, so my main concerns are addressed, just a few remarks, possibly tweaks:

379 + if not isinstance(repository, RemoteRepository):
380 + raise AssertionError('xxx %r' % (repository,))

replacing this 'xxx' with something meaningful can only help people confronted with the raised assertion ;)

406 + raise AssertionError(
407 + 'diff %r: %r vs. (%r + %r)' %
408 + (url_diff, repository.user_url, medium.base, repo_path))

 ditto (less sure here, the tb may be enough)

And finally, you summary in your reply above would be nice to keep in devnotes IMHO. Let's not redo this the next time we want to address the deeper issue.

review: Approve
Revision history for this message
Andrew Bennetts (spiv) wrote :

sent to pqm by email

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/branch.py'
--- bzrlib/branch.py 2010-11-18 00:22:24 +0000
+++ bzrlib/branch.py 2010-12-15 00:41:26 +0000
@@ -105,6 +105,13 @@
105105
106 def _activate_fallback_location(self, url):106 def _activate_fallback_location(self, url):
107 """Activate the branch/repository from url as a fallback repository."""107 """Activate the branch/repository from url as a fallback repository."""
108 for existing_fallback_repo in self.repository._fallback_repositories:
109 if existing_fallback_repo.user_url == url:
110 # This fallback is already configured. This probably only
111 # happens because BzrDir.sprout is a horrible mess. To avoid
112 # confusing _unstack we don't add this a second time.
113 mutter('duplicate activation of fallback %r on %r', url, self)
114 return
108 repo = self._get_fallback_repository(url)115 repo = self._get_fallback_repository(url)
109 if repo.has_same_location(self.repository):116 if repo.has_same_location(self.repository):
110 raise errors.UnstackableLocationError(self.user_url, url)117 raise errors.UnstackableLocationError(self.user_url, url)
@@ -809,7 +816,8 @@
809 old_repository = self.repository816 old_repository = self.repository
810 if len(old_repository._fallback_repositories) != 1:817 if len(old_repository._fallback_repositories) != 1:
811 raise AssertionError("can't cope with fallback repositories "818 raise AssertionError("can't cope with fallback repositories "
812 "of %r" % (self.repository,))819 "of %r (fallbacks: %r)" % (old_repository,
820 old_repository._fallback_repositories))
813 # Open the new repository object.821 # Open the new repository object.
814 # Repositories don't offer an interface to remove fallback822 # Repositories don't offer an interface to remove fallback
815 # repositories today; take the conceptually simpler option and just823 # repositories today; take the conceptually simpler option and just
@@ -1266,7 +1274,8 @@
1266 return result1274 return result
12671275
1268 @needs_read_lock1276 @needs_read_lock
1269 def sprout(self, to_bzrdir, revision_id=None, repository_policy=None):1277 def sprout(self, to_bzrdir, revision_id=None, repository_policy=None,
1278 repository=None):
1270 """Create a new line of development from the branch, into to_bzrdir.1279 """Create a new line of development from the branch, into to_bzrdir.
12711280
1272 to_bzrdir controls the branch format.1281 to_bzrdir controls the branch format.
@@ -1277,7 +1286,7 @@
1277 if (repository_policy is not None and1286 if (repository_policy is not None and
1278 repository_policy.requires_stacking()):1287 repository_policy.requires_stacking()):
1279 to_bzrdir._format.require_stacking(_skip_repo=True)1288 to_bzrdir._format.require_stacking(_skip_repo=True)
1280 result = to_bzrdir.create_branch()1289 result = to_bzrdir.create_branch(repository=repository)
1281 result.lock_write()1290 result.lock_write()
1282 try:1291 try:
1283 if repository_policy is not None:1292 if repository_policy is not None:
@@ -1634,7 +1643,8 @@
1634 hook(params)1643 hook(params)
16351644
1636 def _initialize_helper(self, a_bzrdir, utf8_files, name=None,1645 def _initialize_helper(self, a_bzrdir, utf8_files, name=None,
1637 lock_type='metadir', set_format=True):1646 repository=None, lock_type='metadir',
1647 set_format=True):
1638 """Initialize a branch in a bzrdir, with specified files1648 """Initialize a branch in a bzrdir, with specified files
16391649
1640 :param a_bzrdir: The bzrdir to initialize the branch in1650 :param a_bzrdir: The bzrdir to initialize the branch in
@@ -1674,11 +1684,12 @@
1674 finally:1684 finally:
1675 if lock_taken:1685 if lock_taken:
1676 control_files.unlock()1686 control_files.unlock()
1677 branch = self.open(a_bzrdir, name, _found=True)1687 branch = self.open(a_bzrdir, name, _found=True,
1688 found_repository=repository)
1678 self._run_post_branch_init_hooks(a_bzrdir, name, branch)1689 self._run_post_branch_init_hooks(a_bzrdir, name, branch)
1679 return branch1690 return branch
16801691
1681 def initialize(self, a_bzrdir, name=None):1692 def initialize(self, a_bzrdir, name=None, repository=None):
1682 """Create a branch of this format in a_bzrdir.1693 """Create a branch of this format in a_bzrdir.
1683 1694
1684 :param name: Name of the colocated branch to create.1695 :param name: Name of the colocated branch to create.
@@ -1718,7 +1729,8 @@
1718 """1729 """
1719 raise NotImplementedError(self.network_name)1730 raise NotImplementedError(self.network_name)
17201731
1721 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):1732 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
1733 found_repository=None):
1722 """Return the branch object for a_bzrdir1734 """Return the branch object for a_bzrdir
17231735
1724 :param a_bzrdir: A BzrDir that contains a branch.1736 :param a_bzrdir: A BzrDir that contains a branch.
@@ -2018,8 +2030,11 @@
2018 """See BranchFormat.get_format_description()."""2030 """See BranchFormat.get_format_description()."""
2019 return "Branch format 4"2031 return "Branch format 4"
20202032
2021 def initialize(self, a_bzrdir, name=None):2033 def initialize(self, a_bzrdir, name=None, repository=None):
2022 """Create a branch of this format in a_bzrdir."""2034 """Create a branch of this format in a_bzrdir."""
2035 if repository is not None:
2036 raise NotImplementedError(
2037 "initialize(repository=<not None>) on %r" % (self,))
2023 utf8_files = [('revision-history', ''),2038 utf8_files = [('revision-history', ''),
2024 ('branch-name', ''),2039 ('branch-name', ''),
2025 ]2040 ]
@@ -2034,16 +2049,19 @@
2034 """The network name for this format is the control dirs disk label."""2049 """The network name for this format is the control dirs disk label."""
2035 return self._matchingbzrdir.get_format_string()2050 return self._matchingbzrdir.get_format_string()
20362051
2037 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):2052 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
2053 found_repository=None):
2038 """See BranchFormat.open()."""2054 """See BranchFormat.open()."""
2039 if not _found:2055 if not _found:
2040 # we are being called directly and must probe.2056 # we are being called directly and must probe.
2041 raise NotImplementedError2057 raise NotImplementedError
2058 if found_repository is None:
2059 found_repository = a_bzrdir.open_repository()
2042 return BzrBranch(_format=self,2060 return BzrBranch(_format=self,
2043 _control_files=a_bzrdir._control_files,2061 _control_files=a_bzrdir._control_files,
2044 a_bzrdir=a_bzrdir,2062 a_bzrdir=a_bzrdir,
2045 name=name,2063 name=name,
2046 _repository=a_bzrdir.open_repository())2064 _repository=found_repository)
20472065
2048 def __str__(self):2066 def __str__(self):
2049 return "Bazaar-NG branch format 4"2067 return "Bazaar-NG branch format 4"
@@ -2063,7 +2081,8 @@
2063 """2081 """
2064 return self.get_format_string()2082 return self.get_format_string()
20652083
2066 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False):2084 def open(self, a_bzrdir, name=None, _found=False, ignore_fallbacks=False,
2085 found_repository=None):
2067 """See BranchFormat.open()."""2086 """See BranchFormat.open()."""
2068 if not _found:2087 if not _found:
2069 format = BranchFormat.find_format(a_bzrdir, name=name)2088 format = BranchFormat.find_format(a_bzrdir, name=name)
@@ -2074,11 +2093,13 @@
2074 try:2093 try:
2075 control_files = lockable_files.LockableFiles(transport, 'lock',2094 control_files = lockable_files.LockableFiles(transport, 'lock',
2076 lockdir.LockDir)2095 lockdir.LockDir)
2096 if found_repository is None:
2097 found_repository = a_bzrdir.find_repository()
2077 return self._branch_class()(_format=self,2098 return self._branch_class()(_format=self,
2078 _control_files=control_files,2099 _control_files=control_files,
2079 name=name,2100 name=name,
2080 a_bzrdir=a_bzrdir,2101 a_bzrdir=a_bzrdir,
2081 _repository=a_bzrdir.find_repository(),2102 _repository=found_repository,
2082 ignore_fallbacks=ignore_fallbacks)2103 ignore_fallbacks=ignore_fallbacks)
2083 except errors.NoSuchFile:2104 except errors.NoSuchFile:
2084 raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)2105 raise errors.NotBranchError(path=transport.base, bzrdir=a_bzrdir)
@@ -2116,12 +2137,12 @@
2116 """See BranchFormat.get_format_description()."""2137 """See BranchFormat.get_format_description()."""
2117 return "Branch format 5"2138 return "Branch format 5"
21182139
2119 def initialize(self, a_bzrdir, name=None):2140 def initialize(self, a_bzrdir, name=None, repository=None):
2120 """Create a branch of this format in a_bzrdir."""2141 """Create a branch of this format in a_bzrdir."""
2121 utf8_files = [('revision-history', ''),2142 utf8_files = [('revision-history', ''),
2122 ('branch-name', ''),2143 ('branch-name', ''),
2123 ]2144 ]
2124 return self._initialize_helper(a_bzrdir, utf8_files, name)2145 return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
21252146
2126 def supports_tags(self):2147 def supports_tags(self):
2127 return False2148 return False
@@ -2149,13 +2170,13 @@
2149 """See BranchFormat.get_format_description()."""2170 """See BranchFormat.get_format_description()."""
2150 return "Branch format 6"2171 return "Branch format 6"
21512172
2152 def initialize(self, a_bzrdir, name=None):2173 def initialize(self, a_bzrdir, name=None, repository=None):
2153 """Create a branch of this format in a_bzrdir."""2174 """Create a branch of this format in a_bzrdir."""
2154 utf8_files = [('last-revision', '0 null:\n'),2175 utf8_files = [('last-revision', '0 null:\n'),
2155 ('branch.conf', ''),2176 ('branch.conf', ''),
2156 ('tags', ''),2177 ('tags', ''),
2157 ]2178 ]
2158 return self._initialize_helper(a_bzrdir, utf8_files, name)2179 return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
21592180
2160 def make_tags(self, branch):2181 def make_tags(self, branch):
2161 """See bzrlib.branch.BranchFormat.make_tags()."""2182 """See bzrlib.branch.BranchFormat.make_tags()."""
@@ -2179,14 +2200,14 @@
2179 """See BranchFormat.get_format_description()."""2200 """See BranchFormat.get_format_description()."""
2180 return "Branch format 8"2201 return "Branch format 8"
21812202
2182 def initialize(self, a_bzrdir, name=None):2203 def initialize(self, a_bzrdir, name=None, repository=None):
2183 """Create a branch of this format in a_bzrdir."""2204 """Create a branch of this format in a_bzrdir."""
2184 utf8_files = [('last-revision', '0 null:\n'),2205 utf8_files = [('last-revision', '0 null:\n'),
2185 ('branch.conf', ''),2206 ('branch.conf', ''),
2186 ('tags', ''),2207 ('tags', ''),
2187 ('references', '')2208 ('references', '')
2188 ]2209 ]
2189 return self._initialize_helper(a_bzrdir, utf8_files, name)2210 return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
21902211
2191 def __init__(self):2212 def __init__(self):
2192 super(BzrBranchFormat8, self).__init__()2213 super(BzrBranchFormat8, self).__init__()
@@ -2215,13 +2236,13 @@
2215 This format was introduced in bzr 1.6.2236 This format was introduced in bzr 1.6.
2216 """2237 """
22172238
2218 def initialize(self, a_bzrdir, name=None):2239 def initialize(self, a_bzrdir, name=None, repository=None):
2219 """Create a branch of this format in a_bzrdir."""2240 """Create a branch of this format in a_bzrdir."""
2220 utf8_files = [('last-revision', '0 null:\n'),2241 utf8_files = [('last-revision', '0 null:\n'),
2221 ('branch.conf', ''),2242 ('branch.conf', ''),
2222 ('tags', ''),2243 ('tags', ''),
2223 ]2244 ]
2224 return self._initialize_helper(a_bzrdir, utf8_files, name)2245 return self._initialize_helper(a_bzrdir, utf8_files, name, repository)
22252246
2226 def _branch_class(self):2247 def _branch_class(self):
2227 return BzrBranch72248 return BzrBranch7
@@ -2269,7 +2290,8 @@
2269 transport = a_bzrdir.get_branch_transport(None, name=name)2290 transport = a_bzrdir.get_branch_transport(None, name=name)
2270 location = transport.put_bytes('location', to_branch.base)2291 location = transport.put_bytes('location', to_branch.base)
22712292
2272 def initialize(self, a_bzrdir, name=None, target_branch=None):2293 def initialize(self, a_bzrdir, name=None, target_branch=None,
2294 repository=None):
2273 """Create a branch of this format in a_bzrdir."""2295 """Create a branch of this format in a_bzrdir."""
2274 if target_branch is None:2296 if target_branch is None:
2275 # this format does not implement branch itself, thus the implicit2297 # this format does not implement branch itself, thus the implicit
@@ -2303,7 +2325,8 @@
2303 return clone2325 return clone
23042326
2305 def open(self, a_bzrdir, name=None, _found=False, location=None,2327 def open(self, a_bzrdir, name=None, _found=False, location=None,
2306 possible_transports=None, ignore_fallbacks=False):2328 possible_transports=None, ignore_fallbacks=False,
2329 found_repository=None):
2307 """Return the branch that the branch reference in a_bzrdir points at.2330 """Return the branch that the branch reference in a_bzrdir points at.
23082331
2309 :param a_bzrdir: A BzrDir that contains a branch.2332 :param a_bzrdir: A BzrDir that contains a branch.
23102333
=== modified file 'bzrlib/bzrdir.py'
--- bzrlib/bzrdir.py 2010-12-07 09:06:39 +0000
+++ bzrlib/bzrdir.py 2010-12-15 00:41:26 +0000
@@ -1027,8 +1027,11 @@
1027 tree.clone(result)1027 tree.clone(result)
1028 return result1028 return result
10291029
1030 def create_branch(self, name=None):1030 def create_branch(self, name=None, repository=None):
1031 """See BzrDir.create_branch."""1031 """See BzrDir.create_branch."""
1032 if repository is not None:
1033 raise NotImplementedError(
1034 "create_branch(repository=<not None>) on %r" % (self,))
1032 return self._format.get_branch_format().initialize(self, name=name)1035 return self._format.get_branch_format().initialize(self, name=name)
10331036
1034 def destroy_branch(self, name=None):1037 def destroy_branch(self, name=None):
@@ -1264,9 +1267,10 @@
1264 """See BzrDir.can_convert_format()."""1267 """See BzrDir.can_convert_format()."""
1265 return True1268 return True
12661269
1267 def create_branch(self, name=None):1270 def create_branch(self, name=None, repository=None):
1268 """See BzrDir.create_branch."""1271 """See BzrDir.create_branch."""
1269 return self._format.get_branch_format().initialize(self, name=name)1272 return self._format.get_branch_format().initialize(self, name=name,
1273 repository=repository)
12701274
1271 def destroy_branch(self, name=None):1275 def destroy_branch(self, name=None):
1272 """See BzrDir.create_branch."""1276 """See BzrDir.create_branch."""
12731277
=== modified file 'bzrlib/controldir.py'
--- bzrlib/controldir.py 2010-09-10 09:46:15 +0000
+++ bzrlib/controldir.py 2010-12-15 00:41:26 +0000
@@ -27,11 +27,10 @@
27import textwrap27import textwrap
2828
29from bzrlib import (29from bzrlib import (
30 cleanup,
30 errors,31 errors,
31 graph,32 graph,
32 registry,
33 revision as _mod_revision,33 revision as _mod_revision,
34 symbol_versioning,
35 urlutils,34 urlutils,
36 )35 )
37from bzrlib.push import (36from bzrlib.push import (
@@ -47,6 +46,8 @@
4746
48""")47""")
4948
49from bzrlib import registry
50
5051
51class ControlComponent(object):52class ControlComponent(object):
52 """Abstract base class for control directory components.53 """Abstract base class for control directory components.
@@ -143,7 +144,7 @@
143 """Destroy the repository in this ControlDir."""144 """Destroy the repository in this ControlDir."""
144 raise NotImplementedError(self.destroy_repository)145 raise NotImplementedError(self.destroy_repository)
145146
146 def create_branch(self, name=None):147 def create_branch(self, name=None, repository=None):
147 """Create a branch in this ControlDir.148 """Create a branch in this ControlDir.
148149
149 :param name: Name of the colocated branch to create, None for150 :param name: Name of the colocated branch to create, None for
@@ -364,6 +365,19 @@
364 :param create_tree_if_local: If true, a working-tree will be created365 :param create_tree_if_local: If true, a working-tree will be created
365 when working locally.366 when working locally.
366 """367 """
368 operation = cleanup.OperationWithCleanups(self._sprout)
369 return operation.run(url, revision_id=revision_id,
370 force_new_repo=force_new_repo, recurse=recurse,
371 possible_transports=possible_transports,
372 accelerator_tree=accelerator_tree, hardlink=hardlink,
373 stacked=stacked, source_branch=source_branch,
374 create_tree_if_local=create_tree_if_local)
375
376 def _sprout(self, op, url, revision_id=None, force_new_repo=False,
377 recurse='down', possible_transports=None,
378 accelerator_tree=None, hardlink=False, stacked=False,
379 source_branch=None, create_tree_if_local=True):
380 add_cleanup = op.add_cleanup
367 target_transport = get_transport(url, possible_transports)381 target_transport = get_transport(url, possible_transports)
368 target_transport.ensure_base()382 target_transport.ensure_base()
369 cloning_format = self.cloning_metadir(stacked)383 cloning_format = self.cloning_metadir(stacked)
@@ -373,6 +387,7 @@
373 # even if the origin was stacked387 # even if the origin was stacked
374 stacked_branch_url = None388 stacked_branch_url = None
375 if source_branch is not None:389 if source_branch is not None:
390 add_cleanup(source_branch.lock_read().unlock)
376 if stacked:391 if stacked:
377 stacked_branch_url = self.root_transport.base392 stacked_branch_url = self.root_transport.base
378 source_repository = source_branch.repository393 source_repository = source_branch.repository
@@ -388,9 +403,14 @@
388 source_repository = self.open_repository()403 source_repository = self.open_repository()
389 except errors.NoRepositoryPresent:404 except errors.NoRepositoryPresent:
390 source_repository = None405 source_repository = None
406 else:
407 add_cleanup(source_repository.lock_read().unlock)
408 else:
409 add_cleanup(source_branch.lock_read().unlock)
391 repository_policy = result.determine_repository_policy(410 repository_policy = result.determine_repository_policy(
392 force_new_repo, stacked_branch_url, require_stacking=stacked)411 force_new_repo, stacked_branch_url, require_stacking=stacked)
393 result_repo, is_new_repo = repository_policy.acquire_repository()412 result_repo, is_new_repo = repository_policy.acquire_repository()
413 add_cleanup(result_repo.lock_write().unlock)
394 is_stacked = stacked or (len(result_repo._fallback_repositories) != 0)414 is_stacked = stacked or (len(result_repo._fallback_repositories) != 0)
395 if is_new_repo and revision_id is not None and not is_stacked:415 if is_new_repo and revision_id is not None and not is_stacked:
396 fetch_spec = graph.PendingAncestryResult(416 fetch_spec = graph.PendingAncestryResult(
@@ -412,7 +432,8 @@
412 result_branch = result.create_branch()432 result_branch = result.create_branch()
413 else:433 else:
414 result_branch = source_branch.sprout(result,434 result_branch = source_branch.sprout(result,
415 revision_id=revision_id, repository_policy=repository_policy)435 revision_id=revision_id, repository_policy=repository_policy,
436 repository=result_repo)
416 mutter("created new branch %r" % (result_branch,))437 mutter("created new branch %r" % (result_branch,))
417438
418 # Create/update the result working tree439 # Create/update the result working tree
@@ -420,7 +441,7 @@
420 isinstance(target_transport, local.LocalTransport) and441 isinstance(target_transport, local.LocalTransport) and
421 (result_repo is None or result_repo.make_working_trees())):442 (result_repo is None or result_repo.make_working_trees())):
422 wt = result.create_workingtree(accelerator_tree=accelerator_tree,443 wt = result.create_workingtree(accelerator_tree=accelerator_tree,
423 hardlink=hardlink)444 hardlink=hardlink, from_branch=result_branch)
424 wt.lock_write()445 wt.lock_write()
425 try:446 try:
426 if wt.path2id('') is None:447 if wt.path2id('') is None:
427448
=== modified file 'bzrlib/remote.py'
--- bzrlib/remote.py 2010-12-02 10:41:05 +0000
+++ bzrlib/remote.py 2010-12-15 00:41:26 +0000
@@ -33,6 +33,7 @@
33 revision as _mod_revision,33 revision as _mod_revision,
34 static_tuple,34 static_tuple,
35 symbol_versioning,35 symbol_versioning,
36 urlutils,
36)37)
37from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult38from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat39from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
@@ -246,14 +247,17 @@
246 self._ensure_real()247 self._ensure_real()
247 self._real_bzrdir.destroy_repository()248 self._real_bzrdir.destroy_repository()
248249
249 def create_branch(self, name=None):250 def create_branch(self, name=None, repository=None):
250 # as per meta1 formats - just delegate to the format object which may251 # as per meta1 formats - just delegate to the format object which may
251 # be parameterised.252 # be parameterised.
252 real_branch = self._format.get_branch_format().initialize(self,253 real_branch = self._format.get_branch_format().initialize(self,
253 name=name)254 name=name, repository=repository)
254 if not isinstance(real_branch, RemoteBranch):255 if not isinstance(real_branch, RemoteBranch):
255 result = RemoteBranch(self, self.find_repository(), real_branch,256 if not isinstance(repository, RemoteRepository):
256 name=name)257 raise AssertionError(
258 'need a RemoteRepository to use with RemoteBranch, got %r'
259 % (repository,))
260 result = RemoteBranch(self, repository, real_branch, name=name)
257 else:261 else:
258 result = real_branch262 result = real_branch
259 # BzrDir.clone_on_transport() uses the result of create_branch but does263 # BzrDir.clone_on_transport() uses the result of create_branch but does
@@ -2093,7 +2097,7 @@
2093 name=name)2097 name=name)
2094 return result2098 return result
20952099
2096 def initialize(self, a_bzrdir, name=None):2100 def initialize(self, a_bzrdir, name=None, repository=None):
2097 # 1) get the network name to use.2101 # 1) get the network name to use.
2098 if self._custom_format:2102 if self._custom_format:
2099 network_name = self._custom_format.network_name()2103 network_name = self._custom_format.network_name()
@@ -2127,13 +2131,25 @@
2127 # Turn the response into a RemoteRepository object.2131 # Turn the response into a RemoteRepository object.
2128 format = RemoteBranchFormat(network_name=response[1])2132 format = RemoteBranchFormat(network_name=response[1])
2129 repo_format = response_tuple_to_repo_format(response[3:])2133 repo_format = response_tuple_to_repo_format(response[3:])
2130 if response[2] == '':2134 repo_path = response[2]
2131 repo_bzrdir = a_bzrdir2135 if repository is not None:
2136 remote_repo_url = urlutils.join(medium.base, repo_path)
2137 url_diff = urlutils.relative_url(repository.user_url,
2138 remote_repo_url)
2139 if url_diff != '.':
2140 raise AssertionError(
2141 'repository.user_url %r does not match URL from server '
2142 'response (%r + %r)'
2143 % (repository.user_url, medium.base, repo_path))
2144 remote_repo = repository
2132 else:2145 else:
2133 repo_bzrdir = RemoteBzrDir(2146 if repo_path == '':
2134 a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,2147 repo_bzrdir = a_bzrdir
2135 a_bzrdir._client)2148 else:
2136 remote_repo = RemoteRepository(repo_bzrdir, repo_format)2149 repo_bzrdir = RemoteBzrDir(
2150 a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2151 a_bzrdir._client)
2152 remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2137 remote_branch = RemoteBranch(a_bzrdir, remote_repo,2153 remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2138 format=format, setup_stacking=False, name=name)2154 format=format, setup_stacking=False, name=name)
2139 # XXX: We know this is a new branch, so it must have revno 0, revid2155 # XXX: We know this is a new branch, so it must have revno 0, revid
21402156
=== modified file 'bzrlib/tests/blackbox/test_branch.py'
--- bzrlib/tests/blackbox/test_branch.py 2010-12-02 10:41:05 +0000
+++ bzrlib/tests/blackbox/test_branch.py 2010-12-15 00:41:26 +0000
@@ -414,7 +414,7 @@
414 # being too low. If rpc_count increases, more network roundtrips have414 # being too low. If rpc_count increases, more network roundtrips have
415 # become necessary for this use case. Please do not adjust this number415 # become necessary for this use case. Please do not adjust this number
416 # upwards without agreement from bzr's network support maintainers.416 # upwards without agreement from bzr's network support maintainers.
417 self.assertLength(37, self.hpss_calls)417 self.assertLength(36, self.hpss_calls)
418418
419 def test_branch_from_trivial_branch_streaming_acceptance(self):419 def test_branch_from_trivial_branch_streaming_acceptance(self):
420 self.setup_smart_server_with_call_log()420 self.setup_smart_server_with_call_log()
421421
=== modified file 'bzrlib/tests/test_branch.py'
--- bzrlib/tests/test_branch.py 2010-12-07 09:06:39 +0000
+++ bzrlib/tests/test_branch.py 2010-12-15 00:41:26 +0000
@@ -113,7 +113,7 @@
113 """See BzrBranchFormat.get_format_string()."""113 """See BzrBranchFormat.get_format_string()."""
114 return "Sample branch format."114 return "Sample branch format."
115115
116 def initialize(self, a_bzrdir, name=None):116 def initialize(self, a_bzrdir, name=None, repository=None):
117 """Format 4 branches cannot be created."""117 """Format 4 branches cannot be created."""
118 t = a_bzrdir.get_branch_transport(self, name=name)118 t = a_bzrdir.get_branch_transport(self, name=name)
119 t.put_bytes('format', self.get_format_string())119 t.put_bytes('format', self.get_format_string())
120120
=== modified file 'bzrlib/tests/test_bzrdir.py'
--- bzrlib/tests/test_bzrdir.py 2010-10-15 14:21:03 +0000
+++ bzrlib/tests/test_bzrdir.py 2010-12-15 00:41:26 +0000
@@ -29,6 +29,7 @@
29 controldir,29 controldir,
30 errors,30 errors,
31 help_topics,31 help_topics,
32 lock,
32 repository,33 repository,
33 osutils,34 osutils,
34 remote,35 remote,
@@ -1349,6 +1350,12 @@
1349 def set_parent(self, parent):1350 def set_parent(self, parent):
1350 self._parent = parent1351 self._parent = parent
13511352
1353 def lock_read(self):
1354 return lock.LogicalLockResult(self.unlock)
1355
1356 def unlock(self):
1357 return
1358
13521359
1353class TestBzrDirSprout(TestCaseWithMemoryTransport):1360class TestBzrDirSprout(TestCaseWithMemoryTransport):
13541361
13551362
=== modified file 'bzrlib/tests/test_foreign.py'
--- bzrlib/tests/test_foreign.py 2010-08-02 18:36:31 +0000
+++ bzrlib/tests/test_foreign.py 2010-12-15 00:41:26 +0000
@@ -173,17 +173,19 @@
173 super(DummyForeignVcsBranchFormat, self).__init__()173 super(DummyForeignVcsBranchFormat, self).__init__()
174 self._matchingbzrdir = DummyForeignVcsDirFormat()174 self._matchingbzrdir = DummyForeignVcsDirFormat()
175175
176 def open(self, a_bzrdir, name=None, _found=False):176 def open(self, a_bzrdir, name=None, _found=False, found_repository=None):
177 if not _found:177 if not _found:
178 raise NotImplementedError178 raise NotImplementedError
179 try:179 try:
180 transport = a_bzrdir.get_branch_transport(None, name=name)180 transport = a_bzrdir.get_branch_transport(None, name=name)
181 control_files = lockable_files.LockableFiles(transport, 'lock',181 control_files = lockable_files.LockableFiles(transport, 'lock',
182 lockdir.LockDir)182 lockdir.LockDir)
183 if found_repository is None:
184 found_repository = a_bzrdir.find_repository()
183 return DummyForeignVcsBranch(_format=self,185 return DummyForeignVcsBranch(_format=self,
184 _control_files=control_files,186 _control_files=control_files,
185 a_bzrdir=a_bzrdir,187 a_bzrdir=a_bzrdir,
186 _repository=a_bzrdir.find_repository())188 _repository=found_repository)
187 except errors.NoSuchFile:189 except errors.NoSuchFile:
188 raise errors.NotBranchError(path=transport.base)190 raise errors.NotBranchError(path=transport.base)
189191
190192
=== modified file 'doc/en/release-notes/bzr-2.3.txt'
--- doc/en/release-notes/bzr-2.3.txt 2010-12-14 23:00:51 +0000
+++ doc/en/release-notes/bzr-2.3.txt 2010-12-15 00:41:26 +0000
@@ -31,6 +31,10 @@
31 missing inventories. This removes at least one network roundtrip when31 missing inventories. This removes at least one network roundtrip when
32 pushing to a stacked branch. (Andrew Bennetts)32 pushing to a stacked branch. (Andrew Bennetts)
3333
34* ``ControlDir.sprout`` no longer opens the target repository more than
35 once. This avoids some unnecessary IO, and removes a network roundtrip
36 when doing ``bzr branch`` to a smart server URL. (Andrew Bennetts)
37
34* ``bzr resolve`` now accepts ``--take-this`` and ``--take-other`` actions38* ``bzr resolve`` now accepts ``--take-this`` and ``--take-other`` actions
35 for text conflicts. This *replace* the whole file with the content39 for text conflicts. This *replace* the whole file with the content
36 designated by the action. This will *ignore* all differences that would40 designated by the action. This will *ignore* all differences that would
@@ -53,6 +57,14 @@
53.. Changes that may require updates in plugins or other code that uses57.. Changes that may require updates in plugins or other code that uses
54 bzrlib.58 bzrlib.
5559
60* ``Branch.sprout``, ``BranchFormat.initalize`` and
61 ``ControlDir.create_branch`` now take an optional ``repository`` keyword
62 argument, and ``BranchFormat.open`` now takes an optional
63 ``found_repository`` keyword argument. These provide the repository
64 object for new branch object to use (for cases when the caller has
65 already opened that repository). Implementations of these APIs will
66 need to be updated to accept these arguments. (Andrew Bennetts)
67
56Internals68Internals
57*********69*********
5870
5971
=== modified file 'doc/en/whats-new/whats-new-in-2.3.txt'
--- doc/en/whats-new/whats-new-in-2.3.txt 2010-12-14 09:33:53 +0000
+++ doc/en/whats-new/whats-new-in-2.3.txt 2010-12-15 00:41:26 +0000
@@ -69,8 +69,9 @@
69* ``bzr send`` uses less memory.69* ``bzr send`` uses less memory.
70 (John Arbash Meinel, #614576)70 (John Arbash Meinel, #614576)
7171
72* Fetches involving stacked branches and branches with tags are now faster.72* Fetches involving stacked branches and branches with tags now do slightly less
73 Some redundant network reads were removed. (Andrew Bennetts)73 I/O, and so does branching from an existing branch. This also improves the
74 network performance of these operations. (Andrew Bennetts)
7475
75* Inventory entries now consume less memory (on 32-bit Ubuntu file entries76* Inventory entries now consume less memory (on 32-bit Ubuntu file entries
76 have dropped from 68 bytes to 40, and directory entries from 120 bytes77 have dropped from 68 bytes to 40, and directory entries from 120 bytes