Merge lp:~jtv/launchpad/bug-439248 into lp:launchpad

Proposed by Jeroen T. Vermeulen
Status: Merged
Approved by: Eleanor Berger
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~jtv/launchpad/bug-439248
Merge into: lp:launchpad
Diff against target: 126 lines
3 files modified
lib/lp/code/tests/test_directbranchcommit.py (+1/-2)
lib/lp/translations/scripts/tests/test_translations_to_branch.py (+44/-0)
lib/lp/translations/scripts/translations_to_branch.py (+34/-1)
To merge this branch: bzr merge lp:~jtv/launchpad/bug-439248
Reviewer Review Type Date Requested Status
Eleanor Berger (community) Approve
Review via email: mp+12781@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :
Download full text (4.6 KiB)

= Bug 439248 =

The plot so far:

The translations-to-branch script produces daily snapshots of a product
series' translations and commits them to a selected bzr branch.

It does this using the DirectBranchCommit. This helper class uses a
"shortcut" similar to a lightweight checkout to write files straight to
a bzr branch without having to pull down its full history first.

Branches in Launchpad are now stacked by deafult

The bzr folks have found that committing directly to a stacked branch
failed to preserve invariants. This is bzr bug 375013. Their interim
solution was to prohibit commits straight to stacked branches.

Et voilĂ : now some translations-to-branch exports are failing because
bzrlib refuses to commit (there is a helpful reference to bug 375013).

This branch works around that by unstacking the branch when needed.
Thanks are due to Robert Collins for proffering this solution.

Semantically it changes nothing for the user; the branch is just stored
in a less efficient way, which is mostly our own problem. The advantage
of this approach is that it's a one-time step per branch. I've already
contacted some users suggesting the command-line equivalent of this fix,
and some of them already seem to have fixed their exports in this way.

== Design considerations ==

I considered and rejected two alternatives in favour of this one:

 * Reverting to a more conventional way of writing to the branch. This
   means having two alternative code paths for doing the same job, and
   trying to hide the complexity behind DirectBranchCommit's simple API.
   It also means letting a limitation of exports scalability exist and
   grow unchecked.

 * Requiring users to convert their own branches manually. I don't
   think that would be acceptable; it's bad enough that "registering a
   branch" is not enough to create one. That means there already is a
   highly technical step to setting up a translations branch. We're not
   in the business of selling expensive consultancy services for this
   sort of chore.

Another design choice was whether to apply the fix in DirectBranchCommit
or in the export script that uses DirectBranchCommit. Here I opted for
keeping the workaround in the export script; making a helper class too
intelligent can lead to surprises for people who work intimately with
branches and have detailed expectations. Unstacking a branch may come
as a surprise there. A chat with Aaron Bentley validated this choice.

== Implementation ==

The export-to-branch script class has a factory method to facilitate
testing: _makeDirectBranchCommit. I added a shim _prepareBranchCommit
between that and the calling code, that checks whether the branch it's
looking at is stacked. If so, it drops the committer it created,
converts the branch to unstacked, and creates a new committer.

Why does it create up to two committers? Because the first will open
the underlying bzr branch, which otherwise we'd have to do once extra to
check for stacking. This way is shorter and more efficient for the
common case.

== Tests ==
{{{
./bin/test -vv -t directbranchcommit -t translations.to.branch
}}}

== Demo and Q/A ==

Pick a productseries with trans...

Read more...

