Merge lp:~nmb/bzr/893470-dedupe-list-branches into lp:bzr

Proposed by Neil Martinsen-Burrell
Status: Merged
Approved by: Vincent Ladeuil
Approved revision: no longer in the source branch.
Merged at revision: 6356
Proposed branch: lp:~nmb/bzr/893470-dedupe-list-branches
Merge into: lp:bzr
Diff against target: 150 lines (+49/-11)
8 files modified
bzrlib/bzrdir.py (+6/-8)
bzrlib/controldir.py (+6/-2)
bzrlib/tests/blackbox/test_branch.py (+1/-1)
bzrlib/tests/per_bzrdir/test_bzrdir.py (+12/-0)
bzrlib/tests/per_controldir/test_controldir.py (+5/-0)
bzrlib/tests/per_controldir_colo/test_supported.py (+8/-0)
bzrlib/tests/per_controldir_colo/test_unsupported.py (+6/-0)
doc/en/release-notes/bzr-2.5.txt (+5/-0)
To merge this branch: bzr merge lp:~nmb/bzr/893470-dedupe-list-branches
Reviewer Review Type Date Requested Status
Vincent Ladeuil Needs Fixing
Jelmer Vernooij (community) Approve
Review via email: mp+83075@code.launchpad.net

Description of the change

This checks to see if the current default branch is one of the colocated branches before returning it in BzrDir.list_branches. This avoids trivial duplication in the output of BzrDir.list_branches().

To post a comment you must log in.
Revision history for this message
Jelmer Vernooij (jelmer) wrote :

Hi Neil,

If you care about duplicates in BzrDir.list_branches(), I think we should just fix that API.

This fix eliminates duplicates - but only for the default branch. We should be consistent and remove dupes for the other branches as well, if we're doing that.

An alternative fix for this (rather than checking afterwards) might be to just have a way to exclude all branch references.

Revision history for this message
Jelmer Vernooij (jelmer) :
review: Needs Fixing
Revision history for this message
Martin Pool (mbp) wrote :

based on jelmer's comments i'm going to bump this out of the review queue, but if you want to talk about it more, please do.

Revision history for this message
Neil Martinsen-Burrell (nmb) wrote :

I would like some guidance on how to put this in a state that could fix bug 893470. In a version of bzr-colo designed to work with the colocated functionality, I always see things like

nmb@garble[~/tmp/test]$ bzr branches
 another
 trunk
 new
 another

Should we be unique-ifying the results of list_branches at any point or should callers have to manage that? Should we be excluding all branch references? I can do either of those, but I'd rather not do anything without knowing that it has a chance of being accepted. The attached patch works for the situation of a colocated workspace with a working tree.

My philosophical belief is that we should only show "unique" branches in some sense when we do list_branches. I think that this should mean that references to other branches already in the list should not be listed again. I don't know enough about BranchReferences to understand if they can refer to branches under a different BzrDir, but if they can, then I think that such branch references should be listed by list_branches.

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

Am 07/12/11 06:31, schrieb Neil Martinsen-Burrell:
> I would like some guidance on how to put this in a state that could fix bug 893470. In a version of bzr-colo designed to work with the colocated functionality, I always see things like
>
> nmb@garble[~/tmp/test]$ bzr branches
> another
> trunk
> new
> another
>
> Should we be unique-ifying the results of list_branches at any point or should callers have to manage that? Should we be excluding all branch references? I can do either of those, but I'd rather not do anything without knowing that it has a chance of being accepted. The attached patch works for the situation of a colocated workspace with a working tree.
>
> My philosophical belief is that we should only show "unique" branches in some sense when we do list_branches. I think that this should mean that references to other branches already in the list should not be listed again. I don't know enough about BranchReferences to understand if they can refer to branches under a different BzrDir, but if they can, then I think that such branch references should be listed by list_branches.
There can be branch references to branches outside of the colocated
directory in there as well. I don't think we should be excluding branch
references, as you do want to be able to access them in some situations
- excluding them means there is no way to get at them unless you know
their name.

BzrDir.list_branches() has been available in 2.4 as well, and has always
included branch references so I think we should keep it like that and
instead perhaps add a new method. The most sensible thing (I think)
would be to add a call that returns a dictionary, e.g.:

BzrDir.get_branches() -> { None: Branch("/path/to/some/branch"),
"trunk": Branch("/path/to/some/branch"), "branchb":
Branch("/path/to/some/other/branch"), "bzr.dev":
Branch("http://bazaar-vcs.org/bzr/bzr.dev") }

