Merge lp:~mbp/bzr/45719-update-r into lp:bzr

Proposed by Martin Pool
Status: Merged
Approved by: John A Meinel
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~mbp/bzr/45719-update-r
Merge into: lp:bzr
Diff against target: 361 lines (+176/-37)
5 files modified
NEWS (+13/-5)
bzrlib/builtins.py (+41/-15)
bzrlib/tests/blackbox/test_update.py (+82/-6)
bzrlib/tests/per_workingtree/test_workingtree.py (+14/-0)
bzrlib/workingtree.py (+26/-11)
To merge this branch: bzr merge lp:~mbp/bzr/45719-update-r
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+16476@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

This adds a 'bzr update -r' option. If the branch is bound to another, it always updates from the master branch first. This updated form of the patch addresses John's review comments in <https://bugs.edge.launchpad.net/bzr/+bug/45719/comments/20>.

This is an updated version of what was one of the longest-standing merges, and it's for a five-digit bug too <https://bugs.edge.launchpad.net/bzr/+bug/45719>.

Revision history for this message
John A Meinel (jameinel) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Martin Pool wrote:
> Martin Pool has proposed merging lp:~mbp/bzr/45719-update-r into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
> Related bugs:
> #45719 update command cannot take a revision number
> https://bugs.launchpad.net/bugs/45719
>
>
> This adds a 'bzr update -r' option. If the branch is bound to another, it always updates from the master branch first. This updated form of the patch addresses John's review comments in <https://bugs.edge.launchpad.net/bzr/+bug/45719/comments/20>.
>
> This is an updated version of what was one of the longest-standing merges, and it's for a five-digit bug too <https://bugs.edge.launchpad.net/bzr/+bug/45719>.
>

v- does 'change_reporter' need to be inside the try/except ? I think it
just got refactored into there.

+ try:
+ change_reporter = delta._ChangeReporter(
+ unversioned_filter=tree.is_ignored,
+ view_info=view_info)
+ conflicts = tree.update(
+ change_reporter,
+ possible_transports=possible_transports,
+ revision=rev,
+ old_tip=old_tip)
+ except errors.NoSuchRevision, e:
+ raise errors.BzrCommandError(
+ "branch has no revision %s\n"
+ "bzr update --revision only works"
+ " for a revision in the branch
history"
+ % (e.revision))

+ if revision is not None:
+ rev = revision[0].as_revision_id(branch)
+ else:
+ rev = branch.last_revision()

^- It might be clearer as 'rev_id' or 'revision_id = '. We have some
ambiguous naming (is it a Revision, revision_id, or RevSpec...) However,
this is really minor.

 review: approve
 merge: approve

