Merge lp:~spiv/bzr-builder/merge-subdirs-479705 into lp:bzr-builder

Proposed by Andrew Bennetts
Status: Merged
Merged at revision: 99
Proposed branch: lp:~spiv/bzr-builder/merge-subdirs-479705
Merge into: lp:bzr-builder
Diff against target: 471 lines (+254/-32)
3 files modified
__init__.py (+23/-1)
recipe.py (+109/-4)
tests/test_recipe.py (+122/-27)
To merge this branch: bzr merge lp:~spiv/bzr-builder/merge-subdirs-479705
Reviewer Review Type Date Requested Status
James Westby Needs Fixing
Review via email: mp+31251@code.launchpad.net

Commit message

Add 'nest-part' instruction to recipes.

Description of the change

See discussion so far at <https://code.edge.launchpad.net/~spiv/bzr-builder/merge-subdirs-479705/+merge/14979>. [Making a new submission because lp:bzr-builder has changed identity since the original proposal.]

In summary: this adds a "nest-part" instruction built on the MergeIntoMerger that is being added to bzr 2.2. This will support the use case of merging just "debian/" into an upstream branch even when the packaging branch is a parallel import vs. upstream.

To post a comment you must log in.
71. By Andrew Bennetts

Merge trunk.

Revision history for this message
James Westby (james-w) wrote :

Hi,

The launchpad team have no plans to provide a newer bzr in older
releases, so I would like to proceed by copying the code in to bzr-builder
where needed.

As for the changes proposed here, they are largely fine.

nest-part packaging lp:~foo-dev/foo/packaging -1 debian

I would like to move the revision spec to the end of the line and make it optional.

I don't mind too much whether it or the target subdir comes first.

134 + op = OperationWithCleanups(nest_part_branch)

I don't think we can rely on OperationWithCleanups as it is fairly new,
so either we copy that too, or we do it the old fashioned way.

Thanks for your work on this. Let me know if you would like me to
make these changes.

Thanks,

James

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

What do you mean by "no plans to provide a new bzr in old releases"? Is the
ppa not enough? Should we push to get it into backports or something?

- Martin

On 30/07/2010 7:56 AM, "James Westby"
<<email address hidden><jw%<email address hidden>>>
wrote:

Review: Needs Fixing
Hi,

The launchpad team have no plans to provide a newer bzr in older
releases, so I would like to proceed by copying the code in to bzr-builder
where needed.

As for the changes proposed here, they are largely fine.

nest-part packaging lp:~foo-dev/foo/packaging -1 debian

I would like to move the revision spec to the end of the line and make it
optional.

I don't mind too much whether it or the target subdir comes first.

134 + op = OperationWithCleanups(nest_part_branch)

I don't think we can rely on OperationWithCleanups as it is fairly new,
so either we copy that too, or we do it the old fashioned way.

Thanks for your work on this. Let me know if you would like me to
make these changes.

Thanks,

James

--
https://code.launchpad.net/~spiv/bzr-builder/merge-subdirs-479705/+merge/31251
Your team canoni...

Revision history for this message
James Westby (james-w) wrote :

On Thu, 29 Jul 2010 22:16:41 -0000, Martin Pool <email address hidden> wrote:
> What do you mean by "no plans to provide a new bzr in old releases"? Is the
> ppa not enough? Should we push to get it into backports or something?

https://bugs.edge.launchpad.net/launchpad-code/+bug/603615

They don't plan to put the work in to providing an older bzr to the
builders when doing recipe builds.

Thanks,

James

Revision history for this message
Martin Pool (mbp) wrote :

But they are going to provide a backported bzr-builder (or to run it from a checkout?) That seems a bit strange, but perhaps it's most pragmatic approach.

Revision history for this message
James Westby (james-w) wrote :

On Thu, 05 Aug 2010 01:28:43 -0000, Martin Pool <email address hidden> wrote:
> But they are going to provide a backported bzr-builder (or to run it
> from a checkout?) That seems a bit strange, but perhaps it's most
> pragmatic approach.

They are. However, the other day Aaron was asking about running
bzr-builder outside the chroot so that only one version was used. That
would also enable us to use a newer bzr without backporting. I don't
know if he decided to do this or not.

Thanks,

James

Revision history for this message
Aaron Bentley (abentley) wrote :

