Skip to content

Commit dd84a07

Browse files
committed
Add 'bundle add', 'bundle remove' commands
Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent 3b78f00 commit dd84a07

File tree

4 files changed

+171
-3
lines changed

4 files changed

+171
-3
lines changed

git_pw/bundle.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,9 +215,9 @@ def create_cmd(name, patch_ids, public, fmt):
215215
def update_cmd(bundle_id, name, patch_ids, public, fmt):
216216
"""Update a bundle.
217217
218-
Update a bundle on the Patchwork instance. If PATCH_IDs are specified, this
219-
will overwrite all patches in the bundle. Use 'bundle add' and 'bundle
220-
remove' to add or remove patches.
218+
Update bundle BUNDLE_ID. If PATCH_IDs are specified, this will overwrite
219+
all patches in the bundle. Use 'bundle add' and 'bundle remove' to add or
220+
remove patches.
221221
222222
Requires API version 1.2 or greater.
223223
"""
@@ -258,3 +258,62 @@ def delete_cmd(bundle_id, fmt):
258258
LOG.debug('Delete bundle: id=%s', bundle_id)
259259

260260
api.delete('bundles', bundle_id)
261+
262+
263+
@click.command(name='add')
264+
@click.argument('bundle_id')
265+
@click.argument('patch_ids', type=click.INT, nargs=-1, required=True)
266+
@api.validate_minimum_version(
267+
(1, 2), 'Modifying bundles is only supported from API version 1.2',
268+
)
269+
@utils.format_options
270+
def add_cmd(bundle_id, patch_ids, fmt):
271+
"""Add one or more patches to a bundle.
272+
273+
Append the provided PATCH_IDS to bundle BUNDLE_ID.
274+
275+
Requires API version 1.2 or greater.
276+
"""
277+
LOG.debug('Add to bundle: id=%s, patches=%s', bundle_id, patch_ids)
278+
279+
bundle = _get_bundle(bundle_id)
280+
281+
data = [
282+
('patches', patch_ids + tuple([p['id'] for p in bundle['patches']])),
283+
]
284+
285+
bundle = api.update('bundles', bundle_id, data)
286+
287+
_show_bundle(bundle, fmt)
288+
289+
290+
@click.command(name='remove')
291+
@click.argument('bundle_id')
292+
@click.argument('patch_ids', type=click.INT, nargs=-1, required=True)
293+
@api.validate_minimum_version(
294+
(1, 2), 'Modifying bundles is only supported from API version 1.2',
295+
)
296+
@utils.format_options
297+
def remove_cmd(bundle_id, patch_ids, fmt):
298+
"""Remove one or more patches from a bundle.
299+
300+
Remove the provided PATCH_IDS to bundle BUNDLE_ID.
301+
302+
Requires API version 1.2 or greater.
303+
"""
304+
LOG.debug('Remove from bundle: id=%s, patches=%s', bundle_id, patch_ids)
305+
306+
bundle = _get_bundle(bundle_id)
307+
308+
patches = [p['id'] for p in bundle['patches'] if p['id'] not in patch_ids]
309+
if not patches:
310+
LOG.error(
311+
'Bundles cannot be empty. Consider deleting the bundle instead'
312+
)
313+
sys.exit(1)
314+
315+
data = [('patches', tuple(patches))]
316+
317+
bundle = api.update('bundles', bundle_id, data)
318+
319+
_show_bundle(bundle, fmt)

git_pw/shell.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,5 @@ def bundle():
126126
bundle.add_command(bundle_cmds.create_cmd)
127127
bundle.add_command(bundle_cmds.update_cmd)
128128
bundle.add_command(bundle_cmds.delete_cmd)
129+
bundle.add_command(bundle_cmds.add_cmd)
130+
bundle.add_command(bundle_cmds.remove_cmd)

releasenotes/notes/bundle-crud-47aadae6eb7a20ad.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ features:
66
- ``bundle create``
77
- ``bundle update``
88
- ``bundle delete``
9+
- ``bundle add``
10+
- ``bundle remove``
911
1012
Together, these allow for creation, modification and deletion of bundles.
1113
Bundles are custom, user-defined groups of patches that can be used to keep