John
=:->
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAksw26oACgkQJdeBCYSNAAO00gCeOtH7vUpSUdkD8xTtoyOgod/g
sdIAn3ABG2q0RkqjrPIhgsm9lv5V5Ofs
=IrMf
-----END PGP SIGNATURE-----

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-12-23 05:04:12 +0000
+++ NEWS 2009-12-23 06:33:19 +0000
@@ -23,11 +23,10 @@
23 ``locations.conf`` or ``branch.conf``.23 ``locations.conf`` or ``branch.conf``.
24 (Ted Gould, Matthew Fuller, Vincent Ladeuil)24 (Ted Gould, Matthew Fuller, Vincent Ladeuil)
2525
26* ``bzrlib.tests.permute_for_extension`` is a helper that simplifies26* ``bzr update`` now takes a ``--revision`` argument. This lets you
27 running all tests in the current module, once against a pure python27 change the revision of the working tree to any revision in the
28 implementation, and once against an extension (pyrex/C) implementation.28 ancestry of the current or master branch. (Matthieu Moy, Mark Hammond,
29 It can be used to dramatically simplify the implementation of29 Martin Pool, #45719)
30 ``load_tests``. (John Arbash Meinel)
3130
32Bug Fixes31Bug Fixes
33*********32*********
@@ -60,6 +59,9 @@
60 CamelCase. For the features that were more likely to be used, we added a59 CamelCase. For the features that were more likely to be used, we added a
61 deprecation thunk, but not all. (John Arbash Meinel)60 deprecation thunk, but not all. (John Arbash Meinel)
6261
62* ``WorkingTree.update`` implementations must now accept a ``revision``
63 parameter.
64
63Internals65Internals
64*********66*********
6567
@@ -71,6 +73,12 @@
71Testing73Testing
72*******74*******
7375
76* ``bzrlib.tests.permute_for_extension`` is a helper that simplifies
77 running all tests in the current module, once against a pure python
78 implementation, and once against an extension (pyrex/C) implementation.
79 It can be used to dramatically simplify the implementation of
80 ``load_tests``. (John Arbash Meinel)
81
74* ``bzrlib.tests.TestCase`` now subclasses ``testtools.testcase.TestCase``.82* ``bzrlib.tests.TestCase`` now subclasses ``testtools.testcase.TestCase``.
75 This permits features in testtools such as getUniqueInteger and83 This permits features in testtools such as getUniqueInteger and
76 getUniqueString to be used. Because of this, testtools version 0.9.2 or84 getUniqueString to be used. Because of this, testtools version 0.9.2 or
7785
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2009-12-21 17:24:22 +0000
+++ bzrlib/builtins.py 2009-12-23 06:33:19 +0000
@@ -1388,16 +1388,24 @@
13881388
1389 If you want to discard your local changes, you can just do a1389 If you want to discard your local changes, you can just do a
1390 'bzr revert' instead of 'bzr commit' after the update.1390 'bzr revert' instead of 'bzr commit' after the update.
1391
1392 If the tree's branch is bound to a master branch, it will also update
1393 the branch from the master.
1391 """1394 """
13921395
1393 _see_also = ['pull', 'working-trees', 'status-flags']1396 _see_also = ['pull', 'working-trees', 'status-flags']
1394 takes_args = ['dir?']1397 takes_args = ['dir?']
1398 takes_options = ['revision']
1395 aliases = ['up']1399 aliases = ['up']
13961400
1397 def run(self, dir='.'):1401 def run(self, dir='.', revision=None):
1402 if revision is not None and len(revision) != 1:
1403 raise errors.BzrCommandError(
1404 "bzr update --revision takes exactly one revision")
1398 tree = WorkingTree.open_containing(dir)[0]1405 tree = WorkingTree.open_containing(dir)[0]
1406 branch = tree.branch
1399 possible_transports = []1407 possible_transports = []
1400 master = tree.branch.get_master_branch(1408 master = branch.get_master_branch(
1401 possible_transports=possible_transports)1409 possible_transports=possible_transports)
1402 if master is not None:1410 if master is not None:
1403 tree.lock_write()1411 tree.lock_write()
@@ -1410,20 +1418,38 @@
1410 self.outf.encoding)1418 self.outf.encoding)
1411 try:1419 try:
1412 existing_pending_merges = tree.get_parent_ids()[1:]1420 existing_pending_merges = tree.get_parent_ids()[1:]
1413 last_rev = _mod_revision.ensure_null(tree.last_revision())1421 if master is None:
1414 if last_rev == _mod_revision.ensure_null(1422 old_tip = None
1415 tree.branch.last_revision()):1423 else:
1416 # may be up to date, check master too.1424 # may need to fetch data into a heavyweight checkout
1417 if master is None or last_rev == _mod_revision.ensure_null(1425 # XXX: this may take some time, maybe we should display a
1418 master.last_revision()):1426 # message
1419 revno = tree.branch.revision_id_to_revno(last_rev)1427 old_tip = branch.update(possible_transports)
1420 note('Tree is up to date at revision %d of branch %s'1428 if revision is not None:
1421 % (revno, branch_location))1429 revision_id = revision[0].as_revision_id(branch)
1422 return 01430 else:
1431 revision_id = branch.last_revision()
1432 if revision_id == _mod_revision.ensure_null(tree.last_revision()):
1433 revno = branch.revision_id_to_revno(revision_id)
1434 note("Tree is up to date at revision %d of branch %s" %
1435 (revno, branch_location))
1436 return 0
1423 view_info = _get_view_info_for_change_reporter(tree)1437 view_info = _get_view_info_for_change_reporter(tree)
1424 conflicts = tree.update(1438 change_reporter = delta._ChangeReporter(
1425 delta._ChangeReporter(unversioned_filter=tree.is_ignored,1439 unversioned_filter=tree.is_ignored,
1426 view_info=view_info), possible_transports=possible_transports)1440 view_info=view_info)
1441 try:
1442 conflicts = tree.update(
1443 change_reporter,
1444 possible_transports=possible_transports,
1445 revision=revision_id,
1446 old_tip=old_tip)
1447 except errors.NoSuchRevision, e:
1448 raise errors.BzrCommandError(
1449 "branch has no revision %s\n"
1450 "bzr update --revision only works"
1451 " for a revision in the branch history"
1452 % (e.revision))
1427 revno = tree.branch.revision_id_to_revno(1453 revno = tree.branch.revision_id_to_revno(
1428 _mod_revision.ensure_null(tree.last_revision()))1454 _mod_revision.ensure_null(tree.last_revision()))
1429 note('Updated to revision %d of branch %s' %1455 note('Updated to revision %d of branch %s' %
14301456
=== modified file 'bzrlib/tests/blackbox/test_update.py'
--- bzrlib/tests/blackbox/test_update.py 2009-12-14 15:51:36 +0000
+++ bzrlib/tests/blackbox/test_update.py 2009-12-23 06:33:19 +0000
@@ -1,4 +1,4 @@
1# Copyright (C) 2006 Canonical Ltd1# Copyright (C) 2006, 2009 Canonical Ltd
2# -*- coding: utf-8 -*-2# -*- coding: utf-8 -*-
3#3#
4# This program is free software; you can redistribute it and/or modify4# This program is free software; you can redistribute it and/or modify
@@ -29,6 +29,7 @@
29 urlutils,29 urlutils,
30 workingtree,30 workingtree,
31 )31 )
32from bzrlib.tests.script import ScriptRunner
3233
3334
34class TestUpdate(tests.TestCaseWithTransport):35class TestUpdate(tests.TestCaseWithTransport):
@@ -67,11 +68,11 @@
67 def test_update_up_to_date_checkout(self):68 def test_update_up_to_date_checkout(self):
68 self.make_branch_and_tree('branch')69 self.make_branch_and_tree('branch')
69 self.run_bzr('checkout branch checkout')70 self.run_bzr('checkout branch checkout')
70 out, err = self.run_bzr('update checkout')71 sr = ScriptRunner()
71 self.assertEqual('Tree is up to date at revision 0 of branch %s\n'72 sr.run_script(self, '''
72 % osutils.pathjoin(self.test_dir, 'branch'),73$ bzr update checkout
73 err)742>Tree is up to date at revision 0 of branch .../branch
74 self.assertEqual('', out)75''')
7576
76 def test_update_out_of_date_standalone_tree(self):77 def test_update_out_of_date_standalone_tree(self):
77 # FIXME the default format has to change for this to pass78 # FIXME the default format has to change for this to pass
@@ -239,3 +240,78 @@
239 lightweight=True)240 lightweight=True)
240 tree.commit('empty commit')241 tree.commit('empty commit')
241 self.run_bzr('update checkout')242 self.run_bzr('update checkout')
243
244 def test_update_dash_r(self):
245 # Test that 'bzr update' works correctly when you have
246 # an update in the master tree, and a lightweight checkout
247 # which has merged another branch
248 master = self.make_branch_and_tree('master')
249 os.chdir('master')
250 self.build_tree(['./file1'])
251 master.add(['file1'])
252 master.commit('one', rev_id='m1')
253 self.build_tree(['./file2'])
254 master.add(['file2'])
255 master.commit('two', rev_id='m2')
256
257 sr = ScriptRunner()
258 sr.run_script(self, '''
259$ bzr update -r 1
2602>-D file2
2612>All changes applied successfully.
2622>Updated to revision 1 of .../master
263''')
264 self.failUnlessExists('./file1')
265 self.failIfExists('./file2')
266 self.assertEquals(['m1'], master.get_parent_ids())
267
268 def test_update_dash_r_outside_history(self):
269 # Test that 'bzr update' works correctly when you have
270 # an update in the master tree, and a lightweight checkout
271 # which has merged another branch
272 master = self.make_branch_and_tree('master')
273 self.build_tree(['master/file1'])
274 master.add(['file1'])
275 master.commit('one', rev_id='m1')
276
277 # Create a second branch, with an extra commit
278 other = master.bzrdir.sprout('other').open_workingtree()
279 self.build_tree(['other/file2'])
280 other.add(['file2'])
281 other.commit('other2', rev_id='o2')
282
283 os.chdir('master')
284 self.run_bzr('merge ../other')
285 master.commit('merge', rev_id='merge')
286
287 out, err = self.run_bzr('update -r revid:o2',
288 retcode=3)
289 self.assertEqual('', out)
290 self.assertEqual('bzr: ERROR: branch has no revision o2\n'
291 'bzr update --revision only works'
292 ' for a revision in the branch history\n',
293 err)
294
295 def test_update_dash_r_in_master(self):
296 # Test that 'bzr update' works correctly when you have
297 # an update in the master tree,
298 master = self.make_branch_and_tree('master')
299 self.build_tree(['master/file1'])
300 master.add(['file1'])
301 master.commit('one', rev_id='m1')
302
303 self.run_bzr('checkout master checkout')
304
305 # add a revision in the master.
306 self.build_tree(['master/file2'])
307 master.add(['file2'])
308 master.commit('two', rev_id='m2')
309
310 os.chdir('checkout')
311 sr = ScriptRunner()
312 sr.run_script(self, '''
313$ bzr update -r revid:m2
3142>+N file2
3152>All changes applied successfully.
3162>Updated to revision 2 of branch .../master
317''')
242318
=== modified file 'bzrlib/tests/per_workingtree/test_workingtree.py'
--- bzrlib/tests/per_workingtree/test_workingtree.py 2009-11-08 05:28:57 +0000
+++ bzrlib/tests/per_workingtree/test_workingtree.py 2009-12-23 06:33:19 +0000
@@ -590,6 +590,20 @@
590 self.assertEqual(master_tree.branch.revision_history(),590 self.assertEqual(master_tree.branch.revision_history(),
591 tree.branch.revision_history())591 tree.branch.revision_history())
592592
593 def test_update_takes_revision_parameter(self):
594 wt = self.make_branch_and_tree('wt')
595 self.build_tree_contents([('wt/a', 'old content')])
596 wt.add(['a'])
597 rev1 = wt.commit('first master commit')
598 self.build_tree_contents([('wt/a', 'new content')])
599 rev2 = wt.commit('second master commit')
600 # https://bugs.edge.launchpad.net/bzr/+bug/45719/comments/20
601 # when adding 'update -r' we should make sure all wt formats support
602 # it
603 conflicts = wt.update(revision=rev1)
604 self.assertFileEqual('old content', 'wt/a')
605 self.assertEqual([rev1], wt.get_parent_ids())
606
593 def test_merge_modified_detects_corruption(self):607 def test_merge_modified_detects_corruption(self):
594 # FIXME: This doesn't really test that it works; also this is not608 # FIXME: This doesn't really test that it works; also this is not
595 # implementation-independent. mbp 20070226609 # implementation-independent. mbp 20070226
596610
=== modified file 'bzrlib/workingtree.py'
--- bzrlib/workingtree.py 2009-12-02 18:15:55 +0000
+++ bzrlib/workingtree.py 2009-12-23 06:33:19 +0000
@@ -2191,7 +2191,10 @@
2191 """2191 """
2192 raise NotImplementedError(self.unlock)2192 raise NotImplementedError(self.unlock)
21932193
2194 def update(self, change_reporter=None, possible_transports=None):2194 _marker = object()
2195
2196 def update(self, change_reporter=None, possible_transports=None,
2197 revision=None, old_tip=_marker):
2195 """Update a working tree along its branch.2198 """Update a working tree along its branch.
21962199
2197 This will update the branch if its bound too, which means we have2200 This will update the branch if its bound too, which means we have
@@ -2215,10 +2218,16 @@
2215 - Merge current state -> basis tree of the master w.r.t. the old tree2218 - Merge current state -> basis tree of the master w.r.t. the old tree
2216 basis.2219 basis.
2217 - Do a 'normal' merge of the old branch basis if it is relevant.2220 - Do a 'normal' merge of the old branch basis if it is relevant.
2221
2222 :param revision: The target revision to update to. Must be in the
2223 revision history.
2224 :param old_tip: If branch.update() has already been run, the value it
2225 returned (old tip of the branch or None). _marker is used
2226 otherwise.
2218 """2227 """
2219 if self.branch.get_bound_location() is not None:2228 if self.branch.get_bound_location() is not None:
2220 self.lock_write()2229 self.lock_write()
2221 update_branch = True2230 update_branch = (old_tip is self._marker)
2222 else:2231 else:
2223 self.lock_tree_write()2232 self.lock_tree_write()
2224 update_branch = False2233 update_branch = False
@@ -2226,13 +2235,14 @@
2226 if update_branch:2235 if update_branch:
2227 old_tip = self.branch.update(possible_transports)2236 old_tip = self.branch.update(possible_transports)
2228 else:2237 else:
2229 old_tip = None2238 if old_tip is self._marker:
2230 return self._update_tree(old_tip, change_reporter)2239 old_tip = None
2240 return self._update_tree(old_tip, change_reporter, revision)
2231 finally:2241 finally:
2232 self.unlock()2242 self.unlock()
22332243
2234 @needs_tree_write_lock2244 @needs_tree_write_lock
2235 def _update_tree(self, old_tip=None, change_reporter=None):2245 def _update_tree(self, old_tip=None, change_reporter=None, revision=None):
2236 """Update a tree to the master branch.2246 """Update a tree to the master branch.
22372247
2238 :param old_tip: if supplied, the previous tip revision the branch,2248 :param old_tip: if supplied, the previous tip revision the branch,
@@ -2253,12 +2263,17 @@
2253 last_rev = self.get_parent_ids()[0]2263 last_rev = self.get_parent_ids()[0]
2254 except IndexError:2264 except IndexError:
2255 last_rev = _mod_revision.NULL_REVISION2265 last_rev = _mod_revision.NULL_REVISION
2256 if last_rev != _mod_revision.ensure_null(self.branch.last_revision()):2266 if revision is None:
2257 # merge tree state up to new branch tip.2267 revision = self.branch.last_revision()
2268 else:
2269 if revision not in self.branch.revision_history():
2270 raise errors.NoSuchRevision(self.branch, revision)
2271 if last_rev != _mod_revision.ensure_null(revision):
2272 # merge tree state up to specified revision.
2258 basis = self.basis_tree()2273 basis = self.basis_tree()
2259 basis.lock_read()2274 basis.lock_read()
2260 try:2275 try:
2261 to_tree = self.branch.basis_tree()2276 to_tree = self.branch.repository.revision_tree(revision)
2262 if basis.inventory.root is None:2277 if basis.inventory.root is None:
2263 self.set_root_id(to_tree.get_root_id())2278 self.set_root_id(to_tree.get_root_id())
2264 self.flush()2279 self.flush()
@@ -2268,11 +2283,12 @@
2268 basis,2283 basis,
2269 this_tree=self,2284 this_tree=self,
2270 change_reporter=change_reporter)2285 change_reporter=change_reporter)
2286 self.set_last_revision(revision)
2271 finally:2287 finally:
2272 basis.unlock()2288 basis.unlock()
2273 # TODO - dedup parents list with things merged by pull ?2289 # TODO - dedup parents list with things merged by pull ?
2274 # reuse the tree we've updated to to set the basis:2290 # reuse the tree we've updated to to set the basis:
2275 parent_trees = [(self.branch.last_revision(), to_tree)]2291 parent_trees = [(revision, to_tree)]
2276 merges = self.get_parent_ids()[1:]2292 merges = self.get_parent_ids()[1:]
2277 # Ideally we ask the tree for the trees here, that way the working2293 # Ideally we ask the tree for the trees here, that way the working
2278 # tree can decide whether to give us the entire tree or give us a2294 # tree can decide whether to give us the entire tree or give us a
@@ -2308,8 +2324,7 @@
2308 # should be able to remove this extra flush.2324 # should be able to remove this extra flush.
2309 self.flush()2325 self.flush()
2310 graph = self.branch.repository.get_graph()2326 graph = self.branch.repository.get_graph()
2311 base_rev_id = graph.find_unique_lca(self.branch.last_revision(),2327 base_rev_id = graph.find_unique_lca(revision, old_tip)
2312 old_tip)
2313 base_tree = self.branch.repository.revision_tree(base_rev_id)2328 base_tree = self.branch.repository.revision_tree(base_rev_id)
2314 other_tree = self.branch.repository.revision_tree(old_tip)2329 other_tree = self.branch.repository.revision_tree(old_tip)
2315 result += merge.merge_inner(2330 result += merge.merge_inner(