=== modified file 'bzrlib/builtins.py' --- bzrlib/builtins.py 2010-01-21 17:54:58 +0000 +++ bzrlib/builtins.py 2010-01-21 22:08:58 +0000 @@ -1376,10 +1376,10 @@ _see_also = ['pull', 'working-trees', 'status-flags'] takes_args = ['dir?'] - takes_options = ['revision'] + takes_options = ['revision', 'force'] aliases = ['up'] - def run(self, dir='.', revision=None): + def run(self, dir='.', revision=None, force=False): if revision is not None and len(revision) != 1: raise errors.BzrCommandError( "bzr update --revision takes exactly one revision") @@ -1399,6 +1399,26 @@ branch_location = urlutils.unescape_for_display(branch_location[:-1], self.outf.encoding) existing_pending_merges = tree.get_parent_ids()[1:] + + if master: + # look for local commits + from bzrlib.missing import find_unmerged + unmerged_local, _ = find_unmerged(branch, master, restrict='local') + + # look for uncommitted changes + changes = tree.changes_from(tree.basis_tree()) + + if not force and changes.has_changed() and unmerged_local: + is_lightweight = (tree is not None and tree.bzrdir.root_transport.base != branch.bzrdir.root_transport.base) + if is_lightweight: + raise errors.BzrCommandError('The branch that this lightweight checkout is bound to is out of date.' + 'You should consider updating it first. You can also use --force.') + else: + raise errors.BzrCommandError('Cannot update with a' + ' combination of uncommitted changes and local' + ' commits. Please use bzr commit --local to locally' + ' commit your uncommitted changes before updating.') + if master is None: old_tip = None else: === modified file 'bzrlib/tests/blackbox/test_update.py' --- bzrlib/tests/blackbox/test_update.py 2009-12-23 06:31:19 +0000 +++ bzrlib/tests/blackbox/test_update.py 2010-01-21 22:08:58 +0000 @@ -169,7 +169,7 @@ # now, update checkout -> # get all three files and a pending merge. - out, err = self.run_bzr('update checkout') + out, err = self.run_bzr('update --force checkout') self.assertEqual('', out) self.assertEqualDiff("""+N file All changes applied successfully. @@ -315,3 +315,57 @@ 2>All changes applied successfully. 2>Updated to revision 2 of branch .../master ''') + + def _prepare_checkout(self): + """Helper function to create a checkout with both local commits + and uncommitted changes.""" + master = self.make_branch_and_tree('master') + os.chdir('master') + a_file = file('file', 'wt') + a_file.write('Foo') + a_file.close() + + self.run_bzr('add file') + self.run_bzr('commit -m "Commit on Master"') + + # branch + self.run_bzr('checkout . ../checkout') + + # add a revision in the checkout + os.chdir('../checkout') + + a_file = file('file', 'wt') + a_file.write('Bar') + a_file.close() + self.run_bzr('commit --local -m "Commit on Branch"') + + # add local changes + a_file = file('file', 'wt') + a_file.write('FooBar') + a_file.close() + + # bind branch + self.run_bzr('bind ../master') + + os.chdir('..') + + def test_update_checkout_prevent_merge(self): + """"Launchpad bug 113809 in bzr "update performs two merges" + https://launchpad.net/bugs/113809""" + + self._prepare_checkout() + + os.chdir('checkout') + self.expectFailure('Update with a combination of uncommitted changes and local commits causes unexpected merge. (Bug #113809)', + self.run_bzr, 'update', retcode=3) + self.assertEqual('', out) + + def test_update_checkout_force_merge(self): + """"--force should let update work even with local commits + and uncommitted changes""" + + self._prepare_checkout() + + os.chdir('checkout') + out, err = self.run_bzr('update --force', retcode=1) + self.assertEqual('', out)