tests/test_bundle.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,3 +456,108 @@ def test_delete_api_v1_1(
456456

457457
assert result.exit_code == 1, result
458458
assert mock_log.error.called
459+
460+
461+
@mock.patch('git_pw.api.version', return_value=(1, 2))
462+
@mock.patch('git_pw.api.update')
463+
@mock.patch('git_pw.api.detail')
464+
@mock.patch('git_pw.utils.echo_via_pager')
465+
class AddTestCase(unittest.TestCase):
466+
467+
@staticmethod
468+
def _get_bundle(**kwargs):
469+
return ShowTestCase._get_bundle(**kwargs)
470+
471+
def test_add(
472+
self, mock_echo, mock_detail, mock_update, mock_version,
473+
):
474+
"""Validate standard behavior."""
475+
476+
mock_detail.return_value = self._get_bundle()
477+
mock_update.return_value = self._get_bundle()
478+
479+
runner = CLIRunner()
480+
result = runner.invoke(bundle.add_cmd, ['1', '1', '2'])
481+
482+
assert result.exit_code == 0, result
483+
mock_detail.assert_called_once_with('bundles', '1')
484+
mock_update.assert_called_once_with(
485+
'bundles', '1', [('patches', (1, 2, 42))],
486+
)
487+
488+
@mock.patch('git_pw.api.LOG')
489+
def test_add_api_v1_1(
490+
self, mock_log, mock_echo, mock_detail, mock_update, mock_version,
491+
):
492+
"""Validate behavior with API v1.1."""
493+
494+
mock_version.return_value = (1, 1)
495+
496+
runner = CLIRunner()
497+
result = runner.invoke(bundle.add_cmd, ['1', '1', '2'])
498+
499+
assert result.exit_code == 1, result
500+
assert mock_log.error.called
501+
502+
503+
@mock.patch('git_pw.api.version', return_value=(1, 2))
504+
@mock.patch('git_pw.api.update')
505+
@mock.patch('git_pw.api.detail')
506+
@mock.patch('git_pw.utils.echo_via_pager')
507+
class RemoveTestCase(unittest.TestCase):
508+
509+
@staticmethod
510+
def _get_bundle(**kwargs):
511+
return ShowTestCase._get_bundle(**kwargs)
512+
513+
def test_remove(
514+
self, mock_echo, mock_detail, mock_update, mock_version,
515+
):
516+
"""Validate standard behavior."""
517+
518+
mock_detail.return_value = self._get_bundle(
519+
patches=[{'id': 1}, {'id': 2}, {'id': 3}],
520+
)
521+
mock_update.return_value = self._get_bundle()
522+
523+
runner = CLIRunner()
524+
result = runner.invoke(bundle.remove_cmd, ['1', '1', '2'])
525+
526+
assert result.exit_code == 0, result
527+
mock_detail.assert_called_once_with('bundles', '1')
528+
mock_update.assert_called_once_with(
529+
'bundles', '1', [('patches', (3,))],
530+
)
531+
532+
@mock.patch('git_pw.bundle.LOG')
533+
def test_remove_empty(
534+
self, mock_log, mock_echo, mock_detail, mock_update, mock_version,
535+
):
536+
"""Validate behavior when deleting would remove all patches."""
537+
538+
mock_detail.return_value = self._get_bundle(
539+
patches=[{'id': 1}, {'id': 2}, {'id': 3}],
540+
)
541+
mock_update.return_value = self._get_bundle()
542+
543+
runner = CLIRunner()
544+
result = runner.invoke(bundle.remove_cmd, ['1', '1', '2', '3'])
545+
546+
assert result.exit_code == 1, result.output
547+
assert mock_log.error.called
548+
mock_detail.assert_called_once_with('bundles', '1')
549+
mock_update.assert_not_called()
550+
551+
@mock.patch('git_pw.api.LOG')
552+
def test_remove_api_v1_1(
553+
self, mock_log, mock_echo, mock_detail, mock_update, mock_version,
554+
):
555+
"""Validate behavior with API v1.1."""
556+
557+
mock_version.return_value = (1, 1)
558+
559+
runner = CLIRunner()
560+
result = runner.invoke(bundle.remove_cmd, ['1', '1', '2'])
561+
562+
assert result.exit_code == 1, result
563+
assert mock_log.error.called

0 commit comments

Comments
 (0)