What do you think?

Cheers,

Jelmer

Revision history for this message
Neil Martinsen-Burrell (nmb) wrote :

I agree that we should add a new method and not change the semantics of list_branches. I think a dictionary is a natural object to return. How would the dictionary deal with branch references? That seems like it would give multiple branch objects with the same key. Should the items in the dictionary be lists when this occurs? Should they all be lists, or just those items with multiple objects with that name? For example, what should be the result for the following test:

def test_get_branches(self):
        # Create a branch branch-1 that initially is a checkout of 'foo'
        # Use switch to create 'anotherbranch' which derives from that
        repo = self.make_repository('branch-1', format='development-colo')
        target_branch = repo.bzrdir.create_branch(name='foo')
        branch.BranchReferenceFormat().initialize(
            repo.bzrdir, target_branch=target_branch)
        tree = repo.bzrdir.create_workingtree()

repo.bzrdir.get_branches() -> {'foo':???}

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

Am 08/12/11 07:02, schrieb Neil Martinsen-Burrell:
> I agree that we should add a new method and not change the semantics of list_branches. I think a dictionary is a natural object to return. How would the dictionary deal with branch references? That seems like it would give multiple branch objects with the same key. Should the items in the dictionary be lists when this occurs? Should they all be lists, or just those items with multiple objects with that name? For example, what should be the result for the following test:
>
> def test_get_branches(self):
> # Create a branch branch-1 that initially is a checkout of 'foo'
> # Use switch to create 'anotherbranch' which derives from that
> repo = self.make_repository('branch-1', format='development-colo')
> target_branch = repo.bzrdir.create_branch(name='foo')
> branch.BranchReferenceFormat().initialize(
> repo.bzrdir, target_branch=target_branch)
> tree = repo.bzrdir.create_workingtree()
>
> repo.bzrdir.get_branches() -> {'foo':???}
{'foo': Branch("/path/to/bzrdir,branch=foo"), None:
Branch("/path/to/bzrdir,branch=foo")}

IOW, I think we should just open the branch as .open_branch() would.

Cheers,

Jelmer

Revision history for this message
Neil Martinsen-Burrell (nmb) wrote :

I have now added a get_branches method to ControlDir and BzrDir, along with tests and a mention in the release notes. I have NOT started using this method in the "bzr branches" command since we currently don't create checkouts when creating development-colo workspaces. When we do, we will have duplicate entries in "bzr branches" and we should migrate it over to using get_branches instead of list_branches.

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

Thanks, that looks reasonable.

It seems simpler to just use .open_branch() in ControlDir.get_branches() rather than list_branches(), which doesn't necessarily return the branch with the name None as first element.

I wonder if we should deprecate list_branches now that we have get_branches? Is there any reason to still use list_branches?

75 + def test_get_branches(self):
76 + repo = self.make_repository('branch-1')
77 + target_branch = repo.bzrdir.create_branch(name='foo')
78 + reference = branch.BranchReferenceFormat().initialize(
79 + repo.bzrdir, target_branch=target_branch)
^^ These tests are not bzr-specific, they can be run against e.g. svn as well, where BranchReferenceFormat won't work. To test references, we should instead add (another) test in bzrlib/tests/per_bzrdir.

Cheers,

Jelmer

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

We also need a test in bzrlib/tests/per_controldir_colo/test_unsupported.py to make sure that formats without colocated branch support work properly.

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

Perhaps for now we shouldn't deprecate .list_branches() but rather just provide a simple implementation of it tthat just returns self.get_branches().values().

Revision history for this message
Neil Martinsen-Burrell (nmb) wrote :

I put the BranchReference test in per_bzrdir/bzrdir.py, leaving a test with a single named branch in per_controldir_colo/test_supported.py. I added a test to per_controldir_colo/test_unsupported.py. I reimplmented controldir.list_branches as get_branches().values() and fixed the tests that depended on a particular order of output from "bzr branches".

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

Looks good to me.

One minor nit (can be fixed while landing):

In get_branches(), there is some indentation that is only 3 spaces (should be four), and there should be a space after "None:".

Revision history for this message
Jelmer Vernooij (jelmer) :
review: Approve
Revision history for this message
Vincent Ladeuil (vila) wrote :

146 + branches themselves. Branches without names use the key None.
147 + (Neil Martinsen-Burrell)

[tweak]
Only the default branch has no name right ? The plural form above is misleading then and mentioning that here and in the doc string will make it clear.

[tweak]
88 + self.assertEqual(set(repo.bzrdir.get_branches().keys()),
89 + set([None, 'foo']))

