Merge lp:~vila/bzr/206577-send-strict into lp:~bzr/bzr/trunk-old

Proposed by Vincent Ladeuil
Status: Superseded
Proposed branch: lp:~vila/bzr/206577-send-strict
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 369 lines (has conflicts)
Text conflict in NEWS
To merge this branch: bzr merge lp:~vila/bzr/206577-send-strict
Reviewer Review Type Date Requested Status
bzr-core Pending
Review via email: mp+8006@code.launchpad.net

This proposal has been superseded by a proposal from 2009-07-01.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

This fixes bug #206577 by introducing a --strict boolean option for 'send' and
'bundle-revisions' and aborting when uncommitted changes are found in the working tree.

As for bug #284038 (see https://code.edge.launchpad.net/~vila/bzr/284038-push-strict/+merge/8005),
I plan to introduce a way to query config files for a bool option and a new method on working tree
for checking changes, in a later submission.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-06-30 17:00:26 +0000
3+++ NEWS 2009-06-30 20:35:32 +0000
4@@ -21,6 +21,7 @@
5 New Features
6 ************
7
8+<<<<<<< TREE
9 * ``bzr push`` now aborts if uncommitted changes (including pending merges)
10 are present in the working tree (if one is present) and no revision is
11 speified. The configuration option ``push_strict`` can be used to set the
12@@ -29,6 +30,18 @@
13 * ``bzr revno`` and ``bzr revision-info`` now have a ``--tree`` option to
14 show revision info for the working tree instead of the branch.
15 (Matthew Fuller, John Arbash Meinel)
16+=======
17+* ``bzr push`` now checks if uncommitted changes are present in the working
18+ tree if the ``--strict`` option is used. The ``push_strict`` option can
19+ be declared in a configuration file.
20+ (Vincent Ladeuil, #284038)
21+>>>>>>> MERGE-SOURCE
22+
23+* ``bzr send`` now aborts if uncommitted changes (including pending merges)
24+ are present in the working tree and no revision is speficied. The
25+ ``--no-strict`` option can be used to force the sending. The
26+ ``send_strict`` option can be declared in a configuration file.
27+ (Vincent Ladeuil, #206577)
28
29
30 Bug Fixes
31
32=== modified file 'bzrlib/builtins.py'
33--- bzrlib/builtins.py 2009-06-30 17:00:26 +0000
34+++ bzrlib/builtins.py 2009-06-30 20:35:32 +0000
35@@ -4950,24 +4950,29 @@
36 help='Write merge directive to this file; '
37 'use - for stdout.',
38 type=unicode),
39+ Option('strict',
40+ help='Refuse to send if there are uncommitted changes in'
41+ ' the working tree.'),
42 Option('mail-to', help='Mail the request to this address.',
43 type=unicode),
44 'revision',
45 'message',
46 Option('body', help='Body for the email.', type=unicode),
47 RegistryOption('format',
48- help='Use the specified output format.',
49- lazy_registry=('bzrlib.send', 'format_registry'))
50+ help='Use the specified output format.',
51+ lazy_registry=('bzrlib.send', 'format_registry')),
52 ]
53
54 def run(self, submit_branch=None, public_branch=None, no_bundle=False,
55 no_patch=False, revision=None, remember=False, output=None,
56- format=None, mail_to=None, message=None, body=None, **kwargs):
57+ format=None, mail_to=None, message=None, body=None,
58+ strict=None, **kwargs):
59 from bzrlib.send import send
60 return send(submit_branch, revision, public_branch, remember,
61- format, no_bundle, no_patch, output,
62- kwargs.get('from', '.'), mail_to, message, body,
63- self.outf)
64+ format, no_bundle, no_patch, output,
65+ kwargs.get('from', '.'), mail_to, message, body,
66+ self.outf,
67+ strict=strict)
68
69
70 class cmd_bundle_revisions(cmd_send):
71@@ -5017,6 +5022,9 @@
72 type=unicode),
73 Option('output', short_name='o', help='Write directive to this file.',
74 type=unicode),
75+ Option('strict',
76+ help='Refuse to bundle revisions if there are'
77+ ' uncommitted changes in the working tree.'),
78 'revision',
79 RegistryOption('format',
80 help='Use the specified output format.',
81@@ -5030,14 +5038,14 @@
82
83 def run(self, submit_branch=None, public_branch=None, no_bundle=False,
84 no_patch=False, revision=None, remember=False, output=None,
85- format=None, **kwargs):
86+ format=None, strict=None, **kwargs):
87 if output is None:
88 output = '-'
89 from bzrlib.send import send
90 return send(submit_branch, revision, public_branch, remember,
91 format, no_bundle, no_patch, output,
92 kwargs.get('from', '.'), None, None, None,
93- self.outf)
94+ self.outf, strict=strict)
95
96
97 class cmd_tag(Command):
98
99=== modified file 'bzrlib/help_topics/en/configuration.txt'
100--- bzrlib/help_topics/en/configuration.txt 2009-06-10 13:18:48 +0000
101+++ bzrlib/help_topics/en/configuration.txt 2009-06-30 20:35:32 +0000
102@@ -421,3 +421,10 @@
103
104 If set to "True", the branch should act as a checkout, and push each commit to
105 the bound_location. This option is normally set by ``bind``/``unbind``.
106+
107+send_strict
108+~~~~~~~~~~~
109+
110+If present, defines the ``--strict`` option default value for checking
111+uncommitted changes before sending a merge directive.
112+
113
114=== modified file 'bzrlib/send.py'
115--- bzrlib/send.py 2009-05-28 16:14:16 +0000
116+++ bzrlib/send.py 2009-06-30 20:35:32 +0000
117@@ -37,8 +37,8 @@
118
119
120 def send(submit_branch, revision, public_branch, remember, format,
121- no_bundle, no_patch, output, from_, mail_to, message, body,
122- to_file):
123+ no_bundle, no_patch, output, from_, mail_to, message, body,
124+ to_file, strict=None):
125 tree, branch = bzrdir.BzrDir.open_containing_tree_or_branch(from_)[:2]
126 # we may need to write data into branch's repository to calculate
127 # the data to send.
128@@ -107,6 +107,21 @@
129 if len(revision) == 2:
130 base_revision_id = revision[0].as_revision_id(branch)
131 if revision_id is None:
132+ if strict is None:
133+ strict = branch.get_config().get_user_option('send_strict')
134+ if strict is not None:
135+ # FIXME: This should be better supported by config
136+ # -- vila 20090626
137+ bools = dict(yes=True, no=False, on=True, off=False,
138+ true=True, false=False)
139+ try:
140+ strict = bools[strict.lower()]
141+ except KeyError:
142+ strict = None
143+ if strict is None or strict: # Default to True
144+ changes = tree.changes_from(tree.basis_tree())
145+ if changes.has_changed() or len(tree.get_parent_ids()) > 1:
146+ raise errors.UncommittedChanges(tree)
147 revision_id = branch.last_revision()
148 if revision_id == NULL_REVISION:
149 raise errors.BzrCommandError('No revisions to submit.')
150
151=== modified file 'bzrlib/tests/blackbox/test_send.py'
152--- bzrlib/tests/blackbox/test_send.py 2009-06-26 08:20:13 +0000
153+++ bzrlib/tests/blackbox/test_send.py 2009-06-30 20:35:32 +0000
154@@ -28,12 +28,52 @@
155 from bzrlib.bundle import serializer
156
157
158-def read_bundle(fileobj):
159- md = merge_directive.MergeDirective.from_lines(fileobj.readlines())
160- return serializer.read_bundle(StringIO(md.get_raw_bundle()))
161-
162-
163-class TestSend(tests.TestCaseWithTransport):
164+def load_tests(standard_tests, module, loader):
165+ """Multiply tests for the send command."""
166+ result = loader.suiteClass()
167+
168+ # one for each king of change
169+ changes_tests, remaining_tests = tests.split_suite_by_condition(
170+ standard_tests, tests.condition_isinstance((
171+ TestSendStrictWithChanges,
172+ )))
173+ changes_scenarios = [
174+ ('uncommitted',
175+ dict(_changes_type= '_uncommitted_changes')),
176+ ('pending_merges',
177+ dict(_changes_type= '_pending_merges')),
178+ ]
179+ tests.multiply_tests(changes_tests, changes_scenarios, result)
180+ # No parametrization for the remaining tests
181+ result.addTests(remaining_tests)
182+
183+ return result
184+
185+
186+class TestSendMixin(object):
187+
188+ _default_command = ['send', '-o-']
189+ _default_wd = 'branch'
190+
191+ def run_send(self, args, cmd=None, rc=0, wd=None, err_re=None):
192+ if cmd is None: cmd = self._default_command
193+ if wd is None: wd = self._default_wd
194+ if err_re is None: err_re = []
195+ return self.run_bzr(cmd + args, retcode=rc,
196+ working_dir=wd,
197+ error_regexes=err_re)
198+
199+ def get_MD(self, args, cmd=None, wd='branch'):
200+ out = StringIO(self.run_send(args, cmd=cmd, wd=wd)[0])
201+ return merge_directive.MergeDirective.from_lines(out.readlines())
202+
203+ def assertBundleContains(self, revs, args, cmd=None, wd='branch'):
204+ md = self.get_MD(args, cmd=cmd, wd=wd)
205+ br = serializer.read_bundle(StringIO(md.get_raw_bundle()))
206+ self.assertEqual(set(revs), set(r.revision_id for r in br.revisions))
207+
208+
209+class TestSend(tests.TestCaseWithTransport, TestSendMixin):
210
211 def setUp(self):
212 super(TestSend, self).setUp()
213@@ -51,24 +91,6 @@
214 self.build_tree_contents([('branch/file1', 'branch')])
215 branch_tree.commit('last commit', rev_id='rev3')
216
217- def run_send(self, args, cmd=None, rc=0, wd='branch'):
218- if cmd is None:
219- cmd = ['send', '-o-']
220- return self.run_bzr(cmd + args, retcode=rc, working_dir=wd)
221-
222- def get_MD(self, args, cmd=None, wd='branch'):
223- out = StringIO(self.run_send(args, cmd=cmd, wd=wd)[0])
224- return merge_directive.MergeDirective.from_lines(out.readlines())
225-
226- def get_bundle(self, args, cmd=None, wd='branch'):
227- md = self.get_MD(args, cmd=cmd, wd=wd)
228- return serializer.read_bundle(StringIO(md.get_raw_bundle()))
229-
230- def assertBundleContains(self, revs, args, cmd=None, wd='branch'):
231- out = self.run_send(args, cmd=cmd, wd=wd)[0]
232- br = read_bundle(StringIO(out))
233- self.assertEqual(set(revs), set(r.revision_id for r in br.revisions))
234-
235 def assertFormatIs(self, fmt_string, md):
236 self.assertEqual(fmt_string, md.get_raw_bundle().splitlines()[0])
237
238@@ -263,3 +285,131 @@
239 out, err = self.run_bzr(["send", "--from", location], retcode=3)
240 self.assertEqual(out, '')
241 self.assertEqual(err, 'bzr: ERROR: Not a branch: "%s".\n' % location)
242+
243+
244+class TestSendStrictMixin(TestSendMixin):
245+
246+ _default_command = ['send', '-o-', '../parent']
247+ _default_wd = 'local'
248+
249+ def make_parent_and_local_branches(self):
250+ # Create a 'parent' branch as the base
251+ self.parent_tree = bzrdir.BzrDir.create_standalone_workingtree('parent')
252+ self.build_tree_contents([('parent/file', 'parent')])
253+ self.parent_tree.add('file')
254+ self.parent_tree.commit('first commit', rev_id='parent')
255+ # Branch 'local' from parent and do a change
256+ local_bzrdir = self.parent_tree.bzrdir.sprout('local')
257+ self.local_tree = local_bzrdir.open_workingtree()
258+ self.build_tree_contents([('local/file', 'local')])
259+ self.local_tree.commit('second commit', rev_id='local')
260+
261+ def set_config_send_strict(self, value):
262+ # set config var (any of bazaar.conf, locations.conf, branch.conf
263+ # should do)
264+ conf = self.local_tree.branch.get_config()
265+ conf.set_user_option('send_strict', value)
266+
267+ def assertSendFails(self, args):
268+ self.run_send(args, rc=3,
269+ err_re=['Working tree ".*/local/"'
270+ ' has uncommitted changes.$',])
271+
272+ def assertSendSucceeds(self, revs, args):
273+ out, err = self.run_send(args)
274+ self.assertEquals('Bundling 1 revision(s).\n', err)
275+ md = merge_directive.MergeDirective.from_lines(
276+ StringIO(out).readlines())
277+ self.assertEqual('parent', md.base_revision_id)
278+ br = serializer.read_bundle(StringIO(md.get_raw_bundle()))
279+ self.assertEqual(set(revs), set(r.revision_id for r in br.revisions))
280+
281+
282+class TestSendStrictWithoutChanges(tests.TestCaseWithTransport,
283+ TestSendStrictMixin):
284+
285+ def setUp(self):
286+ super(TestSendStrictWithoutChanges, self).setUp()
287+ self.make_parent_and_local_branches()
288+
289+ def test_send_default(self):
290+ self.assertSendSucceeds(['local'], [])
291+
292+ def test_send_strict(self):
293+ self.assertSendSucceeds(['local'], ['--strict'])
294+
295+ def test_send_no_strict(self):
296+ self.assertSendSucceeds(['local'], ['--no-strict'])
297+
298+ def test_send_config_var_strict(self):
299+ self.set_config_send_strict('true')
300+ self.assertSendSucceeds(['local'], [])
301+
302+ def test_send_config_var_no_strict(self):
303+ self.set_config_send_strict('false')
304+ self.assertSendSucceeds(['local'], [])
305+
306+
307+class TestSendStrictWithChanges(tests.TestCaseWithTransport,
308+ TestSendStrictMixin):
309+
310+ _changes_type = None # Set by load_tests
311+
312+ def setUp(self):
313+ super(TestSendStrictWithChanges, self).setUp()
314+ getattr(self, self._changes_type)()
315+
316+ def _uncommitted_changes(self):
317+ self.make_parent_and_local_branches()
318+ # Make a change without committing it
319+ self.build_tree_contents([('local/file', 'modified')])
320+
321+ def _pending_merges(self):
322+ self.make_parent_and_local_branches()
323+ # Create 'other' branch containing a new file
324+ other_bzrdir = self.parent_tree.bzrdir.sprout('other')
325+ other_tree = other_bzrdir.open_workingtree()
326+ self.build_tree_contents([('other/other-file', 'other')])
327+ other_tree.add('other-file')
328+ other_tree.commit('other commit', rev_id='other')
329+ # Merge and revert, leaving a pending merge
330+ self.local_tree.merge_from_branch(other_tree.branch)
331+ self.local_tree.revert(filenames=['other-file'], backups=False)
332+
333+ def test_send_default(self):
334+ self.assertSendFails([])
335+
336+ def test_send_with_revision(self):
337+ self.assertSendSucceeds(['local'], ['-r', 'revid:local'])
338+
339+ def test_send_no_strict(self):
340+ self.assertSendSucceeds(['local'], ['--no-strict'])
341+
342+ def test_send_strict_with_changes(self):
343+ self.assertSendFails(['--strict'])
344+
345+ def test_send_respect_config_var_strict(self):
346+ self.set_config_send_strict('true')
347+ self.assertSendFails([])
348+ self.assertSendSucceeds(['local'], ['--no-strict'])
349+
350+
351+ def test_send_bogus_config_var_ignored(self):
352+ self.set_config_send_strict("I'm unsure")
353+ self.assertSendFails([])
354+
355+
356+ def test_send_no_strict_command_line_override_config(self):
357+ self.set_config_send_strict('true')
358+ self.assertSendFails([])
359+ self.assertSendSucceeds(['local'], ['--no-strict'])
360+
361+ def test_push_strict_command_line_override_config(self):
362+ self.set_config_send_strict('false')
363+ self.assertSendSucceeds(['local'], [])
364+ self.assertSendFails(['--strict'])
365+
366+
367+class TestBundleStrictWithoutChanges(TestSendStrictWithoutChanges):
368+
369+ _default_command = ['bundle-revisions', '../parent']