We are planning to run bzr-builder outside the chroot. That should reduce the risk of new bzr's contaminating daily builds for old releases, reduce the amount of maintenance required when new releases of bzr-builder become available, and allow us to build from 2a branches into ancient, but still supported, distros like Dapper.

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

> Thanks for your work on this. Let me know if you would like me to
> make these changes.

The backport of 2.2's MergeIntoMerger and removal of OperationWithCleanups can be found in <https://code.edge.launchpad.net/~spiv/bzr-builder/backport-merge-into/+merge/32428>.

I'm working on the requested syntax change now.

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

The syntax change is now done. Please review.

72. By Andrew Bennetts

Change the syntax of nest-part to have the revspec at the end, and make it optional.

73. By Andrew Bennetts

Tweak plugin help text.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '__init__.py'
2--- __init__.py 2010-07-09 19:23:36 +0000
3+++ __init__.py 2010-08-12 13:41:15 +0000
4@@ -21,7 +21,7 @@
5
6 A recipe is just a text file that starts with a line such as::
7
8- # bzr-builder format 0.2 deb-version 1.0+{revno}-{revno:packaging}
9+ # bzr-builder format 0.3 deb-version 1.0+{revno}-{revno:packaging}
10
11 The format specifier is there to allow the syntax to be changed in later
12 versions, and the meaning of "deb-version" will be explained later.
13@@ -70,6 +70,20 @@
14 for the revisionspec is identical to that taken by the "--revision" argument
15 to many bzr commands. See "bzr help revisionspec" for details.
16
17+You can also merge specific subdirectories from a branch with a "nest-part"
18+line like
19+
20+nest-part packaging lp:~foo-dev/foo/packaging debian
21+
22+which specifies that the only the debian/ subdirectory should be merged. This
23+works even if the branches share no revision history. You can optionally
24+specify the revision and subdirectory in the target with a line like
25+
26+nest-part libfoo lp:libfoo src lib/foo tag:release-1.2
27+
28+will put the "src" directory of libfoo in "lib/foo", using the revision of
29+libfoo tagged "release-1.2"
30+
31 It is also possible to run an arbitrary command at a particular point in the
32 construction process. For example::
33
34@@ -114,10 +128,18 @@
35 tree is built the top of debian/changelog has a version number of
36 "1.0-1" then this would evaluate to "1.0".
37
38+Instruction syntax summary:
39+
40+ * nest NAME BRANCH TARGET-DIR [REVISION]
41+ * merge NAME BRANCH [REVISION]
42+ * nest-part NAME BRANCH SUBDIR [TARGET-DIR [REVISION]]
43+ * run COMMAND
44+
45 Format versions:
46
47 0.1 - original format.
48 0.2 - added "run" instruction.
49+ 0.3 - added "nest-part" instruction.
50 """
51
52 if __name__ == '__main__':
53
54=== modified file 'recipe.py'
55--- recipe.py 2010-07-09 20:51:37 +0000
56+++ recipe.py 2010-08-12 13:41:15 +0000
57@@ -22,6 +22,7 @@
58 bzrdir,
59 errors,
60 merge,
61+ revision,
62 revisionspec,
63 tag,
64 trace,
65@@ -41,6 +42,7 @@
66
67
68 MERGE_INSTRUCTION = "merge"
69+NEST_PART_INSTRUCTION = "nest-part"
70 NEST_INSTRUCTION = "nest"
71 RUN_INSTRUCTION = "run"
72
73@@ -223,6 +225,47 @@
74 child_branch.branch.unlock()
75
76
77+def nest_part_branch(op, child_branch, tree_to, br_to, subpath,
78+ target_subdir=None):
79+ """Merge the branch subdirectory specified by child_branch.
80+
81+ :param child_branch: the RecipeBranch to retrieve the branch and revision to
82+ merge from.
83+ :param tree_to: the WorkingTree to merge in to.
84+ :param br_to: the Branch to merge in to.
85+ :param subpath: only merge files from branch that are from this path.
86+ e.g. subpath='/debian' will only merge changes from that directory.
87+ :param target_subdir: (optional) directory in target to merge that
88+ subpath into. Defaults to basename of subpath.
89+ """
90+ merge_from = branch.Branch.open(child_branch.url)
91+ merge_from.lock_read()
92+ op.add_cleanup(merge_from.unlock)
93+ if child_branch.revspec is not None:
94+ merge_revspec = revisionspec.RevisionSpec.from_string(
95+ child_branch.revspec)
96+ merge_revid = merge_revspec.as_revision_id(merge_from)
97+ else:
98+ merge_revid = merge_from.last_revision()
99+ child_branch.revid = merge_revid
100+ other_tree = merge_from.basis_tree()
101+ other_tree.lock_read()
102+ op.add_cleanup(other_tree.unlock)
103+ if target_subdir is None:
104+ target_subdir = os.path.basename(subpath)
105+ merger = merge.MergeIntoMerger(this_tree=tree_to, other_tree=other_tree,
106+ other_branch=merge_from, target_subdir=target_subdir,
107+ source_subpath=subpath, other_rev_id=merge_revid)
108+ merger.set_base_revision(revision.NULL_REVISION, merge_from)
109+ conflict_count = merger.do_merge()
110+ merger.set_pending()
111+ if conflict_count:
112+ # FIXME: better reporting
113+ raise errors.BzrCommandError("Conflicts from merge")
114+ tree_to.commit("Merge %s of %s" %
115+ (subpath, urlutils.unescape_for_display(child_branch.url, 'utf-8')))
116+
117+
118 def update_branch(base_branch, tree_to, br_to, to_transport,
119 possible_transports=None):
120 if base_branch.branch is None:
121@@ -422,6 +465,20 @@
122 possible_transports=possible_transports)
123
124
125+class NestPartInstruction(ChildBranch):
126+
127+ def __init__(self, recipe_branch, subpath, target_subdir):
128+ ChildBranch.__init__(self, recipe_branch)
129+ self.subpath = subpath
130+ self.target_subdir = target_subdir
131+
132+ def apply(self, target_path, tree_to, br_to):
133+ from bzrlib.cleanup import OperationWithCleanups
134+ op = OperationWithCleanups(nest_part_branch)
135+ op.run(self.recipe_branch, tree_to, br_to, self.subpath,
136+ self.target_subdir)
137+
138+
139 class NestInstruction(ChildBranch):
140
141 def apply(self, target_path, tree_to, br_to, possible_transports=None):
142@@ -440,6 +497,9 @@
143 The child_branches attribute is a list of tuples of ChildBranch objects.
144 The revid attribute records the revid that the url and revspec resolved
145 to when the RecipeBranch was built, or None if it has not been built.
146+
147+ :ivar revid: after this recipe branch has been built this is set to the
148+ revision ID that was merged/nested from the branch at self.url.
149 """
150
151 def __init__(self, name, url, revspec=None):
152@@ -464,6 +524,18 @@
153 """
154 self.child_branches.append(MergeInstruction(branch))
155
156+ def nest_part_branch(self, branch, subpath=None, target_subdir=None):
157+ """Merge subdir of a child branch into this one.
158+
159+ :param branch: the RecipeBranch to merge.
160+ :param subpath: only merge files from branch that are from this path.
161+ e.g. subpath='/debian' will only merge changes from that directory.
162+ :param target_subdir: (optional) directory in target to merge that
163+ subpath into. Defaults to basename of subpath.
164+ """
165+ self.child_branches.append(
166+ NestPartInstruction(branch, subpath, target_subdir))
167+
168 def nest_branch(self, location, branch):
169 """Nest a child branch in to this one.
170
171@@ -663,7 +735,7 @@
172 eol_char = "\n"
173 digit_chars = ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
174
175- NEWEST_VERSION = 0.2
176+ NEWEST_VERSION = 0.3
177
178 def __init__(self, f, filename=None):
179 """Create a RecipeParser.
180@@ -741,13 +813,25 @@
181 url = self.parse_branch_url()
182 if instruction == NEST_INSTRUCTION:
183 location = self.parse_branch_location()
184- revspec = self.parse_optional_revspec()
185+ if instruction == NEST_PART_INSTRUCTION:
186+ path = self.parse_subpath()
187+ target_subdir = self.parse_optional_path()
188+ if target_subdir == '':
189+ target_subdir = None
190+ revspec = None
191+ else:
192+ revspec = self.parse_optional_revspec()
193+ else:
194+ revspec = self.parse_optional_revspec()
195 self.new_line()
196 last_branch = RecipeBranch(branch_id, url, revspec=revspec)
197 if instruction == NEST_INSTRUCTION:
198 active_branches[-1].nest_branch(location, last_branch)
199- else:
200+ elif instruction == MERGE_INSTRUCTION:
201 active_branches[-1].merge_branch(last_branch)
202+ elif instruction == NEST_PART_INSTRUCTION:
203+ active_branches[-1].nest_part_branch(
204+ last_branch, path, target_subdir)
205 last_instruction = instruction
206 if len(active_branches) == 0:
207 self.throw_parse_error("Empty recipe")
208@@ -771,9 +855,13 @@
209 if self.version < 0.2:
210 options = (MERGE_INSTRUCTION, NEST_INSTRUCTION)
211 options_str = "'%s' or '%s'" % options
212- else:
213+ elif self.version < 0.3:
214 options = (MERGE_INSTRUCTION, NEST_INSTRUCTION, RUN_INSTRUCTION)
215 options_str = "'%s', '%s' or '%s'" % options
216+ else:
217+ options = (MERGE_INSTRUCTION, NEST_INSTRUCTION,
218+ NEST_PART_INSTRUCTION, RUN_INSTRUCTION)
219+ options_str = "'%s', '%s', '%s' or '%s'" % options
220 instruction = self.peek_to_whitespace()
221 if instruction is None:
222 self.throw_parse_error("End of line while looking for %s"
223@@ -830,6 +918,16 @@
224 self.seen_paths[norm_location] = self.line_index + 1
225 return location
226
227+ def parse_subpath(self):
228+ self.parse_whitespace("the subpath to merge")
229+ location = self.take_to_whitespace("the subpath to merge")
230+ return location
231+
232+ def parse_revspec(self):
233+ self.parse_whitespace("the revspec")
234+ revspec = self.take_to_whitespace("the revspec")
235+ return revspec
236+
237 def parse_optional_revspec(self):
238 self.parse_whitespace(None, require=False)
239 revspec = self.peek_to_whitespace()
240@@ -837,6 +935,13 @@
241 self.take_chars(len(revspec))
242 return revspec
243
244+ def parse_optional_path(self):
245+ self.parse_whitespace(None, require=False)
246+ path = self.peek_to_whitespace()
247+ if path is not None:
248+ self.take_chars(len(path))
249+ return path
250+
251 def throw_parse_error(self, problem, cls=None, **kwargs):
252 if cls is None:
253 cls = RecipeParseError
254
255=== modified file 'tests/test_recipe.py'
256--- tests/test_recipe.py 2010-07-09 20:51:37 +0000
257+++ tests/test_recipe.py 2010-08-12 13:41:15 +0000
258@@ -42,7 +42,7 @@
259 class RecipeParserTests(TestCaseInTempDir):
260
261 deb_version = "0.1-{revno}"
262- basic_header = ("# bzr-builder format 0.2 deb-version "
263+ basic_header = ("# bzr-builder format 0.3 deb-version "
264 + deb_version +"\n")
265 basic_branch = "http://foo.org/"
266 basic_header_and_branch = basic_header + basic_branch + "\n"
267@@ -133,8 +133,8 @@
268 self.basic_header + "http://foo.org/ 2 foo")
269
270 def tests_rejects_unknown_instruction(self):
271- self.assertParseError(3, 1, "Expecting 'merge', 'nest' or 'run', "
272- "got 'cat'", self.get_recipe,
273+ self.assertParseError(3, 1, "Expecting 'merge', 'nest', 'nest-part' "
274+ "or 'run', got 'cat'", self.get_recipe,
275 self.basic_header + "http://foo.org/\n" + "cat")
276
277 def test_rejects_merge_no_name(self):
278@@ -152,6 +152,21 @@
279 "got 'bar'", self.get_recipe,
280 self.basic_header_and_branch + "merge foo url 2 bar")
281
282+ def test_rejects_nest_part_no_name(self):
283+ self.assertParseError(3, 11, "End of line while looking for "
284+ "the branch id", self.get_recipe,
285+ self.basic_header_and_branch + "nest-part ")
286+
287+ def test_rejects_nest_part_no_url(self):
288+ self.assertParseError(3, 15, "End of line while looking for "
289+ "the branch url", self.get_recipe,
290+ self.basic_header_and_branch + "nest-part foo ")
291+
292+ def test_rejects_nest_part_no_subpath(self):
293+ self.assertParseError(3, 22, "End of line while looking for "
294+ "the subpath to merge", self.get_recipe,
295+ self.basic_header_and_branch + "nest-part foo url:// ")
296+
297 def test_rejects_nest_no_name(self):
298 self.assertParseError(3, 6, "End of line while looking for "
299 "the branch id", self.get_recipe,
300@@ -229,6 +244,42 @@
301 self.assertEqual(None, location)
302 self.check_recipe_branch(child_branch, "bar", "http://bar.org")
303
304+ def test_builds_recipe_with_nest_part(self):
305+ base_branch = self.get_recipe(self.basic_header_and_branch
306+ + "nest-part bar http://bar.org some/path")
307+ self.check_base_recipe_branch(base_branch, "http://foo.org/",
308+ num_child_branches=1)
309+ instruction = base_branch.child_branches[0]
310+ self.assertEqual(None, instruction.nest_path)
311+ self.check_recipe_branch(
312+ instruction.recipe_branch, "bar", "http://bar.org")
313+ self.assertEqual("some/path", instruction.subpath)
314+ self.assertEqual(None, instruction.target_subdir)
315+
316+ def test_builds_recipe_with_nest_part_subdir(self):
317+ base_branch = self.get_recipe(self.basic_header_and_branch
318+ + "nest-part bar http://bar.org some/path target-subdir")
319+ self.check_base_recipe_branch(base_branch, "http://foo.org/",
320+ num_child_branches=1)
321+ instruction = base_branch.child_branches[0]
322+ self.assertEqual(None, instruction.nest_path)
323+ self.check_recipe_branch(
324+ instruction.recipe_branch, "bar", "http://bar.org")
325+ self.assertEqual("some/path", instruction.subpath)
326+ self.assertEqual("target-subdir", instruction.target_subdir)
327+
328+ def test_builds_recipe_with_nest_part_subdir_and_revspec(self):
329+ base_branch = self.get_recipe(self.basic_header_and_branch
330+ + "nest-part bar http://bar.org some/path target-subdir 1234")
331+ self.check_base_recipe_branch(base_branch, "http://foo.org/",
332+ num_child_branches=1)
333+ instruction = base_branch.child_branches[0]
334+ self.assertEqual(None, instruction.nest_path)
335+ self.check_recipe_branch(
336+ instruction.recipe_branch, "bar", "http://bar.org", "1234")
337+ self.assertEqual("some/path", instruction.subpath)
338+ self.assertEqual("target-subdir", instruction.target_subdir)
339+
340 def test_builds_recipe_with_nest(self):
341 base_branch = self.get_recipe(self.basic_header_and_branch
342 + "nest bar http://bar.org baz")
343@@ -462,15 +513,17 @@
344 self.assertEqual(revid, tree.last_revision())
345 self.assertEqual(revid, base_branch.revid)
346
347+ def make_source_branch(self, relpath):
348+ """Make a branch with one file and one commit."""
349+ source1 = self.make_branch_and_tree(relpath)
350+ self.build_tree([relpath + "/a"])
351+ source1.add(["a"])
352+ source1.commit("one")
353+ return source1
354+
355 def test_build_tree_nested(self):
356- source1 = self.make_branch_and_tree("source1")
357- self.build_tree(["source1/a"])
358- source1.add(["a"])
359- source1_rev_id = source1.commit("one")
360- source2 = self.make_branch_and_tree("source2")
361- self.build_tree(["source2/a"])
362- source2.add(["a"])
363- source2_rev_id = source2.commit("one")
364+ source1_rev_id = self.make_source_branch("source1").last_revision()
365+ source2_rev_id = self.make_source_branch("source2").last_revision()
366 base_branch = BaseRecipeBranch("source1", "1", 0.2)
367 nested_branch = RecipeBranch("nested", "source2")
368 base_branch.nest_branch("sub", nested_branch)
369@@ -484,10 +537,8 @@
370 self.assertEqual(source2_rev_id, nested_branch.revid)
371
372 def test_build_tree_merged(self):
373- source1 = self.make_branch_and_tree("source1")
374- self.build_tree(["source1/a"])
375- source1.add(["a"])
376- source1_rev_id = source1.commit("one")
377+ source1 = self.make_source_branch("source1")
378+ source1_rev_id = source1.last_revision()
379 source2 = source1.bzrdir.sprout("source2").open_workingtree()
380 self.build_tree_contents([("source2/a", "other change")])
381 source2_rev_id = source2.commit("one")
382@@ -505,11 +556,60 @@
383 self.assertEqual(source1_rev_id, base_branch.revid)
384 self.assertEqual(source2_rev_id, merged_branch.revid)
385
386+ def test_build_tree_nest_part(self):
387+ """A recipe can specify a merge of just part of an unrelated tree."""
388+ source1 = self.make_source_branch("source1")
389+ source2 = self.make_source_branch("source2")
390+ source1.lock_read()
391+ self.addCleanup(source1.unlock)
392+ source1_rev_id = source1.last_revision()
393+ # Add 'b' to source2.
394+ self.build_tree_contents([
395+ ("source2/b", "new file"), ("source2/not-b", "other file")])
396+ source2.add(["b", "not-b"])
397+ source2_rev_id = source2.commit("two")
398+ base_branch = BaseRecipeBranch("source1", "1", 0.2)
399+ merged_branch = RecipeBranch("merged", "source2")
400+ # Merge just 'b' from source2; 'a' is untouched.
401+ base_branch.nest_part_branch(merged_branch, "b")
402+ build_tree(base_branch, "target")
403+ file_id = source1.path2id("a")
404+ self.check_file_contents("target/a", source1.get_file_text(file_id))
405+ self.check_file_contents("target/b", "new file")
406+ self.assertNotInWorkingTree("not-b", "target")
407+ self.assertEqual(source1_rev_id, base_branch.revid)
408+ self.assertEqual(source2_rev_id, merged_branch.revid)
409+
410+ def test_build_tree_nest_part_explicit_target(self):
411+ """A recipe can specify a merge of just part of an unrelated tree into
412+ a specific subdirectory of the target tree.
413+ """
414+ source1 = self.make_branch_and_tree("source1")
415+ self.build_tree(["source1/dir/"])
416+ source1.add(["dir"])
417+ source1.commit("one")
418+ source2 = self.make_source_branch("source2")
419+ source1.lock_read()
420+ self.addCleanup(source1.unlock)
421+ source1_rev_id = source1.last_revision()
422+ # Add 'b' to source2.
423+ self.build_tree_contents([
424+ ("source2/b", "new file"), ("source2/not-b", "other file")])
425+ source2.add(["b", "not-b"])
426+ source2_rev_id = source2.commit("two")
427+ base_branch = BaseRecipeBranch("source1", "1", 0.2)
428+ merged_branch = RecipeBranch("merged", "source2")
429+ # Merge just 'b' from source2; 'a' is untouched.
430+ base_branch.nest_part_branch(merged_branch, "b", "dir/b")
431+ build_tree(base_branch, "target")
432+ self.check_file_contents("target/dir/b", "new file")
433+ self.assertNotInWorkingTree("dir/not-b", "target")
434+ self.assertEqual(source1_rev_id, base_branch.revid)
435+ self.assertEqual(source2_rev_id, merged_branch.revid)
436+
437 def test_build_tree_merge_twice(self):
438- source1 = self.make_branch_and_tree("source1")
439- self.build_tree(["source1/a"])
440- source1.add(["a"])
441- source1_rev_id = source1.commit("one")
442+ source1 = self.make_source_branch("source1")
443+ source1_rev_id = source1.last_revision()
444 source2 = source1.bzrdir.sprout("source2").open_workingtree()
445 self.build_tree_contents([("source2/a", "other change")])
446 source2_rev_id = source2.commit("one")
447@@ -538,10 +638,7 @@
448 self.assertEqual(source3_rev_id, merged_branch2.revid)
449
450 def test_build_tree_merged_with_conflicts(self):
451- source1 = self.make_branch_and_tree("source1")
452- self.build_tree(["source1/a"])
453- source1.add(["a"])
454- source1_rev_id = source1.commit("one")
455+ source1 = self.make_source_branch("source1")
456 source2 = source1.bzrdir.sprout("source2").open_workingtree()
457 self.build_tree_contents([("source2/a", "other change\n")])
458 source2_rev_id = source2.commit("one")
459@@ -567,10 +664,8 @@
460 self.assertEqual(source2_rev_id, merged_branch.revid)
461
462 def test_build_tree_with_revspecs(self):
463- source1 = self.make_branch_and_tree("source1")
464- self.build_tree(["source1/a"])
465- source1.add(["a"])
466- source1_rev_id = source1.commit("one")
467+ source1 = self.make_source_branch("source1")
468+ source1_rev_id = source1.last_revision()
469 source2 = source1.bzrdir.sprout("source2").open_workingtree()
470 self.build_tree_contents([("source2/a", "other change\n")])
471 source2_rev_id = source2.commit("one")

Subscribers

People subscribed via source and target branches