Revision history for this message
Eleanor Berger (intellectronica) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/tests/test_directbranchcommit.py'
2--- lib/lp/code/tests/test_directbranchcommit.py 2009-09-09 08:17:59 +0000
3+++ lib/lp/code/tests/test_directbranchcommit.py 2009-10-19 10:17:17 +0000
4@@ -23,8 +23,7 @@
5 self.useBzrBranches()
6
7 self.series = self.factory.makeProductSeries()
8- self.db_branch, self.tree = self.create_branch_and_tree(
9- db_branch=self.db_branch, hosted=True)
10+ self.db_branch, self.tree = self.create_branch_and_tree(hosted=True)
11
12 self.series.translations_branch = self.db_branch
13
14
15=== modified file 'lib/lp/translations/scripts/tests/test_translations_to_branch.py'
16--- lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-09-24 09:47:20 +0000
17+++ lib/lp/translations/scripts/tests/test_translations_to_branch.py 2009-10-19 10:17:17 +0000
18@@ -16,6 +16,9 @@
19
20 from lp.testing import map_branch_contents, TestCaseWithFactory
21
22+from lp.translations.scripts.translations_to_branch import (
23+ ExportTranslationsToBranch)
24+
25
26 class TestExportTranslationsToBranch(TestCaseWithFactory):
27
28@@ -120,5 +123,46 @@
29 None, re.search("INFO\s+Committed [0-9]+ file", stderr))
30
31
32+class TestExportToStackedBranch(TestCaseWithFactory):
33+ """Test workaround for bzr bug 375013."""
34+ # XXX JeroenVermeulen 2009-10-02 bug=375013: Once bug 375013 is
35+ # fixed, this entire test can go.
36+ layer = ZopelessAppServerLayer
37+
38+ def _setUpBranch(self, db_branch, tree, message):
39+ """Set the given branch and tree up for use."""
40+ bzr_branch = tree.branch
41+ last_revno, last_revision_id = bzr_branch.last_revision_info()
42+ removeSecurityProxy(db_branch).last_scanned_id = last_revision_id
43+
44+ def setUp(self):
45+ super(TestExportToStackedBranch, self).setUp()
46+ self.useBzrBranches()
47+
48+ base_branch, base_tree = self.create_branch_and_tree(
49+ 'base', name='base', hosted=True)
50+ self._setUpBranch(base_branch, base_tree, "Base branch.")
51+
52+ stacked_branch, stacked_tree = self.create_branch_and_tree(
53+ 'stacked', name='stacked', hosted=True)
54+ stacked_tree.branch.set_stacked_on_url('/' + base_branch.unique_name)
55+ stacked_branch.stacked_on = base_branch
56+ self._setUpBranch(stacked_branch, stacked_tree, "Stacked branch.")
57+
58+ self.stacked_branch = stacked_branch
59+
60+ def test_export_to_shared_branch(self):
61+ # The script knows how to deal with stacked branches.
62+ # Otherwise, this would fail.
63+ script = ExportTranslationsToBranch('reupload', test_args=['-q'])
64+ committer = script._prepareBranchCommit(self.stacked_branch)
65+ try:
66+ self.assertNotEqual(None, committer)
67+ committer.writeFile('x.txt', 'x')
68+ committer.commit("x!")
69+ finally:
70+ committer.unlock()
71+
72+
73 def test_suite():
74 return unittest.TestLoader().loadTestsFromName(__name__)
75
76=== modified file 'lib/lp/translations/scripts/translations_to_branch.py'
77--- lib/lp/translations/scripts/translations_to_branch.py 2009-09-24 08:26:10 +0000
78+++ lib/lp/translations/scripts/translations_to_branch.py 2009-10-19 10:17:17 +0000
79@@ -79,6 +79,39 @@
80 """
81 return DirectBranchCommit(db_branch)
82
83+ def _prepareBranchCommit(self, db_branch):
84+ """Prepare branch for use with `DirectBranchCommit`.
85+
86+ Create a `DirectBranchCommit` for `db_branch`. If `db_branch`
87+ is not in a format we can commit directly to, try to deal with
88+ that.
89+
90+ :param db_branch: A `Branch`.
91+ :return: `DirectBranchCommit`.
92+ """
93+ # XXX JeroenVermeulen 2009-09-30 bug=375013: It should become
94+ # possible again to commit to these branches at some point.
95+ # When that happens, remove this workaround and just call
96+ # _makeDirectBranchCommit directly.
97+ committer = self._makeDirectBranchCommit(db_branch)
98+ if not db_branch.stacked_on:
99+ # The normal case.
100+ return committer
101+
102+ self.logger.info("Unstacking branch to work around bug 375013.")
103+ try:
104+ committer.bzrbranch.set_stacked_on_url(None)
105+ finally:
106+ committer.unlock()
107+ self.logger.info("Done unstacking branch.")
108+
109+ # This may have taken a while, so commit for good
110+ # manners.
111+ if self.txn:
112+ self.txn.commit()
113+
114+ return self._makeDirectBranchCommit(db_branch)
115+
116 def _commit(self, source, committer):
117 """Commit changes to branch. Check for race conditions."""
118 self._checkForObjections(source)
119@@ -121,7 +154,7 @@
120 self.logger.info("Exporting %s." % source.title)
121 self._checkForObjections(source)
122
123- committer = self._makeDirectBranchCommit(source.translations_branch)
124+ committer = self._prepareBranchCommit(source.translations_branch)
125
126 bzr_branch = committer.bzrbranch
127