101 + self.assertEqual(repo.bzrdir.get_branches().keys(), [None])

118 + self.assertEqual(repo.bzrdir.get_branches().keys(),
119 + ['foo'])
120 + self.assertEqual(repo.bzrdir.get_branches()['foo'].base,
121 + target_branch.base)

134 + self.assertEqual(made_control.get_branches().keys(),
135 + [None])

The assertXX(expected, actual) is the preferred idiom. Being coherent here helps when test fail as you don't need to look at the test source code to know which is which especially when a diff between the expected and actual values is displayed. Too bad, it's not documented as such in doc/developers/testing.txt :-/

Both of this tweaks can be done while landing.

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/bzrdir.py'
--- bzrlib/bzrdir.py 2011-12-11 03:44:06 +0000
+++ bzrlib/bzrdir.py 2011-12-11 04:20:30 +0000
@@ -1054,18 +1054,16 @@
1054 self.control_files.unlock()1054 self.control_files.unlock()
1055 self.transport.delete_tree(path)1055 self.transport.delete_tree(path)
10561056
1057 def list_branches(self):1057 def get_branches(self):
1058 """See ControlDir.list_branches."""1058 """See ControlDir.get_branches."""
1059 ret = []1059 ret = {}
1060 # Default branch
1061 try:1060 try:
1062 ret.append(self.open_branch())1061 ret[None] = self.open_branch()
1063 except (errors.NotBranchError, errors.NoRepositoryPresent):1062 except (errors.NotBranchError, errors.NoRepositoryPresent):
1064 pass1063 pass
10651064
1066 # colocated branches1065 for name in self._read_branch_list():
1067 ret.extend([self.open_branch(name.decode("utf-8")) for name in1066 ret[name] = self.open_branch(name.decode('utf-8'))
1068 self._read_branch_list()])
10691067
1070 return ret1068 return ret
10711069
10721070
=== modified file 'bzrlib/controldir.py'
--- bzrlib/controldir.py 2011-12-07 14:49:51 +0000
+++ bzrlib/controldir.py 2011-12-11 04:20:30 +0000
@@ -106,10 +106,14 @@
106 """Return a sequence of all branches local to this control directory.106 """Return a sequence of all branches local to this control directory.
107107
108 """108 """
109 return self.get_branches().values()
110
111 def get_branches(self):
112 """Return a dictionary with branch_names and branch objects."""
109 try:113 try:
110 return [self.open_branch()]114 return {None:self.open_branch()}
111 except (errors.NotBranchError, errors.NoRepositoryPresent):115 except (errors.NotBranchError, errors.NoRepositoryPresent):
112 return []116 return {}
113117
114 def is_control_filename(self, filename):118 def is_control_filename(self, filename):
115 """True if filename is the name of a path which is reserved for119 """True if filename is the name of a path which is reserved for
116120
=== modified file 'bzrlib/tests/blackbox/test_branch.py'
--- bzrlib/tests/blackbox/test_branch.py 2011-11-17 19:50:38 +0000
+++ bzrlib/tests/blackbox/test_branch.py 2011-12-11 04:20:30 +0000
@@ -78,7 +78,7 @@
78 self.assertEqual('', out)78 self.assertEqual('', out)
79 self.assertEqual('Branched 2 revisions.\n', err)79 self.assertEqual('Branched 2 revisions.\n', err)
80 out, err = self.run_bzr('branches b')80 out, err = self.run_bzr('branches b')
81 self.assertEqual(" orig\n thiswasa\n", out)81 self.assertEqual(" thiswasa\n orig\n", out)
82 self.assertEqual('', err)82 self.assertEqual('', err)
83 out,err = self.run_bzr('branch a file:b,branch=orig', retcode=3)83 out,err = self.run_bzr('branch a file:b,branch=orig', retcode=3)
84 self.assertEqual('', out)84 self.assertEqual('', out)
8585
=== modified file 'bzrlib/tests/per_bzrdir/test_bzrdir.py'
--- bzrlib/tests/per_bzrdir/test_bzrdir.py 2011-11-21 18:59:51 +0000
+++ bzrlib/tests/per_bzrdir/test_bzrdir.py 2011-12-11 04:20:30 +0000
@@ -21,6 +21,7 @@
2121
22import bzrlib.branch22import bzrlib.branch
23from bzrlib import (23from bzrlib import (
24 branch,
24 bzrdir,25 bzrdir,
25 errors,26 errors,
26 repository,27 repository,
@@ -683,3 +684,14 @@
683 self.assertEquals(684 self.assertEquals(
684 branch.bzrdir.user_transport.list_dir("."),685 branch.bzrdir.user_transport.list_dir("."),
685 [".bzr"])686 [".bzr"])
687
688 def test_get_branches(self):
689 repo = self.make_repository('branch-1')
690 try:
691 target_branch = repo.bzrdir.create_branch(name='foo')
692 except errors.NoColocatedBranchSupport:
693 return
694 reference = branch.BranchReferenceFormat().initialize(
695 repo.bzrdir, target_branch=target_branch)
696 self.assertEqual(set(repo.bzrdir.get_branches().keys()),
697 set([None, 'foo']))
686698
=== modified file 'bzrlib/tests/per_controldir/test_controldir.py'
--- bzrlib/tests/per_controldir/test_controldir.py 2011-11-17 10:59:40 +0000
+++ bzrlib/tests/per_controldir/test_controldir.py 2011-12-11 04:20:30 +0000
@@ -1192,6 +1192,11 @@
1192 else:1192 else:
1193 self.assertEquals([], made_control.list_branches())1193 self.assertEquals([], made_control.list_branches())
11941194
1195 def test_get_branches(self):
1196 repo = self.make_repository('branch-1')
1197 target_branch = repo.bzrdir.create_branch()
1198 self.assertEqual(repo.bzrdir.get_branches().keys(), [None])
1199
1195 def test_create_repository(self):1200 def test_create_repository(self):
1196 # a bzrdir can construct a repository for itself.1201 # a bzrdir can construct a repository for itself.
1197 if not self.bzrdir_format.is_initializable():1202 if not self.bzrdir_format.is_initializable():
11981203
=== modified file 'bzrlib/tests/per_controldir_colo/test_supported.py'
--- bzrlib/tests/per_controldir_colo/test_supported.py 2011-11-17 18:39:26 +0000
+++ bzrlib/tests/per_controldir_colo/test_supported.py 2011-12-11 04:20:30 +0000
@@ -127,3 +127,11 @@
127 made_branch = Branch.open(made_branch.user_url)127 made_branch = Branch.open(made_branch.user_url)
128 self.assertEquals(u"col\xe9", made_branch.name)128 self.assertEquals(u"col\xe9", made_branch.name)
129 made_control.destroy_branch(u"col\xe9")129 made_control.destroy_branch(u"col\xe9")
130
131 def test_get_branches(self):
132 repo = self.make_repository('branch-1')
133 target_branch = repo.bzrdir.create_branch(name='foo')
134 self.assertEqual(repo.bzrdir.get_branches().keys(),
135 ['foo'])
136 self.assertEqual(repo.bzrdir.get_branches()['foo'].base,
137 target_branch.base)
130138
=== modified file 'bzrlib/tests/per_controldir_colo/test_unsupported.py'
--- bzrlib/tests/per_controldir_colo/test_unsupported.py 2011-09-23 12:32:16 +0000
+++ bzrlib/tests/per_controldir_colo/test_unsupported.py 2011-12-11 04:20:30 +0000
@@ -68,3 +68,9 @@
68 made_control = self.make_bzrdir_with_repo()68 made_control = self.make_bzrdir_with_repo()
69 self.assertRaises(errors.NoColocatedBranchSupport,69 self.assertRaises(errors.NoColocatedBranchSupport,
70 made_control.get_branch_reference, "colo")70 made_control.get_branch_reference, "colo")
71
72 def test_get_branches(self):
73 made_control = self.make_bzrdir_with_repo()
74 made_control.create_branch()
75 self.assertEqual(made_control.get_branches().keys(),
76 [None])
7177
=== modified file 'doc/en/release-notes/bzr-2.5.txt'
--- doc/en/release-notes/bzr-2.5.txt 2011-12-09 12:04:25 +0000
+++ doc/en/release-notes/bzr-2.5.txt 2011-12-11 04:20:30 +0000
@@ -240,6 +240,11 @@
240* The registry of merge types has been moved to ``merge`` from ``option`` but240* The registry of merge types has been moved to ``merge`` from ``option`` but
241 ``merge.get_merge_type_registry`` remains as an accessor. (Martin Packman)241 ``merge.get_merge_type_registry`` remains as an accessor. (Martin Packman)
242242
243* ControlDir now has a get_branches method that returns a dictionary
244 whose keys are the names of the branches and whose values are the
245 branches themselves. Branches without names use the key None.
246 (Neil Martinsen-Burrell)
247
243Testing248Testing
244*******249*******
245250