cli improvements (#222)

* feat: support annotate

* feat: extract supports --feature

* feat: support classify in dev cli
This commit is contained in:
Nikhil
2025-12-06 17:04:00 -08:00
committed by GitHub
parent 3f51d50081
commit ffb1dfdf2a
14 changed files with 1217 additions and 349 deletions

View File

@@ -170,6 +170,9 @@ def extract_commit(
base: Optional[str] = Option(
None, "--base", help="Extract full diff from base commit for files in COMMIT"
),
feature: bool = Option(
False, "--feature", help="Add extracted files to a feature in features.yaml"
),
):
"""Extract patches from a single commit"""
ctx = create_build_context(state.chromium_src)
@@ -190,6 +193,7 @@ def extract_commit(
force=force,
include_binary=include_binary,
base=base,
feature=feature,
)
except Exception as e:
log_error(f"Failed to extract commit: {e}")
@@ -201,6 +205,9 @@ def extract_patch_cmd(
chromium_path: str = Argument(..., help="Chromium file path (e.g., chrome/common/foo.h)"),
base: str = Option(..., "--base", "-b", help="Base commit to diff against"),
force: bool = Option(False, "--force", "-f", help="Overwrite existing patch without prompting"),
feature: bool = Option(
False, "--feature", help="Add extracted file to a feature in features.yaml"
),
):
"""Extract patch for a specific file"""
ctx = create_build_context(state.chromium_src)
@@ -215,6 +222,17 @@ def extract_patch_cmd(
raise typer.Exit(1)
log_success(f"Successfully extracted patch for: {chromium_path}")
# Handle --feature flag
if feature:
from ..modules.feature import prompt_feature_selection, add_files_to_feature
result = prompt_feature_selection(ctx, base[:12], None)
if result is None:
log_warning("Skipped adding file to feature")
else:
feature_name, description = result
add_files_to_feature(ctx, feature_name, description, [chromium_path])
@extract_app.command(name="range")
def extract_range(
@@ -232,6 +250,9 @@ def extract_range(
"--base",
help="Use different base for diff (full diff from base for files in range)",
),
feature: bool = Option(
False, "--feature", help="Add extracted files to a feature in features.yaml"
),
):
"""Extract patches from a range of commits"""
ctx = create_build_context(state.chromium_src)
@@ -254,6 +275,7 @@ def extract_range(
include_binary=include_binary,
squash=squash,
base=base,
feature=feature,
)
except Exception as e:
log_error(f"Failed to extract range: {e}")
@@ -266,10 +288,12 @@ def apply_all(
interactive: bool = Option(
True, "--interactive/--no-interactive", "-i/-n", help="Interactive mode"
),
commit: bool = Option(False, "--commit", "-c", help="Commit after each patch"),
reset_to: Optional[str] = Option(
None, "--reset-to", "-r", help="Reset files to this commit before applying patches"
),
annotate: bool = Option(
False, "--annotate", "-a", help="Create git commits per feature after applying"
),
):
"""Apply all patches from chromium_patches/"""
ctx = create_build_context(state.chromium_src)
@@ -281,7 +305,7 @@ def apply_all(
module = ApplyAllModule()
try:
module.validate(ctx)
module.execute(ctx, interactive=interactive, commit=commit, reset_to=reset_to)
module.execute(ctx, interactive=interactive, reset_to=reset_to, annotate=annotate)
except Exception as e:
log_error(f"Failed to apply patches: {e}")
raise typer.Exit(1)
@@ -293,10 +317,12 @@ def apply_feature(
interactive: bool = Option(
True, "--interactive/--no-interactive", "-i/-n", help="Interactive mode"
),
commit: bool = Option(False, "--commit", "-c", help="Commit after applying"),
reset_to: Optional[str] = Option(
None, "--reset-to", "-r", help="Reset files to this commit before applying patches"
),
annotate: bool = Option(
False, "--annotate", "-a", help="Create git commit for this feature after applying"
),
):
"""Apply patches for a specific feature"""
ctx = create_build_context(state.chromium_src)
@@ -309,7 +335,7 @@ def apply_feature(
try:
module.validate(ctx)
module.execute(
ctx, feature_name=feature_name, interactive=interactive, commit=commit, reset_to=reset_to
ctx, feature_name=feature_name, interactive=interactive, reset_to=reset_to, annotate=annotate
)
except Exception as e:
log_error(f"Failed to apply feature: {e}")
@@ -403,5 +429,61 @@ def feature_add(
raise typer.Exit(1)
@feature_app.command(name="classify")
def feature_classify():
"""Classify unclassified patch files into features
Lists all patches in chromium_patches/ that are not in any feature,
then prompts one-by-one to assign each to a feature.
Examples:
browseros dev feature classify
"""
ctx = create_build_context(state.chromium_src)
if not ctx:
raise typer.Exit(1)
from ..modules.feature import ClassifyFeaturesModule
module = ClassifyFeaturesModule()
try:
module.validate(ctx)
module.execute(ctx)
except Exception as e:
log_error(f"Failed to classify features: {e}")
raise typer.Exit(1)
# Annotate command
@app.command(name="annotate")
def annotate_cmd(
feature_name: Optional[str] = Argument(
None, help="Optional: specific feature to annotate (default: all features)"
),
):
"""Create git commits organized by features from features.yaml
For each feature with modified files, creates a commit with the format:
"{feature_name}: {description}"
Examples:
browseros dev annotate -S /path/to/chromium
browseros dev annotate llm-chat -S /path/to/chromium
"""
ctx = create_build_context(state.chromium_src)
if not ctx:
raise typer.Exit(1)
from ..modules.annotate import AnnotateModule
module = AnnotateModule()
try:
module.validate(ctx)
module.execute(ctx, feature_name=feature_name)
except Exception as e:
log_error(f"Failed to annotate: {e}")
raise typer.Exit(1)
if __name__ == "__main__":
app()

View File

@@ -1,358 +1,370 @@
version: "1.0"
version: '1.0'
features:
add-sparkle-info-plist-keys:
description: "patch: app-info.plist changes"
description: 'patch: app-info.plist changes'
files:
- chrome/app/app-Info.plist
- chrome/app/app-Info.plist
adding-new-vector-icons:
description: "patch: adding-new-vector-icons"
description: 'patch: adding-new-vector-icons'
files:
- components/vector_icons/BUILD.gn
- components/vector_icons/chat_orange.icon
- components/vector_icons/clash_of_gpts.icon
- components/vector_icons/BUILD.gn
- components/vector_icons/chat_orange.icon
- components/vector_icons/clash_of_gpts.icon
branding-file-updates:
description: browseros branding for file paths
files:
- chrome/common/chrome_constants.cc
- chrome/common/chrome_paths_linux.cc
- chrome/install_static/chromium_install_modes.cc
- chrome/install_static/chromium_install_modes.h
- components/os_crypt/sync/keychain_password_mac.mm
- chrome/common/chrome_constants.cc
- chrome/common/chrome_paths_linux.cc
- chrome/install_static/chromium_install_modes.cc
- chrome/install_static/chromium_install_modes.h
- components/os_crypt/sync/keychain_password_mac.mm
branding-resources:
description: browseros branding resources and assets
files:
- chrome/app/chromium_strings.grd
- chrome/app/settings_chromium_strings.grdp
- chrome/app/theme/chromium/BRANDING
- chrome/app/theme/chromium/chromeos/
- chrome/app/theme/chromium/chromium.ai
- chrome/app/theme/chromium/linux/
- chrome/app/theme/chromium/mac/
- chrome/app/theme/chromium/product_logo.ai
- chrome/app/theme/chromium/product_logo.svg
- chrome/app/theme/chromium/product_logo.png
- chrome/app/theme/chromium/product_logo_16.png
- chrome/app/theme/chromium/product_logo_22.png
- chrome/app/theme/chromium/product_logo_22_mono.png
- chrome/app/theme/chromium/product_logo_24.png
- chrome/app/theme/chromium/product_logo_32.png
- chrome/app/theme/chromium/product_logo_48.png
- chrome/app/theme/chromium/product_logo_64.png
- chrome/app/theme/chromium/product_logo_128.png
- chrome/app/theme/chromium/product_logo_192.png
- chrome/app/theme/chromium/product_logo_256.png
- chrome/app/theme/chromium/product_logo_animation.svg
- chrome/app/theme/chromium/product_logo_name_22.png
- chrome/app/theme/chromium/product_logo_name_22_2x.png
- chrome/app/theme/chromium/product_logo_name_22_white.png
- chrome/app/theme/chromium/product_logo_name_22_white_2x.png
- chrome/app/theme/chromium/win/
- chrome/app/theme/default_100_percent/chromium/
- chrome/app/theme/default_200_percent/chromium/
- chrome/enterprise_companion/branding.gni
- chrome/app/chromium_strings.grd
- chrome/app/settings_chromium_strings.grdp
- chrome/app/theme/
- chrome/enterprise_companion/branding.gni
ai-settings-page:
description: llm settings page
files:
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/prefs/browser_prefs.h
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.html
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.ts
- chrome/browser/resources/settings/route.ts
- chrome/browser/resources/settings/router.ts
- chrome/browser/resources/settings/settings.ts
- chrome/browser/resources/settings/settings_main/settings_main.html
- chrome/browser/resources/settings/settings_main/settings_main.ts
- chrome/browser/resources/settings/settings_menu/settings_menu.html
- chrome/common/pref_names.h
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/prefs/browser_prefs.h
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.html
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.ts
- chrome/browser/resources/settings/route.ts
- chrome/browser/resources/settings/router.ts
- chrome/browser/resources/settings/settings.ts
- chrome/browser/resources/settings/settings_main/settings_main.html
- chrome/browser/resources/settings/settings_main/settings_main.ts
- chrome/browser/resources/settings/settings_menu/settings_menu.html
- chrome/common/pref_names.h
api:
description: browseros API
files:
- chrome/browser/extensions/BUILD.gn
- chrome/browser/extensions/api/browser_os/browser_os_api.cc
- chrome/browser/extensions/api/browser_os/browser_os_api.h
- chrome/browser/extensions/api/browser_os/browser_os_api_helpers.cc
- chrome/browser/extensions/api/browser_os/browser_os_api_helpers.h
- chrome/browser/extensions/api/browser_os/browser_os_api_utils.cc
- chrome/browser/extensions/api/browser_os/browser_os_api_utils.h
- chrome/browser/extensions/api/browser_os/browser_os_change_detector.cc
- chrome/browser/extensions/api/browser_os/browser_os_change_detector.h
- chrome/browser/extensions/api/browser_os/browser_os_content_processor.cc
- chrome/browser/extensions/api/browser_os/browser_os_content_processor.h
- chrome/browser/extensions/api/browser_os/browser_os_snapshot_processor.cc
- chrome/browser/extensions/api/browser_os/browser_os_snapshot_processor.h
- chrome/browser/extensions/chrome_extensions_browser_api_provider.cc
- chrome/browser/media/extension_media_access_handler.cc
- chrome/common/extensions/api/_api_features.json
- chrome/common/extensions/api/_permission_features.json
- chrome/common/extensions/api/api_sources.gni
- chrome/common/extensions/api/browser_os.idl
- chrome/common/extensions/permissions/chrome_api_permissions.cc
- extensions/browser/extension_function_histogram_value.h
- extensions/common/mojom/api_permission_id.mojom
- tools/metrics/histograms/metadata/extensions/enums.xml
- chrome/browser/extensions/BUILD.gn
- chrome/browser/extensions/api/browser_os/browser_os_api.cc
- chrome/browser/extensions/api/browser_os/browser_os_api.h
- chrome/browser/extensions/api/browser_os/browser_os_api_helpers.cc
- chrome/browser/extensions/api/browser_os/browser_os_api_helpers.h
- chrome/browser/extensions/api/browser_os/browser_os_api_utils.cc
- chrome/browser/extensions/api/browser_os/browser_os_api_utils.h
- chrome/browser/extensions/api/browser_os/browser_os_change_detector.cc
- chrome/browser/extensions/api/browser_os/browser_os_change_detector.h
- chrome/browser/extensions/api/browser_os/browser_os_content_processor.cc
- chrome/browser/extensions/api/browser_os/browser_os_content_processor.h
- chrome/browser/extensions/api/browser_os/browser_os_snapshot_processor.cc
- chrome/browser/extensions/api/browser_os/browser_os_snapshot_processor.h
- chrome/browser/extensions/api/side_panel/side_panel_api.h
- chrome/browser/extensions/api/side_panel/side_panel_service.cc
- chrome/browser/extensions/api/side_panel/side_panel_service.h
- chrome/browser/extensions/chrome_extensions_browser_api_provider.cc
- chrome/browser/media/extension_media_access_handler.cc
- chrome/browser/ui/extensions/extension_side_panel_utils.h
- chrome/browser/ui/views/side_panel/extensions/extension_side_panel_utils.cc
- chrome/common/extensions/api/_api_features.json
- chrome/common/extensions/api/_permission_features.json
- chrome/common/extensions/api/api_sources.gni
- chrome/common/extensions/api/browser_os.idl
- chrome/common/extensions/api/side_panel.idl
- chrome/common/extensions/permissions/chrome_api_permissions.cc
- extensions/browser/extension_function_histogram_value.h
- extensions/common/mojom/api_permission_id.mojom
- tools/metrics/histograms/metadata/extensions/enums.xml
server:
description: browseros server
files:
- chrome/browser/browseros_server/
- base/threading/thread_restrictions.h
- base/threading/thread_restrictions.h
- chrome/browser/browseros_server/
- chrome/browser/browseros_server/BUILD.gn
- chrome/browser/browseros_server/browseros_server_manager.cc
- chrome/browser/browseros_server/browseros_server_manager.h
- chrome/browser/browseros_server/browseros_server_prefs.cc
- chrome/browser/browseros_server/browseros_server_prefs.h
- chrome/browser/browseros_server/validate_resources.py
metrics:
description: browseros metrics
files:
- chrome/browser/metrics/chrome_metrics_service_client.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
- chrome/browser/ui/BUILD.gn
- chrome/browser/ui/webui/settings/browseros_metrics_handler.cc
- chrome/browser/ui/webui/settings/browseros_metrics_handler.h
- chrome/browser/ui/webui/settings/settings_ui.cc
- chrome/common/pref_names.h
- components/metrics/browseros_metrics/BUILD.gn
- components/metrics/browseros_metrics/DEPS
- components/metrics/browseros_metrics/browseros_metrics.cc
- components/metrics/browseros_metrics/browseros_metrics.h
- components/metrics/browseros_metrics/browseros_metrics_prefs.cc
- components/metrics/browseros_metrics/browseros_metrics_prefs.h
- components/metrics/browseros_metrics/browseros_metrics_service.cc
- components/metrics/browseros_metrics/browseros_metrics_service.h
- components/metrics/browseros_metrics/browseros_metrics_service_factory.cc
- components/metrics/browseros_metrics/browseros_metrics_service_factory.h
- chrome/browser/metrics/chrome_metrics_service_client.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
- chrome/browser/ui/BUILD.gn
- chrome/browser/ui/webui/settings/browseros_metrics_handler.cc
- chrome/browser/ui/webui/settings/browseros_metrics_handler.h
- chrome/browser/ui/webui/settings/settings_ui.cc
- chrome/common/pref_names.h
- components/metrics/browseros_metrics/BUILD.gn
- components/metrics/browseros_metrics/DEPS
- components/metrics/browseros_metrics/browseros_metrics.cc
- components/metrics/browseros_metrics/browseros_metrics.h
- components/metrics/browseros_metrics/browseros_metrics_prefs.cc
- components/metrics/browseros_metrics/browseros_metrics_prefs.h
- components/metrics/browseros_metrics/browseros_metrics_service.cc
- components/metrics/browseros_metrics/browseros_metrics_service.h
- components/metrics/browseros_metrics/browseros_metrics_service_factory.cc
- components/metrics/browseros_metrics/browseros_metrics_service_factory.h
ota-updater:
description: extensions ota updater
files:
- chrome/browser/extensions/BUILD.gn
- chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/extensions/browseros_external_loader.cc
- chrome/browser/extensions/browseros_external_loader.h
- chrome/browser/extensions/chrome_extension_registrar_delegate.cc
- chrome/browser/extensions/extension_web_ui_override_registrar.cc
- chrome/browser/extensions/external_provider_impl.cc
- chrome/browser/ui/extensions/settings_overridden_params_providers.cc
- chrome/browser/extensions/BUILD.gn
- chrome/browser/extensions/api/developer_private/extension_info_generator_shared.cc
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/extensions/browseros_external_loader.cc
- chrome/browser/extensions/browseros_external_loader.h
- chrome/browser/extensions/chrome_extension_registrar_delegate.cc
- chrome/browser/extensions/extension_web_ui_override_registrar.cc
- chrome/browser/extensions/external_provider_impl.cc
- chrome/browser/ui/extensions/settings_overridden_params_providers.cc
chrome-importer:
description: chrome importer
files:
- chrome/app/generated_resources.grd
- chrome/app/settings_strings.grdp
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/importer/external_process_importer_client.cc
- chrome/browser/importer/external_process_importer_client.h
- chrome/browser/importer/importer_list.cc
- chrome/browser/importer/importer_uma.cc
- chrome/browser/importer/in_process_importer_bridge.cc
- chrome/browser/importer/in_process_importer_bridge.h
- chrome/browser/importer/profile_writer.cc
- chrome/browser/importer/profile_writer.h
- chrome/browser/resources/settings/people_page/import_data_browser_proxy.ts
- chrome/browser/resources/settings/people_page/import_data_dialog.html
- chrome/browser/ui/webui/settings/import_data_handler.cc
- chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
- chrome/browser/ui/webui/settings/settings_ui.cc
- chrome/common/importer/importer_bridge.h
- chrome/common/importer/importer_data_types.h
- chrome/common/importer/importer_type.h
- chrome/common/importer/profile_import.mojom
- chrome/common/importer/profile_import_process_param_traits_macros.h
- chrome/common/pref_names.h
- chrome/utility/BUILD.gn
- chrome/utility/importer/chrome_importer.cc
- chrome/utility/importer/chrome_importer.h
- chrome/utility/importer/external_process_importer_bridge.cc
- chrome/utility/importer/external_process_importer_bridge.h
- chrome/utility/importer/importer_creator.cc
- tools/metrics/histograms/metadata/sql/histograms.xml
- chrome/app/generated_resources.grd
- chrome/app/settings_strings.grdp
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/importer/external_process_importer_client.cc
- chrome/browser/importer/external_process_importer_client.h
- chrome/browser/importer/importer_list.cc
- chrome/browser/importer/importer_uma.cc
- chrome/browser/importer/in_process_importer_bridge.cc
- chrome/browser/importer/in_process_importer_bridge.h
- chrome/browser/importer/profile_writer.cc
- chrome/browser/importer/profile_writer.h
- chrome/browser/resources/settings/people_page/import_data_browser_proxy.ts
- chrome/browser/resources/settings/people_page/import_data_dialog.html
- chrome/browser/ui/webui/settings/import_data_handler.cc
- chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
- chrome/browser/ui/webui/settings/settings_ui.cc
- chrome/common/importer/importer_bridge.h
- chrome/common/importer/importer_data_types.h
- chrome/common/importer/importer_type.h
- chrome/common/importer/profile_import.mojom
- chrome/common/importer/profile_import_process_param_traits_macros.h
- chrome/common/pref_names.h
- chrome/utility/BUILD.gn
- chrome/utility/importer/chrome_importer.cc
- chrome/utility/importer/chrome_importer.h
- chrome/utility/importer/external_process_importer_bridge.cc
- chrome/utility/importer/external_process_importer_bridge.h
- chrome/utility/importer/importer_creator.cc
- components/user_data_importer/common/importer_data_types.h
- components/user_data_importer/common/importer_type.h
- tools/metrics/histograms/metadata/sql/histograms.xml
chrome-version-updater:
description: "patch: chrome version update"
description: 'patch: chrome version update'
files:
- chrome/VERSION
- chrome/VERSION
default-light-mode:
description: enable light mode as default theme
files:
- chrome/browser/themes/theme_service_factory.cc
- chrome/browser/themes/theme_service_factory.cc
disable-chrome-labs-pinning:
description: "patch: disable-chrome-labs-pinning"
description: 'patch: disable-chrome-labs-pinning'
files:
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
disable-google-key-info-bar:
description: "patch: disable-google-key-info-bar"
description: 'patch: disable-google-key-info-bar'
files:
- chrome/browser/ui/startup/google_api_keys_infobar_delegate.cc
- chrome/browser/ui/startup/google_api_keys_infobar_delegate.cc
disable-info-bar-in-cdp:
description: "patch: disable-info-bar-in-cdp"
description: 'patch: disable-info-bar-in-cdp'
files:
- chrome/browser/extensions/api/debugger/debugger_api.cc
- chrome/browser/extensions/api/debugger/debugger_api.cc
disable-sidepanel-animation:
description: disable sidepanel animation
files:
- chrome/browser/ui/views/side_panel/side_panel.cc
- chrome/browser/ui/views/side_panel/side_panel.h
- chrome/browser/ui/views/side_panel/side_panel.cc
- chrome/browser/ui/views/side_panel/side_panel.h
disable-user-gesture-restriction-on-sidepanel:
description: "patch: disable-user-gesture-restriction-on-sidepanel"
description: 'patch: disable-user-gesture-restriction-on-sidepanel'
files:
- chrome/browser/extensions/api/side_panel/side_panel_api.cc
- chrome/browser/extensions/api/side_panel/side_panel_api.cc
first-run:
description: first run
files:
- chrome/browser/chrome_browser_main.cc
- chrome/browser/ui/webui/chrome_web_ui_configs.cc
- chrome/browser/ui/webui/nxtscape_first_run.h
- chrome/common/webui_url_constants.cc
- chrome/browser/chrome_browser_main.cc
- chrome/browser/ui/webui/chrome_web_ui_configs.cc
- chrome/browser/ui/webui/nxtscape_first_run.h
- chrome/common/webui_url_constants.cc
llm-chat:
description: llm chat and updates
files:
- chrome/app/chrome_command_ids.h
- chrome/app/generated_resources.grd
- chrome/browser/global_keyboard_shortcuts_mac.mm
- chrome/browser/ui/actions/chrome_action_id.h
- chrome/browser/ui/browser_actions.cc
- chrome/browser/ui/browser_command_controller.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/ui_features.cc
- chrome/browser/ui/ui_features.h
- chrome/browser/ui/views/accelerator_table.cc
- chrome/browser/ui/views/side_panel/BUILD.gn
- chrome/browser/ui/views/side_panel/browseros_simple_page_extractor.cc
- chrome/browser/ui/views/side_panel/browseros_simple_page_extractor.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.cc
- chrome/browser/ui/views/side_panel/side_panel_entry_id.h
- chrome/browser/ui/views/side_panel/side_panel_prefs.cc
- chrome/browser/ui/views/side_panel/side_panel_util.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_panel_coordinator.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_panel_coordinator.h
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.h
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar.mojom
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
- chrome/app/chrome_command_ids.h
- chrome/app/generated_resources.grd
- chrome/browser/global_keyboard_shortcuts_mac.mm
- chrome/browser/ui/actions/chrome_action_id.h
- chrome/browser/ui/browser_actions.cc
- chrome/browser/ui/browser_command_controller.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/ui_features.cc
- chrome/browser/ui/ui_features.h
- chrome/browser/ui/views/accelerator_table.cc
- chrome/browser/ui/views/side_panel/BUILD.gn
- chrome/browser/ui/views/side_panel/browseros_simple_page_extractor.cc
- chrome/browser/ui/views/side_panel/browseros_simple_page_extractor.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.cc
- chrome/browser/ui/views/side_panel/side_panel_entry_id.h
- chrome/browser/ui/views/side_panel/side_panel_prefs.cc
- chrome/browser/ui/views/side_panel/side_panel_util.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_panel_coordinator.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_panel_coordinator.h
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.cc
- chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.h
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar.mojom
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
llm-hub:
description: llm-hub
files:
- chrome/app/chrome_command_ids.h
- chrome/app/generated_resources.grd
- chrome/browser/global_keyboard_shortcuts_mac.mm
- chrome/browser/ui/actions/chrome_action_id.h
- chrome/browser/ui/browser_actions.cc
- chrome/browser/ui/browser_command_controller.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/ui_features.cc
- chrome/browser/ui/ui_features.h
- chrome/browser/ui/views/accelerator_table.cc
- chrome/browser/ui/views/side_panel/BUILD.gn
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_view.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_view.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_window.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_window.h
- chrome/browser/ui/views/side_panel/side_panel_entry_id.h
- chrome/browser/ui/views/side_panel/side_panel_prefs.cc
- chrome/browser/ui/views/side_panel/side_panel_util.cc
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/webui/BUILD.gn
- chrome/browser/ui/webui/chrome_web_ui_configs.cc
- chrome/browser/ui/webui/clash_of_gpts/clash_of_gpts_ui.cc
- chrome/browser/ui/webui/clash_of_gpts/clash_of_gpts_ui.h
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar.mojom
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
- chrome/common/webui_url_constants.h
- chrome/app/chrome_command_ids.h
- chrome/app/generated_resources.grd
- chrome/browser/global_keyboard_shortcuts_mac.mm
- chrome/browser/ui/actions/chrome_action_id.h
- chrome/browser/ui/browser_actions.cc
- chrome/browser/ui/browser_command_controller.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/ui_features.cc
- chrome/browser/ui/ui_features.h
- chrome/browser/ui/views/accelerator_table.cc
- chrome/browser/ui/views/side_panel/BUILD.gn
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_coordinator.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_view.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_view.h
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_window.cc
- chrome/browser/ui/views/side_panel/clash_of_gpts/clash_of_gpts_window.h
- chrome/browser/ui/views/side_panel/side_panel_entry_id.h
- chrome/browser/ui/views/side_panel/side_panel_prefs.cc
- chrome/browser/ui/views/side_panel/side_panel_util.cc
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/webui/BUILD.gn
- chrome/browser/ui/webui/chrome_web_ui_configs.cc
- chrome/browser/ui/webui/clash_of_gpts/clash_of_gpts_ui.cc
- chrome/browser/ui/webui/clash_of_gpts/clash_of_gpts_ui.h
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar.mojom
- chrome/browser/ui/webui/side_panel/customize_chrome/customize_toolbar/customize_toolbar_handler.cc
- chrome/common/webui_url_constants.h
llm-settings-page-updates:
description: "llm settings page: updates"
description: 'llm settings page: updates'
files:
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/nxtscape_page/models_data.html
- chrome/browser/resources/settings/nxtscape_page/models_data.ts
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.html
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.ts
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/nxtscape_page/models_data.html
- chrome/browser/resources/settings/nxtscape_page/models_data.ts
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.html
- chrome/browser/resources/settings/nxtscape_page/nxtscape_page.ts
mac-sparkle-updater:
description: "patch: nxtscape-updater-sparkle"
description: 'patch: nxtscape-updater-sparkle'
files:
- chrome/BUILD.gn
- chrome/browser/BUILD.gn
- chrome/browser/mac/chrome_browser_main_extra_parts_mac.h
- chrome/browser/mac/chrome_browser_main_extra_parts_mac.mm
- chrome/browser/mac/sparkle_glue.h
- chrome/browser/mac/sparkle_glue.mm
- chrome/browser/mac/su_updater.h
- chrome/browser/sparkle_buildflags.gni
- chrome/browser/ui/BUILD.gn
- chrome/browser/ui/webui/help/sparkle_version_updater_mac.h
- chrome/browser/ui/webui/help/sparkle_version_updater_mac.mm
- chrome/browser/ui/webui/help/version_updater_mac.mm
- third_party/sparkle/
- chrome/BUILD.gn
- chrome/browser/BUILD.gn
- chrome/browser/mac/chrome_browser_main_extra_parts_mac.h
- chrome/browser/mac/chrome_browser_main_extra_parts_mac.mm
- chrome/browser/mac/sparkle_glue.h
- chrome/browser/mac/sparkle_glue.mm
- chrome/browser/mac/su_updater.h
- chrome/browser/sparkle_buildflags.gni
- chrome/browser/ui/BUILD.gn
- chrome/browser/ui/webui/help/sparkle_version_updater_mac.h
- chrome/browser/ui/webui/help/sparkle_version_updater_mac.mm
- chrome/browser/ui/webui/help/version_updater_mac.mm
- third_party/sparkle/
pin-chat-and-hub:
description: pin browseros native panels
files:
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
- chrome/browser/ui/actions/browseros_actions_config.h
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.cc
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.h
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/sync/prefs/chrome_syncable_prefs_database.cc
- chrome/browser/ui/actions/browseros_actions_config.h
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.cc
- chrome/browser/ui/toolbar/pinned_toolbar/pinned_toolbar_actions_model.h
- chrome/browser/ui/toolbar/toolbar_pref_names.cc
- chrome/browser/ui/toolbar/toolbar_pref_names.h
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/views/toolbar/pinned_toolbar_actions_container.cc
pin-extensions-toolbar:
description: pin browseros extensions to extension toolbar
files:
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/extensions/extension_context_menu_model.cc
- chrome/browser/extensions/extension_management.cc
- chrome/browser/ui/actions/browseros_actions_config.h
- chrome/browser/ui/toolbar/toolbar_actions_model.cc
- chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc
- chrome/browser/extensions/browseros_extension_constants.h
- chrome/browser/extensions/extension_context_menu_model.cc
- chrome/browser/extensions/extension_management.cc
- chrome/browser/ui/actions/browseros_actions_config.h
- chrome/browser/ui/toolbar/toolbar_actions_model.cc
- chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.cc
- chrome/browser/ui/views/side_panel/side_panel_action_callback.cc
- chrome/browser/ui/views/side_panel/side_panel_action_callback.h
preferences-settings-page:
description: "patch: settings prefs page"
description: 'patch: settings prefs page'
files:
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/browseros_prefs_page/browseros_prefs_page.html
- chrome/browser/resources/settings/browseros_prefs_page/browseros_prefs_page.ts
- chrome/browser/resources/settings/route.ts
- chrome/browser/resources/settings/router.ts
- chrome/browser/resources/settings/settings.ts
- chrome/browser/resources/settings/settings_main/settings_main.html
- chrome/browser/resources/settings/settings_main/settings_main.ts
- chrome/browser/resources/settings/settings_menu/settings_menu.html
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.h
- chrome/common/pref_names.h
- chrome/browser/extensions/api/settings_private/prefs_util.cc
- chrome/browser/prefs/browser_prefs.cc
- chrome/browser/resources/settings/BUILD.gn
- chrome/browser/resources/settings/browseros_prefs_page/browseros_prefs_page.html
- chrome/browser/resources/settings/browseros_prefs_page/browseros_prefs_page.ts
- chrome/browser/resources/settings/route.ts
- chrome/browser/resources/settings/router.ts
- chrome/browser/resources/settings/settings.ts
- chrome/browser/resources/settings/settings_main/settings_main.html
- chrome/browser/resources/settings/settings_main/settings_main.ts
- chrome/browser/resources/settings/settings_menu/settings_menu.html
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.cc
- chrome/browser/ui/views/toolbar/pinned_action_toolbar_button.h
- chrome/common/pref_names.h
ui-fixes:
description: "patch: chromium ui fixes"
description: 'patch: chromium ui fixes'
files:
- chrome/browser/chrome_content_browser_client.cc
- chrome/browser/net/profile_network_context_service.cc
- chrome/browser/resources/settings/about_page/about_page.html
- chrome/browser/resources/settings/about_page/about_page.ts
- chrome/browser/resources/settings/reset_page/reset_profile_dialog.html
- chrome/browser/ui/browser_ui_prefs.cc
- chrome/browser/ui/views/chrome_layout_provider.cc
- chrome/browser/ui/views/infobars/infobar_container_view.cc
- chrome/browser/ui/views/new_tab_footer/
- chrome/browser/ui/webui/new_tab_footer/
- components/bookmarks/browser/bookmark_utils.cc
- components/content_settings/core/browser/cookie_settings.cc
- components/payments/core/payment_prefs.cc
- components/performance_manager/user_tuning/prefs.cc
- components/search/ntp_features.cc
- chrome/browser/about_flags.cc
- chrome/browser/browser_features.cc
- chrome/browser/browser_features.h
- chrome/browser/chrome_content_browser_client.cc
- chrome/browser/net/profile_network_context_service.cc
- chrome/browser/resources/settings/about_page/about_page.html
- chrome/browser/resources/settings/about_page/about_page.ts
- chrome/browser/resources/settings/reset_page/reset_profile_dialog.html
- chrome/browser/ui/browser_ui_prefs.cc
- chrome/browser/ui/views/chrome_layout_provider.cc
- chrome/browser/ui/views/infobars/infobar_container_view.cc
- chrome/browser/ui/views/new_tab_footer/
- chrome/browser/ui/views/new_tab_footer/footer_controller.cc
- chrome/browser/ui/webui/new_tab_footer/
- chrome/browser/ui/webui/new_tab_footer/new_tab_footer_ui.cc
- components/bookmarks/browser/bookmark_utils.cc
- components/content_settings/core/browser/cookie_settings.cc
- components/payments/core/payment_prefs.cc
- components/performance_manager/user_tuning/prefs.cc
- components/search/ntp_features.cc
browseros-version:
description: "patch: browseros version"
description: 'patch: browseros version'
files:
- base/version_info/BUILD.gn
- base/version_info/version_info.h
- base/version_info/version_info_values.h.version
- chrome/BROWSEROS_VERSION
- chrome/browser/resources/settings/about_page/about_page.html
- chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
- base/version_info/BUILD.gn
- base/version_info/version_info.h
- base/version_info/version_info_values.h.version
- chrome/BROWSEROS_VERSION
- chrome/browser/resources/settings/about_page/about_page.html
- chrome/browser/ui/webui/settings/settings_localized_strings_provider.cc
misc:
description: miscellaneous patches
files:
- chrome/browser/ui/omnibox/chrome_omnibox_client.cc
- chrome/browser/ui/profiles/profile_error_dialog.cc
- chrome/browser/ui/startup/infobar_utils.cc
- chrome/installer/mini_installer/chrome.release
- chrome/updater/branding.gni
- extensions/browser/process_manager.cc
- extensions/browser/process_manager.h
- third_party/blink/renderer/core/frame/navigator.cc
- chrome/browser/browseros_server/.gitignore
- chrome/browser/ui/omnibox/chrome_omnibox_client.cc
- chrome/browser/ui/profiles/profile_error_dialog.cc
- chrome/browser/ui/startup/infobar_utils.cc
- chrome/installer/mini_installer/chrome.release
- chrome/updater/branding.gni
- extensions/browser/process_manager.cc
- extensions/browser/process_manager.h
- third_party/blink/renderer/core/frame/navigator.cc
flags:
description: adding new flags or udpating existing
files:
- chrome/browser/flag_descriptions.cc
- chrome/browser/flag_descriptions.h
- chrome/browser/ui/browser_window/internal/browser_window_features.cc
- chrome/browser/ui/browser_window/public/browser_window_features.h
keyboard-shorcuts:
description: keyboard shortcuts
files:
- chrome/browser/ui/accelerator_table.cc
mac-sparkle:
description: mac sparkle updater
files:
- third_party/sparkle/BUILD.gn

View File

@@ -0,0 +1,15 @@
"""
Annotate module - Create git commits organized by features.
Provides commands for creating feature-based commits:
- annotate_features: Create commits for all features with modified files
- annotate_single_feature: Create a commit for a specific feature
"""
from .annotate import annotate_features, annotate_single_feature, AnnotateModule
__all__ = [
"annotate_features",
"annotate_single_feature",
"AnnotateModule",
]

View File

@@ -0,0 +1,239 @@
"""
Annotate - Create git commits organized by features from features.yaml
For each feature, checks which files have modifications and creates a commit
with the feature name and description.
"""
import yaml
from pathlib import Path
from typing import List, Tuple, Optional, Dict
from ..apply.utils import run_git_command
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
from ...common.utils import log_info, log_error, log_success, log_warning
def load_features(features_file: Path) -> Dict:
"""Load features from YAML file."""
try:
with open(features_file, "r") as f:
data = yaml.safe_load(f)
return data.get("features", {})
except Exception as e:
log_error(f"Failed to load features file: {e}")
return {}
def get_modified_files(chromium_src: Path, files: List[str]) -> List[str]:
"""Get list of files that have modifications or are untracked.
Args:
chromium_src: Chromium source directory
files: List of file paths to check
Returns:
List of file paths that have modifications
"""
modified = []
for file_path in files:
full_path = chromium_src / file_path
if not full_path.exists():
continue
result = run_git_command(
["git", "status", "--porcelain", str(file_path)],
cwd=chromium_src,
)
if result.returncode == 0 and result.stdout.strip():
modified.append(file_path)
return modified
def git_add_and_commit(
chromium_src: Path, files: List[str], commit_message: str
) -> bool:
"""Add files and create commit.
Args:
chromium_src: Chromium source directory
files: List of file paths to add
commit_message: Commit message
Returns:
True if commit was created successfully
"""
# Add all specified files
for file_path in files:
result = run_git_command(
["git", "add", str(file_path)],
cwd=chromium_src,
)
if result.returncode != 0:
log_error(f"Failed to add file: {file_path}")
return False
# Create commit
result = run_git_command(
["git", "commit", "-m", commit_message],
cwd=chromium_src,
)
if result.returncode != 0:
stderr = result.stderr or ""
if "nothing to commit" in stderr or "nothing added to commit" in stderr:
return False
log_error(f"Failed to commit: {stderr}")
return False
return True
def annotate_features(
ctx: Context,
feature_filter: Optional[str] = None,
) -> Tuple[int, int]:
"""Create commits for features with modified files.
Iterates through features.yaml and creates a commit for each feature
that has modified files in the working tree.
Args:
ctx: Build context
feature_filter: If specified, only process this feature
Returns:
Tuple of (commits_created, features_skipped)
"""
features_file = ctx.get_features_yaml_path()
if not features_file.exists():
log_error(f"Features file not found: {features_file}")
return 0, 0
features = load_features(features_file)
if not features:
log_error("No features found in features.yaml")
return 0, 0
# Filter to specific feature if requested
if feature_filter:
if feature_filter not in features:
log_error(f"Feature '{feature_filter}' not found in features.yaml")
return 0, 0
features = {feature_filter: features[feature_filter]}
log_info(f"📋 Processing {len(features)} feature(s)")
log_info("=" * 60)
commits_created = 0
features_skipped = 0
for feature_name, feature_data in features.items():
description = feature_data.get("description", feature_name)
files = feature_data.get("files", [])
log_info(f"\n🔧 {feature_name}")
log_info(f" {description}")
if not files:
log_warning(" No files specified, skipping")
features_skipped += 1
continue
# Find files with modifications
modified_files = get_modified_files(ctx.chromium_src, files)
if not modified_files:
log_warning(f" No modified files ({len(files)} files checked)")
features_skipped += 1
continue
log_info(f" Found {len(modified_files)} modified file(s)")
# Create commit
commit_message = f"{feature_name}: {description}"
if git_add_and_commit(ctx.chromium_src, modified_files, commit_message):
log_success(f" ✓ Committed {len(modified_files)} file(s)")
commits_created += 1
else:
log_warning(" No changes staged, skipping commit")
features_skipped += 1
return commits_created, features_skipped
def annotate_single_feature(
ctx: Context,
feature_name: str,
) -> bool:
"""Create a commit for a single feature.
Args:
ctx: Build context
feature_name: Name of the feature to commit
Returns:
True if commit was created successfully
"""
commits, _ = annotate_features(ctx, feature_filter=feature_name)
return commits > 0
class AnnotateModule(CommandModule):
"""Create git commits organized by features from features.yaml"""
produces = []
requires = []
description = "Create git commits organized by features"
def validate(self, ctx: Context) -> None:
"""Validate git is available and chromium_src exists."""
import shutil
if not shutil.which("git"):
raise ValidationError("Git is not available in PATH")
if not ctx.chromium_src.exists():
raise ValidationError(f"Chromium source not found: {ctx.chromium_src}")
# Check if it's a git repository
git_dir = ctx.chromium_src / ".git"
if not git_dir.exists():
raise ValidationError(f"Not a git repository: {ctx.chromium_src}")
def execute(
self,
ctx: Context,
feature_name: Optional[str] = None,
**kwargs,
) -> None:
"""Execute annotate.
Args:
ctx: Build context
feature_name: If specified, only annotate this feature
"""
log_info("🏗️ Annotate Features")
log_info("=" * 60)
log_info(f"📁 Chromium source: {ctx.chromium_src}")
log_info(f"📄 Features file: {ctx.get_features_yaml_path()}")
commits_created, features_skipped = annotate_features(
ctx, feature_filter=feature_name
)
log_info("\n" + "=" * 60)
if commits_created > 0:
log_success(f"✓ Created {commits_created} commit(s)")
else:
log_info("No commits created (no modified files found)")
if features_skipped > 0:
log_info(f" Skipped {features_skipped} feature(s) with no changes")
log_info("=" * 60)

View File

@@ -6,13 +6,12 @@ from typing import List, Tuple, Optional
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
from ...common.utils import log_info, log_error, log_warning
from ...common.utils import log_info, log_error, log_warning, log_success
from .common import find_patch_files, process_patch_list
def apply_all_patches(
build_ctx: Context,
commit_each: bool = False,
dry_run: bool = False,
interactive: bool = False,
reset_to: Optional[str] = None,
@@ -21,7 +20,6 @@ def apply_all_patches(
Args:
build_ctx: Build context
commit_each: Create a commit after each patch
dry_run: Only check if patches would apply
interactive: Ask for confirmation before each patch
reset_to: Commit to reset files to before applying (optional)
@@ -55,7 +53,6 @@ def apply_all_patches(
patch_list,
build_ctx.chromium_src,
patches_dir,
commit_each,
dry_run,
interactive,
reset_to=reset_to,
@@ -92,23 +89,34 @@ class ApplyAllModule(CommandModule):
self,
ctx: Context,
interactive: bool = True,
commit: bool = False,
reset_to: Optional[str] = None,
annotate: bool = False,
**kwargs,
) -> None:
"""Execute apply all patches
Args:
interactive: Interactive mode (ask before each patch)
commit: Create git commit after each patch
reset_to: Commit to reset files to before applying (optional)
annotate: Create git commits per feature after applying
"""
applied, failed = apply_all_patches(
ctx,
commit_each=commit,
dry_run=False,
interactive=interactive,
reset_to=reset_to,
)
if failed:
raise RuntimeError(f"Failed to apply {len(failed)} patches")
# Run annotate if requested
if annotate:
from ..annotate import annotate_features
log_info("\n" + "=" * 60)
log_info("🏗️ Creating feature-based commits...")
commits, skipped = annotate_features(ctx)
if commits > 0:
log_success(f"✓ Created {commits} commit(s)")
else:
log_info("No commits created (no modified files found)")

View File

@@ -7,14 +7,13 @@ from typing import List, Tuple, Optional
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
from ...common.utils import log_info, log_error, log_warning
from ...common.utils import log_info, log_error, log_warning, log_success
from .common import process_patch_list
def apply_feature_patches(
build_ctx: Context,
feature_name: str,
commit_each: bool = False,
dry_run: bool = False,
reset_to: Optional[str] = None,
) -> Tuple[int, List[str]]:
@@ -23,7 +22,6 @@ def apply_feature_patches(
Args:
build_ctx: Build context
feature_name: Name of the feature
commit_each: Create a commit after each patch
dry_run: Only check if patches would apply
reset_to: Commit to reset files to before applying (optional)
@@ -71,10 +69,8 @@ def apply_feature_patches(
patch_list,
build_ctx.chromium_src,
patches_dir,
commit_each,
dry_run,
interactive=False, # Feature patches don't support interactive mode
feature_name=feature_name,
reset_to=reset_to,
)
@@ -110,8 +106,8 @@ class ApplyFeatureModule(CommandModule):
ctx: Context,
feature_name: str,
interactive: bool = True,
commit: bool = False,
reset_to: Optional[str] = None,
annotate: bool = False,
**kwargs,
) -> None:
"""Execute apply feature patches
@@ -119,13 +115,12 @@ class ApplyFeatureModule(CommandModule):
Args:
feature_name: Name of the feature to apply
interactive: Interactive mode (ask before each patch)
commit: Create git commit after applying
reset_to: Commit to reset files to before applying (optional)
annotate: Create git commit for this feature after applying
"""
applied, failed = apply_feature_patches(
ctx,
feature_name,
commit_each=commit,
dry_run=False,
reset_to=reset_to,
)
@@ -133,3 +128,14 @@ class ApplyFeatureModule(CommandModule):
raise RuntimeError(
f"Failed to apply {len(failed)} patches for feature '{feature_name}'"
)
# Run annotate for this specific feature if requested
if annotate:
from ..annotate import annotate_single_feature
log_info("\n" + "=" * 60)
log_info(f"🏗️ Creating commit for feature '{feature_name}'...")
if annotate_single_feature(ctx, feature_name):
log_success(f"✓ Created commit for '{feature_name}'")
else:
log_info("No commit created (no modified files found)")

View File

@@ -153,10 +153,8 @@ def process_patch_list(
patch_list: List[Tuple[Path, str]],
chromium_src: Path,
patches_dir: Path,
commit_each: bool = False,
dry_run: bool = False,
interactive: bool = False,
feature_name: Optional[str] = None,
reset_to: Optional[str] = None,
) -> Tuple[int, List[str]]:
"""Process a list of patches.
@@ -165,10 +163,8 @@ def process_patch_list(
patch_list: List of (patch_path, display_name) tuples
chromium_src: Chromium source directory
patches_dir: Base directory for relative path display
commit_each: Create a commit after each patch
dry_run: Only check if patches would apply
interactive: Ask for confirmation before each patch
feature_name: Optional feature name for commit messages
reset_to: Commit to reset files to before applying (optional)
Returns:
@@ -218,8 +214,6 @@ def process_patch_list(
if success:
applied += 1
if commit_each and not dry_run:
create_patch_commit(display_name, chromium_src, feature_name)
else:
failed.append(display_name)

View File

@@ -6,7 +6,7 @@ Contains core extraction logic used by extract_commit and extract_range.
import click
from pathlib import Path
from typing import Dict, Optional
from typing import Dict, List, Optional, Tuple
from ...common.context import Context
from ...common.utils import log_info, log_error, log_warning
@@ -50,11 +50,16 @@ def write_patches(
file_patches: Dict[str, FilePatch],
verbose: bool,
include_binary: bool,
) -> int:
"""Write patches to disk"""
) -> Tuple[int, List[str]]:
"""Write patches to disk.
Returns:
Tuple of (success_count, list of successfully extracted file paths)
"""
success_count = 0
fail_count = 0
skip_count = 0
extracted_files: List[str] = []
for file_path, patch in file_patches.items():
if verbose:
@@ -66,6 +71,7 @@ def write_patches(
# Create deletion marker
if create_deletion_marker(ctx, file_path):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
@@ -74,6 +80,7 @@ def write_patches(
# Create binary marker
if create_binary_marker(ctx, file_path, patch.operation):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
else:
@@ -86,6 +93,7 @@ def write_patches(
# If there are changes beyond the rename
if write_patch_file(ctx, file_path, patch.patch_content):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
else:
@@ -98,6 +106,7 @@ def write_patches(
marker_path.write_text(marker_content)
log_info(f" Rename marked: {file_path}")
success_count += 1
extracted_files.append(file_path)
except Exception as e:
log_error(f" Failed to mark rename: {e}")
fail_count += 1
@@ -107,6 +116,7 @@ def write_patches(
if patch.patch_content:
if write_patch_file(ctx, file_path, patch.patch_content):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
else:
@@ -121,7 +131,7 @@ def write_patches(
if skip_count > 0:
log_info(f"Skipped {skip_count} files")
return success_count
return success_count, extracted_files
def extract_normal(
@@ -130,8 +140,12 @@ def extract_normal(
verbose: bool,
force: bool,
include_binary: bool,
) -> int:
"""Extract patches normally (diff against parent)"""
) -> Tuple[int, List[str]]:
"""Extract patches normally (diff against parent).
Returns:
Tuple of (count, list of extracted file paths)
"""
from .utils import GitError
# Get diff against parent
@@ -149,11 +163,11 @@ def extract_normal(
if not file_patches:
log_warning("No changes found in commit")
return 0
return 0, []
# Check for existing patches
if not force and not check_overwrite(ctx, file_patches, verbose):
return 0
return 0, []
# Write patches
return write_patches(ctx, file_patches, verbose, include_binary)
@@ -166,15 +180,19 @@ def extract_with_base(
verbose: bool,
force: bool,
include_binary: bool,
) -> int:
"""Extract patches with custom base (full diff from base for files in commit)"""
) -> Tuple[int, List[str]]:
"""Extract patches with custom base (full diff from base for files in commit).
Returns:
Tuple of (count, list of extracted file paths)
"""
# Step 1: Get list of files changed in the commit
changed_files = get_commit_changed_files(commit_hash, ctx.chromium_src)
if not changed_files:
log_warning(f"No files changed in commit {commit_hash}")
return 0
return 0, []
if verbose:
log_info(f"Files changed in {commit_hash}: {len(changed_files)}")
@@ -243,13 +261,13 @@ def extract_with_base(
if not file_patches:
log_warning("No patches to extract")
return 0
return 0, []
log_info(f"Extracting {len(file_patches)} patches with base {base}")
# Check for existing patches
if not force and not check_overwrite(ctx, file_patches, verbose):
return 0
return 0, []
# Write patches
return write_patches(ctx, file_patches, verbose, include_binary)

View File

@@ -3,7 +3,7 @@ Extract Commit - Extract patches from a single git commit.
"""
from pathlib import Path
from typing import Optional
from typing import List, Optional, Tuple
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
@@ -24,7 +24,7 @@ def extract_single_commit(
force: bool = False,
include_binary: bool = False,
base: Optional[str] = None,
) -> int:
) -> Tuple[int, List[str]]:
"""Extract patches from a single commit
Args:
@@ -36,7 +36,7 @@ def extract_single_commit(
base: If provided, extract full diff from base for files in commit
Returns:
Number of patches successfully extracted
Tuple of (count, list of extracted file paths)
"""
# Step 1: Validate commit
if not validate_commit_exists(commit_hash, ctx.chromium_src):
@@ -82,6 +82,7 @@ class ExtractCommitModule(CommandModule):
force: bool = False,
include_binary: bool = False,
base: Optional[str] = None,
feature: bool = False,
) -> None:
"""Execute extract commit
@@ -93,9 +94,10 @@ class ExtractCommitModule(CommandModule):
force: Overwrite existing patches
include_binary: Include binary files
base: Extract full diff from base commit for files in COMMIT
feature: Prompt to add extracted files to a feature in features.yaml
"""
try:
count = extract_single_commit(
count, extracted_files = extract_single_commit(
ctx,
commit_hash=commit,
verbose=verbose,
@@ -107,5 +109,28 @@ class ExtractCommitModule(CommandModule):
log_warning(f"No patches extracted from {commit}")
else:
log_success(f"Successfully extracted {count} patches from {commit}")
# Handle --feature flag
if feature and extracted_files:
self._add_to_feature(ctx, commit, extracted_files)
except GitError as e:
raise RuntimeError(f"Git error: {e}")
def _add_to_feature(self, ctx: Context, commit: str, files: List[str]) -> None:
"""Prompt user to add extracted files to a feature."""
from ..feature import prompt_feature_selection, add_files_to_feature
from .utils import get_commit_info
# Get commit info for context
commit_info = get_commit_info(commit, ctx.chromium_src)
commit_message = commit_info.get("subject") if commit_info else None
# Prompt for feature selection
result = prompt_feature_selection(ctx, commit[:12], commit_message)
if result is None:
log_warning("Skipped adding files to feature")
return
feature_name, description = result
add_files_to_feature(ctx, feature_name, description, files)

View File

@@ -4,7 +4,7 @@ Extract Range - Extract patches from a range of git commits.
import click
from pathlib import Path
from typing import Optional
from typing import List, Optional, Tuple
from ...common.context import Context
from ...common.module import CommandModule, ValidationError
@@ -33,11 +33,11 @@ def extract_commit_range(
force: bool = False,
include_binary: bool = False,
custom_base: Optional[str] = None,
) -> int:
) -> Tuple[int, List[str]]:
"""Extract patches from a commit range as a single cumulative diff
Returns:
Number of patches successfully extracted
Tuple of (count, list of extracted file paths)
"""
# Step 1: Validate commits
if not validate_commit_exists(base_commit, ctx.chromium_src):
@@ -56,7 +56,7 @@ def extract_commit_range(
if commit_count == 0:
log_warning(f"No commits between {base_commit} and {head_commit}")
return 0
return 0, []
log_info(f"Processing {commit_count} commits")
@@ -80,7 +80,7 @@ def extract_commit_range(
if not changed_files:
log_warning("No files changed in range")
return 0
return 0, []
log_info(f"Found {len(changed_files)} files changed in range")
@@ -107,15 +107,16 @@ def extract_commit_range(
if not file_patches:
log_warning("No changes found in commit range")
return 0
return 0, []
# Check for existing patches
if not force and not check_overwrite(ctx, file_patches, verbose):
return 0
return 0, []
success_count = 0
fail_count = 0
skip_count = 0
extracted_files: List[str] = []
# Process with progress indicator
with click.progressbar(
@@ -129,6 +130,7 @@ def extract_commit_range(
if patch.operation == FileOperation.DELETE:
if create_deletion_marker(ctx, file_path):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
@@ -136,6 +138,7 @@ def extract_commit_range(
if include_binary:
if create_binary_marker(ctx, file_path, patch.operation):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
else:
@@ -144,6 +147,7 @@ def extract_commit_range(
elif patch.patch_content:
if write_patch_file(ctx, file_path, patch.patch_content):
success_count += 1
extracted_files.append(file_path)
else:
fail_count += 1
else:
@@ -157,7 +161,7 @@ def extract_commit_range(
if skip_count > 0:
log_info(f"Skipped {skip_count} files")
return success_count
return success_count, extracted_files
def extract_commits_individually(
@@ -168,13 +172,13 @@ def extract_commits_individually(
force: bool = False,
include_binary: bool = False,
custom_base: Optional[str] = None,
) -> int:
) -> Tuple[int, List[str]]:
"""Extract patches from each commit in a range individually
This preserves commit boundaries and can help with conflict resolution.
Returns:
Total number of patches successfully extracted
Tuple of (count, list of extracted file paths)
"""
# Validate custom base if provided
if custom_base and not validate_commit_exists(custom_base, ctx.chromium_src):
@@ -193,13 +197,14 @@ def extract_commits_individually(
if not commits:
log_warning(f"No commits between {base_commit} and {head_commit}")
return 0
return 0, []
log_info(f"Extracting patches from {len(commits)} commits individually")
if custom_base:
log_info(f"Using custom base: {custom_base}")
total_extracted = 0
all_extracted_files: List[str] = []
failed_commits = []
with click.progressbar(
@@ -209,7 +214,7 @@ def extract_commits_individually(
try:
if custom_base:
# Use extract_with_base for full diff from custom base
extracted = extract_with_base(
extracted, files = extract_with_base(
ctx,
commit,
custom_base,
@@ -219,7 +224,7 @@ def extract_commits_individually(
)
else:
# Normal extraction from parent
extracted = extract_single_commit(
extracted, files = extract_single_commit(
ctx,
commit,
verbose=False,
@@ -227,6 +232,7 @@ def extract_commits_individually(
include_binary=include_binary,
)
total_extracted += extracted
all_extracted_files.extend(files)
except GitError as e:
failed_commits.append((commit, str(e)))
if verbose:
@@ -239,7 +245,9 @@ def extract_commits_individually(
if len(failed_commits) > 5:
log_warning(f" ... and {len(failed_commits) - 5} more")
return total_extracted
# Deduplicate files (same file may appear in multiple commits)
unique_files = list(dict.fromkeys(all_extracted_files))
return total_extracted, unique_files
class ExtractRangeModule(CommandModule):
@@ -268,6 +276,7 @@ class ExtractRangeModule(CommandModule):
include_binary: bool = False,
squash: bool = False,
base: Optional[str] = None,
feature: bool = False,
) -> None:
"""Execute extract range
@@ -281,10 +290,11 @@ class ExtractRangeModule(CommandModule):
include_binary: Include binary files
squash: Squash all commits into single patches
base: Use different base for diff (full diff from base for files in range)
feature: Prompt to add extracted files to a feature in features.yaml
"""
try:
if squash:
count = extract_commit_range(
count, extracted_files = extract_commit_range(
ctx,
base_commit=start,
head_commit=end,
@@ -294,7 +304,7 @@ class ExtractRangeModule(CommandModule):
custom_base=base,
)
else:
count = extract_commits_individually(
count, extracted_files = extract_commits_individually(
ctx,
base_commit=start,
head_commit=end,
@@ -307,5 +317,28 @@ class ExtractRangeModule(CommandModule):
log_warning(f"No patches extracted from range {start}..{end}")
else:
log_success(f"Successfully extracted {count} patches from {start}..{end}")
# Handle --feature flag
if feature and extracted_files:
self._add_to_feature(ctx, end, extracted_files)
except GitError as e:
raise RuntimeError(f"Git error: {e}")
def _add_to_feature(self, ctx: Context, commit: str, files: List[str]) -> None:
"""Prompt user to add extracted files to a feature."""
from ..feature import prompt_feature_selection, add_files_to_feature
from .utils import get_commit_info
# Get commit info for context (use the end commit)
commit_info = get_commit_info(commit, ctx.chromium_src)
commit_message = commit_info.get("subject") if commit_info else None
# Prompt for feature selection
result = prompt_feature_selection(ctx, commit[:12], commit_message)
if result is None:
log_warning("Skipped adding files to feature")
return
feature_name, description = result
add_files_to_feature(ctx, feature_name, description, files)

View File

@@ -5,6 +5,9 @@ Provides commands for managing features:
- add_feature: Add files from a commit to a feature
- list_features: List all defined features
- show_feature: Show details of a specific feature
- prompt_feature_selection: Interactive feature selection for extract commands
- add_files_to_feature: Add files to a feature (with duplicate handling)
- classify_files: Classify unclassified patch files into features
"""
from .feature import (
@@ -14,6 +17,13 @@ from .feature import (
ListFeaturesModule,
show_feature,
ShowFeatureModule,
ClassifyFeaturesModule,
)
from .select import (
prompt_feature_selection,
add_files_to_feature,
classify_files,
get_unclassified_files,
)
__all__ = [
@@ -23,4 +33,9 @@ __all__ = [
"ListFeaturesModule",
"show_feature",
"ShowFeatureModule",
"ClassifyFeaturesModule",
"prompt_feature_selection",
"add_files_to_feature",
"classify_files",
"get_unclassified_files",
]

View File

@@ -151,4 +151,32 @@ class AddFeatureModule(CommandModule):
def execute(self, ctx: Context, feature_name: str, commit: str, description: Optional[str] = None, **kwargs) -> None:
success = add_feature(ctx, feature_name, commit, description)
if not success:
raise RuntimeError(f"Failed to add feature '{feature_name}'")
raise RuntimeError(f"Failed to add feature '{feature_name}'")
class ClassifyFeaturesModule(CommandModule):
"""Classify unclassified patch files into features"""
produces = []
requires = []
description = "Classify unclassified patch files into features"
def validate(self, ctx: Context) -> None:
"""Validate patches directory exists"""
patches_dir = ctx.get_patches_dir()
if not patches_dir.exists():
raise ValidationError(f"Patches directory not found: {patches_dir}")
def execute(self, ctx: Context, **kwargs) -> None:
from .select import classify_files, get_unclassified_files
# Show summary first
unclassified = get_unclassified_files(ctx)
if not unclassified:
log_success("All patch files are already classified!")
return
log_info(f"Found {len(unclassified)} unclassified patch file(s)")
log_info("")
# Run classification
classified, skipped = classify_files(ctx)

View File

@@ -0,0 +1,396 @@
"""
Feature selection utilities for interactive feature assignment.
Provides functions to prompt users to select or create features
and add files to them.
"""
import yaml
from pathlib import Path
from typing import List, Optional, Dict, Tuple, Set
from ...common.context import Context
from ...common.utils import log_info, log_success, log_warning, log_error
def load_features_yaml(features_file: Path) -> Dict:
"""Load features from YAML file."""
if not features_file.exists():
return {"version": "1.0", "features": {}}
with open(features_file, "r") as f:
content = yaml.safe_load(f)
if not content:
return {"version": "1.0", "features": {}}
return content
def save_features_yaml(features_file: Path, data: Dict) -> None:
"""Save features to YAML file."""
with open(features_file, "w") as f:
yaml.safe_dump(data, f, sort_keys=False, default_flow_style=False)
def prompt_feature_selection(
ctx: Context,
commit_hash: Optional[str] = None,
commit_message: Optional[str] = None,
) -> Optional[Tuple[str, str]]:
"""Prompt user to select an existing feature or create a new one.
Args:
ctx: Build context
commit_hash: Optional commit hash for display
commit_message: Optional commit message for display/defaults
Returns:
Tuple of (feature_name, description) or None if cancelled
"""
features_file = ctx.get_features_yaml_path()
data = load_features_yaml(features_file)
features = data.get("features", {})
# Display commit info if available
if commit_hash or commit_message:
log_info("")
log_info("=" * 60)
if commit_hash:
log_info(f"Commit: {commit_hash[:12]}")
if commit_message:
log_info(f"Message: {commit_message}")
log_info("=" * 60)
# Display numbered list of features
log_info("")
log_info("Select a feature to add files to:")
log_info("-" * 40)
feature_list = list(features.keys())
for i, name in enumerate(feature_list, 1):
desc = features[name].get("description", name)
file_count = len(features[name].get("files", []))
log_info(f" {i}) {desc} ({file_count} files)")
# Add "new feature" option
new_option = len(feature_list) + 1
log_info(f" {new_option}) [Add new feature]")
log_info("")
# Get user selection
while True:
try:
choice = input(f"Enter choice (1-{new_option}): ").strip()
if not choice:
log_warning("Cancelled")
return None
choice_num = int(choice)
if choice_num < 1 or choice_num > new_option:
log_warning(f"Please enter a number between 1 and {new_option}")
continue
break
except ValueError:
log_warning("Please enter a valid number")
continue
except (KeyboardInterrupt, EOFError):
log_warning("\nCancelled")
return None
# Handle selection
if choice_num == new_option:
# Create new feature
return prompt_new_feature(commit_message)
else:
# Selected existing feature
feature_name = feature_list[choice_num - 1]
description = features[feature_name].get("description", "")
return (feature_name, description)
def prompt_new_feature(default_description: Optional[str] = None) -> Optional[Tuple[str, str]]:
"""Prompt user to create a new feature.
Args:
default_description: Optional default description (e.g., from commit message)
Returns:
Tuple of (feature_name, description) or None if cancelled
"""
log_info("")
log_info("Creating new feature:")
log_info("-" * 40)
try:
# Get feature name
feature_name = input("Feature name: ").strip()
if not feature_name:
log_warning("Cancelled - no feature name provided")
return None
# Sanitize feature name (lowercase, hyphens instead of spaces)
feature_name = feature_name.lower().replace(" ", "-")
# Get description
if default_description:
desc_prompt = f"Description [{default_description}]: "
else:
desc_prompt = "Description: "
description = input(desc_prompt).strip()
if not description and default_description:
description = default_description
if not description:
description = f"Feature: {feature_name}"
return (feature_name, description)
except (KeyboardInterrupt, EOFError):
log_warning("\nCancelled")
return None
def add_files_to_feature(
ctx: Context,
feature_name: str,
description: str,
files: List[str],
) -> int:
"""Add files to a feature in features.yaml, avoiding duplicates.
Args:
ctx: Build context
feature_name: Name of the feature
description: Feature description
files: List of file paths to add
Returns:
Number of new files added (excludes duplicates)
"""
features_file = ctx.get_features_yaml_path()
data = load_features_yaml(features_file)
if "features" not in data:
data["features"] = {}
features = data["features"]
# Get or create feature entry
if feature_name in features:
existing_files = set(features[feature_name].get("files", []))
# Keep existing description if present
if not features[feature_name].get("description"):
features[feature_name]["description"] = description
else:
existing_files = set()
features[feature_name] = {
"description": description,
"files": [],
}
# Add new files, avoiding duplicates
new_files = []
duplicate_files = []
for file_path in files:
if file_path in existing_files:
duplicate_files.append(file_path)
else:
new_files.append(file_path)
existing_files.add(file_path)
# Update feature with merged file list
features[feature_name]["files"] = sorted(existing_files)
# Save to file
save_features_yaml(features_file, data)
# Log results
if new_files:
log_success(f"Added {len(new_files)} file(s) to feature '{feature_name}'")
for f in new_files[:5]:
log_info(f" + {f}")
if len(new_files) > 5:
log_info(f" ... and {len(new_files) - 5} more")
if duplicate_files:
log_warning(f"Skipped {len(duplicate_files)} duplicate file(s)")
for f in duplicate_files[:3]:
log_info(f" ~ {f}")
if len(duplicate_files) > 3:
log_info(f" ... and {len(duplicate_files) - 3} more")
return len(new_files)
def get_all_patch_files(ctx: Context) -> List[str]:
"""Get all patch files from chromium_patches/ directory.
Returns:
List of file paths (relative to chromium_patches/)
"""
patches_dir = ctx.get_patches_dir()
if not patches_dir.exists():
return []
patch_files = []
for patch_path in patches_dir.rglob("*"):
if patch_path.is_file():
# Get relative path from patches_dir
rel_path = str(patch_path.relative_to(patches_dir))
patch_files.append(rel_path)
return sorted(patch_files)
def get_all_classified_files(ctx: Context) -> Set[str]:
"""Get all files that are already classified in features.yaml.
Returns:
Set of file paths
"""
features_file = ctx.get_features_yaml_path()
data = load_features_yaml(features_file)
features = data.get("features", {})
classified = set()
for feature_data in features.values():
files = feature_data.get("files", [])
classified.update(files)
return classified
def get_unclassified_files(ctx: Context) -> List[str]:
"""Get list of patch files not in any feature.
Returns:
List of unclassified file paths
"""
all_patches = set(get_all_patch_files(ctx))
classified = get_all_classified_files(ctx)
unclassified = all_patches - classified
return sorted(unclassified)
def classify_files(ctx: Context) -> Tuple[int, int]:
"""Interactively classify unclassified patch files into features.
Goes through each unclassified file one-by-one and prompts user
to select a feature or create a new one.
Returns:
Tuple of (files_classified, files_skipped)
"""
unclassified = get_unclassified_files(ctx)
if not unclassified:
log_success("All patch files are already classified!")
return 0, 0
log_info(f"Found {len(unclassified)} unclassified file(s)")
log_info("=" * 60)
log_info("Press Ctrl+C to stop at any time")
log_info("")
classified_count = 0
skipped_count = 0
for i, file_path in enumerate(unclassified, 1):
log_info(f"\n[{i}/{len(unclassified)}] {file_path}")
log_info("-" * 40)
try:
# Prompt for feature selection (no commit context for classify)
result = prompt_feature_selection_for_file(ctx, file_path)
if result is None:
log_warning("Skipped")
skipped_count += 1
continue
feature_name, description = result
add_files_to_feature(ctx, feature_name, description, [file_path])
classified_count += 1
except KeyboardInterrupt:
log_info("\n\nStopped by user")
break
log_info("")
log_info("=" * 60)
log_success(f"Classified {classified_count} file(s)")
if skipped_count > 0:
log_info(f"Skipped {skipped_count} file(s)")
remaining = len(unclassified) - classified_count - skipped_count
if remaining > 0:
log_info(f"Remaining: {remaining} file(s)")
return classified_count, skipped_count
def prompt_feature_selection_for_file(
ctx: Context,
file_path: str,
) -> Optional[Tuple[str, str]]:
"""Prompt user to select a feature for a single file.
Simplified version of prompt_feature_selection for classify workflow.
Args:
ctx: Build context
file_path: The file being classified
Returns:
Tuple of (feature_name, description) or None if skipped
"""
features_file = ctx.get_features_yaml_path()
data = load_features_yaml(features_file)
features = data.get("features", {})
if not features:
log_info("No features defined yet. Create a new one:")
return prompt_new_feature()
# Display numbered list of features
feature_list = list(features.keys())
for i, name in enumerate(feature_list, 1):
desc = features[name].get("description", name)
file_count = len(features[name].get("files", []))
log_info(f" {i}) {desc} ({file_count} files)")
# Add options
new_option = len(feature_list) + 1
skip_option = len(feature_list) + 2
log_info(f" {new_option}) [Add new feature]")
log_info(f" {skip_option}) [Skip this file]")
# Get user selection
while True:
try:
choice = input(f"Choice (1-{skip_option}): ").strip()
if not choice:
return None
choice_num = int(choice)
if choice_num < 1 or choice_num > skip_option:
log_warning(f"Please enter 1-{skip_option}")
continue
break
except ValueError:
log_warning("Enter a valid number")
continue
except (KeyboardInterrupt, EOFError):
raise KeyboardInterrupt
# Handle selection
if choice_num == skip_option:
return None
elif choice_num == new_option:
return prompt_new_feature()
else:
feature_name = feature_list[choice_num - 1]
description = features[feature_name].get("description", "")
return (feature_name, description)

View File

@@ -24,13 +24,11 @@ class PatchesModule(CommandModule):
def execute(self, ctx: Context) -> None:
log_info("\n🩹 Applying patches...")
if not apply_patches_impl(ctx, interactive=False, commit_each=False):
if not apply_patches_impl(ctx, interactive=False):
raise RuntimeError("Failed to apply patches")
def apply_patches_impl(
ctx: Context, interactive: bool = False, commit_each: bool = False
) -> bool:
def apply_patches_impl(ctx: Context, interactive: bool = False) -> bool:
"""Apply patches using the dev CLI patch system
Returns:
@@ -53,7 +51,6 @@ def apply_patches_impl(
# Call the dev CLI function directly
_, failed = apply_all_patches(
build_ctx=ctx,
commit_each=commit_each,
dry_run=False,
interactive=interactive,
)