From: Simon Glass <simon.glass@canonical.com> Add a command to update the database with the last cherry-picked commit. This allows reviewing the cherry-picked branch before committing to the database. Usage: pickman commit-source us/next <commit-hash> Co-developed-by: Claude Opus 4.5 <noreply@anthropic.com> Signed-off-by: Simon Glass <simon.glass@canonical.com> --- tools/pickman/README.rst | 7 +++++ tools/pickman/__main__.py | 5 +++ tools/pickman/control.py | 36 ++++++++++++++++++++++ tools/pickman/ftest.py | 65 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 113 insertions(+) diff --git a/tools/pickman/README.rst b/tools/pickman/README.rst index ab37763a918..d15b15f3331 100644 --- a/tools/pickman/README.rst +++ b/tools/pickman/README.rst @@ -73,6 +73,13 @@ On successful cherry-pick, an entry is appended to ``.pickman-history`` with: This file is committed automatically and included in the MR description when using ``-p``. +After successfully applying commits, update the database to record progress:: + + ./tools/pickman/pickman commit-source us/next <commit-hash> + +This updates the last cherry-picked commit for the source branch, so subsequent +``next-set`` and ``apply`` commands will start from the new position. + Requirements ------------ diff --git a/tools/pickman/__main__.py b/tools/pickman/__main__.py index ac029a38382..1693af991ac 100755 --- a/tools/pickman/__main__.py +++ b/tools/pickman/__main__.py @@ -45,6 +45,11 @@ def parse_args(argv): apply_cmd.add_argument('-t', '--target', default='master', help='Target branch for MR (default: master)') + commit_src = subparsers.add_parser('commit-source', + help='Update database with last commit') + commit_src.add_argument('source', help='Source branch name') + commit_src.add_argument('commit', help='Commit hash to record') + subparsers.add_parser('compare', help='Compare branches') subparsers.add_parser('list-sources', help='List tracked source branches') diff --git a/tools/pickman/control.py b/tools/pickman/control.py index a482de85b00..fe840ee2740 100644 --- a/tools/pickman/control.py +++ b/tools/pickman/control.py @@ -401,6 +401,41 @@ def do_apply(args, dbs): # pylint: disable=too-many-locals return 0 if success else 1 +def do_commit_source(args, dbs): + """Update the database with the last cherry-picked commit + + Args: + args (Namespace): Parsed arguments with 'source' and 'commit' attributes + dbs (Database): Database instance + + Returns: + int: 0 on success, 1 on failure + """ + source = args.source + commit = args.commit + + # Resolve the commit to a full hash + try: + full_hash = run_git(['rev-parse', commit]) + except Exception: # pylint: disable=broad-except + tout.error(f"Could not resolve commit '{commit}'") + return 1 + + old_commit = dbs.source_get(source) + if not old_commit: + tout.error(f"Source '{source}' not found in database") + return 1 + + dbs.source_set(source, full_hash) + dbs.commit() + + short_old = old_commit[:12] + short_new = full_hash[:12] + tout.info(f"Updated '{source}': {short_old} -> {short_new}") + + return 0 + + def do_test(args, dbs): # pylint: disable=unused-argument """Run tests for this module. @@ -424,6 +459,7 @@ def do_test(args, dbs): # pylint: disable=unused-argument COMMANDS = { 'add-source': do_add_source, 'apply': do_apply, + 'commit-source': do_commit_source, 'compare': do_compare, 'list-sources': do_list_sources, 'next-set': do_next_set, diff --git a/tools/pickman/ftest.py b/tools/pickman/ftest.py index 4f5c90980c6..9b445173b3b 100644 --- a/tools/pickman/ftest.py +++ b/tools/pickman/ftest.py @@ -127,6 +127,13 @@ class TestParseArgs(unittest.TestCase): self.assertEqual(args.source, 'us/next') self.assertEqual(args.branch, 'my-branch') + def test_parse_commit_source(self): + """Test parsing commit-source command.""" + args = pickman.parse_args(['commit-source', 'us/next', 'abc123']) + self.assertEqual(args.cmd, 'commit-source') + self.assertEqual(args.source, 'us/next') + self.assertEqual(args.commit, 'abc123') + def test_parse_compare(self): """Test parsing compare command.""" args = pickman.parse_args(['compare']) @@ -942,6 +949,64 @@ class TestApply(unittest.TestCase): self.assertIn('No new commits to cherry-pick', stdout.getvalue()) +class TestCommitSource(unittest.TestCase): + """Tests for commit-source command.""" + + def setUp(self): + """Set up test fixtures.""" + fd, self.db_path = tempfile.mkstemp(suffix='.db') + os.close(fd) + os.unlink(self.db_path) + self.old_db_fname = control.DB_FNAME + control.DB_FNAME = self.db_path + database.Database.instances.clear() + + def tearDown(self): + """Clean up test fixtures.""" + control.DB_FNAME = self.old_db_fname + if os.path.exists(self.db_path): + os.unlink(self.db_path) + database.Database.instances.clear() + command.TEST_RESULT = None + + def test_commit_source_not_found(self): + """Test commit-source with unknown source.""" + with terminal.capture(): + dbs = database.Database(self.db_path) + dbs.start() + dbs.close() + + database.Database.instances.clear() + command.TEST_RESULT = command.CommandResult(stdout='fullhash123') + + args = argparse.Namespace(cmd='commit-source', source='unknown', + commit='abc123') + with terminal.capture() as (_, stderr): + ret = control.do_pickman(args) + self.assertEqual(ret, 1) + self.assertIn("Source 'unknown' not found", stderr.getvalue()) + + def test_commit_source_success(self): + """Test commit-source updates database.""" + with terminal.capture(): + dbs = database.Database(self.db_path) + dbs.start() + dbs.source_set('us/next', 'oldcommit12345') + dbs.commit() + dbs.close() + + database.Database.instances.clear() + command.TEST_RESULT = command.CommandResult(stdout='newcommit67890') + + args = argparse.Namespace(cmd='commit-source', source='us/next', + commit='abc123') + with terminal.capture() as (stdout, _): + ret = control.do_pickman(args) + self.assertEqual(ret, 0) + self.assertIn('oldcommit123', stdout.getvalue()) + self.assertIn('newcommit678', stdout.getvalue()) + + class TestParseUrl(unittest.TestCase): """Tests for parse_url function.""" -- 2.43.0