mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-21 12:55:09 +00:00
942 lines
29 KiB
Markdown
942 lines
29 KiB
Markdown
# Dev CLI Design Document
|
|
|
|
## 1. Overview
|
|
|
|
### 1.1 Purpose
|
|
The Dev CLI is a patch management system for Chromium development that acts like git but specifically for managing patches against the Chromium codebase. It enables developers to:
|
|
- Extract patches from git commits in a Chromium repository
|
|
- Store patches as individual file diffs
|
|
- Track which files belong to which features
|
|
- Apply patches to new Chromium versions with conflict resolution
|
|
- Manage the upgrade process when moving to newer Chromium versions
|
|
|
|
### 1.2 Key Design Principles
|
|
- **Simplicity**: Mirror git's command structure and workflow
|
|
- **Transparency**: Store patches as readable diff files
|
|
- **Flexibility**: Support both individual file and feature-based operations
|
|
- **Robustness**: Handle conflicts gracefully during Chromium upgrades
|
|
|
|
## 2. Architecture
|
|
|
|
### 2.1 Directory Structure
|
|
```
|
|
nxtscape/
|
|
├── build/
|
|
│ ├── dev.py # Main CLI entry point
|
|
│ ├── context.py # Shared BuildContext (used by both build.py and dev.py)
|
|
│ └── modules/
|
|
│ └── dev_cli/ # Dev CLI modules
|
|
│ ├── __init__.py
|
|
│ ├── extract.py # Extract commands (extract, extract-range)
|
|
│ ├── apply.py # Apply commands (apply --all, apply --feature)
|
|
│ ├── feature.py # Feature management (add, list, show, generate-patch)
|
|
│ └── utils.py # Shared utilities (diff parsing, git commands)
|
|
├── chromium_src/ # Individual file patches (mirrors Chromium structure)
|
|
│ ├── chrome/
|
|
│ │ ├── app/
|
|
│ │ │ └── chrome_command_ids.h.patch
|
|
│ │ └── browser/
|
|
│ │ └── ui/
|
|
│ │ ├── ui_features.h.patch
|
|
│ │ └── views/
|
|
│ │ └── side_panel/
|
|
│ │ └── third_party_llm/
|
|
│ │ ├── third_party_llm_panel_coordinator.cc.patch
|
|
│ │ └── third_party_llm_view.cc.patch # New file (diff against /dev/null)
|
|
│ └── ...
|
|
├── features.yaml # Feature to file mapping
|
|
└── patches/ # Legacy patches directory (existing)
|
|
└── browseros/
|
|
└── llm-chat.patch # Combined patch for reference
|
|
```
|
|
|
|
### 2.2 Module Structure
|
|
|
|
Each module in `build/modules/dev_cli/` is self-contained and registers its own Click commands:
|
|
|
|
#### 2.2.1 Module Organization
|
|
- **extract.py**: Handles extracting patches from git commits
|
|
- **apply.py**: Handles applying patches with conflict resolution
|
|
- **feature.py**: Manages feature-to-file mappings
|
|
- **utils.py**: Shared utilities for git operations, diff parsing, etc.
|
|
|
|
#### 2.2.2 BuildContext Integration
|
|
The Dev CLI reuses the existing `BuildContext` from `build/context.py` with additional methods:
|
|
|
|
```python
|
|
# In context.py - extended for dev CLI
|
|
class BuildContext:
|
|
# ... existing code ...
|
|
|
|
def get_dev_patches_dir(self) -> Path:
|
|
"""Get individual patches directory (chromium_src/)"""
|
|
return join_paths(self.root_dir, "chromium_src")
|
|
|
|
def get_features_yaml_path(self) -> Path:
|
|
"""Get features.yaml file path"""
|
|
return join_paths(self.root_dir, "features.yaml")
|
|
|
|
def get_patch_path_for_file(self, file_path: str) -> Path:
|
|
"""Convert a chromium file path to patch file path"""
|
|
return join_paths(self.get_dev_patches_dir(), f"{file_path}.patch")
|
|
```
|
|
|
|
### 2.2 File Formats
|
|
|
|
#### 2.2.1 Patch Files (.patch)
|
|
Standard unified diff format (git diff output):
|
|
```diff
|
|
--- a/chrome/app/chrome_command_ids.h
|
|
+++ b/chrome/app/chrome_command_ids.h
|
|
@@ -290,6 +290,8 @@
|
|
#define IDC_SHOW_HISTORY_SIDE_PANEL 40293
|
|
#define IDC_OPEN_GLIC 40294
|
|
#define IDC_FIND_EXTENSIONS 40295
|
|
+#define IDC_SHOW_THIRD_PARTY_LLM_SIDE_PANEL 40296
|
|
+#define IDC_CYCLE_THIRD_PARTY_LLM_PROVIDER 40297
|
|
```
|
|
|
|
New files use /dev/null as the source:
|
|
```diff
|
|
--- /dev/null
|
|
+++ b/chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.cc
|
|
@@ -0,0 +1,11 @@
|
|
+// Copyright 2024 The Chromium Authors
|
|
+// ... file contents ...
|
|
```
|
|
|
|
#### 2.2.2 Features YAML
|
|
```yaml
|
|
version: 1.0
|
|
features:
|
|
llm-chat:
|
|
description: "LLM chat integration in side panel"
|
|
files:
|
|
- chrome/app/chrome_command_ids.h
|
|
- chrome/browser/ui/ui_features.h
|
|
- 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_view.cc
|
|
|
|
another-feature:
|
|
description: "Another feature description"
|
|
files:
|
|
- chrome/app/chrome_command_ids.h # Note: can be in multiple features
|
|
- chrome/browser/some_other_file.cc
|
|
```
|
|
|
|
## 3. Implementation Structure
|
|
|
|
### 3.1 Main Entry Point (build/dev.py)
|
|
|
|
The main entry point uses Click's group mechanism to register subcommands from modules:
|
|
|
|
```python
|
|
#!/usr/bin/env python3
|
|
"""Dev CLI for Chromium patch management"""
|
|
|
|
import click
|
|
from pathlib import Path
|
|
from context import BuildContext
|
|
from modules.dev_cli import extract, apply, feature
|
|
from utils import log_info, log_error
|
|
|
|
@click.group()
|
|
@click.option('--chromium-src', '-S', type=click.Path(exists=True, path_type=Path),
|
|
help='Path to Chromium source directory')
|
|
@click.pass_context
|
|
def cli(ctx, chromium_src):
|
|
"""Dev CLI - Chromium patch management tool"""
|
|
# Store chromium_src in context for subcommands
|
|
ctx.ensure_object(dict)
|
|
ctx.obj['chromium_src'] = chromium_src or None
|
|
|
|
# Register subcommand groups from modules
|
|
cli.add_command(extract.extract_group)
|
|
cli.add_command(apply.apply_group)
|
|
cli.add_command(feature.feature_group)
|
|
|
|
if __name__ == '__main__':
|
|
cli()
|
|
```
|
|
|
|
### 3.2 Module Implementation Examples
|
|
|
|
#### 3.2.1 Extract Module (build/modules/dev_cli/extract.py)
|
|
|
|
```python
|
|
import click
|
|
from pathlib import Path
|
|
from context import BuildContext
|
|
from modules.dev_cli.utils import run_git_command, parse_diff_output
|
|
from utils import log_info, log_error, log_success
|
|
|
|
@click.group(name='extract')
|
|
def extract_group():
|
|
"""Extract patches from git commits"""
|
|
pass
|
|
|
|
@extract_group.command(name='commit')
|
|
@click.argument('commit')
|
|
@click.pass_context
|
|
def extract_commit(ctx, commit):
|
|
"""Extract patches from a single commit"""
|
|
chromium_src = ctx.obj.get('chromium_src')
|
|
if not chromium_src:
|
|
log_error("--chromium-src is required")
|
|
return
|
|
|
|
# Create BuildContext
|
|
build_ctx = BuildContext(
|
|
root_dir=Path.cwd(),
|
|
chromium_src=chromium_src,
|
|
architecture="", # Not needed for patch operations
|
|
build_type="debug" # Not needed for patch operations
|
|
)
|
|
|
|
if extract_single_commit(build_ctx, commit):
|
|
log_success(f"Successfully extracted patches from {commit}")
|
|
else:
|
|
log_error(f"Failed to extract patches from {commit}")
|
|
|
|
@extract_group.command(name='range')
|
|
@click.argument('base_commit')
|
|
@click.argument('head_commit')
|
|
@click.pass_context
|
|
def extract_range(ctx, base_commit, head_commit):
|
|
"""Extract patches from a range of commits"""
|
|
# Similar implementation...
|
|
|
|
def extract_single_commit(ctx: BuildContext, commit_hash: str) -> bool:
|
|
"""Implementation of single commit extraction"""
|
|
# Implementation as detailed in section 4
|
|
pass
|
|
```
|
|
|
|
#### 3.2.2 Apply Module (build/modules/dev_cli/apply.py)
|
|
|
|
```python
|
|
import click
|
|
from pathlib import Path
|
|
from context import BuildContext
|
|
from modules.dev_cli.utils import apply_single_patch
|
|
from utils import log_info, log_error, log_success
|
|
|
|
@click.group(name='apply')
|
|
def apply_group():
|
|
"""Apply patches to Chromium source"""
|
|
pass
|
|
|
|
@apply_group.command(name='all')
|
|
@click.option('--commit-each', is_flag=True, help='Create git commit after each patch')
|
|
@click.option('--dry-run', is_flag=True, help='Test patches without applying')
|
|
@click.pass_context
|
|
def apply_all(ctx, commit_each, dry_run):
|
|
"""Apply all patches from chromium_src/"""
|
|
chromium_src = ctx.obj.get('chromium_src')
|
|
if not chromium_src:
|
|
log_error("--chromium-src is required")
|
|
return
|
|
|
|
build_ctx = BuildContext(
|
|
root_dir=Path.cwd(),
|
|
chromium_src=chromium_src,
|
|
architecture="",
|
|
build_type="debug"
|
|
)
|
|
|
|
apply_all_patches(build_ctx, commit_each, dry_run)
|
|
|
|
@apply_group.command(name='feature')
|
|
@click.argument('feature_name')
|
|
@click.option('--commit-each', is_flag=True, help='Create git commit after each patch')
|
|
@click.option('--dry-run', is_flag=True, help='Test patches without applying')
|
|
@click.pass_context
|
|
def apply_feature(ctx, feature_name, commit_each, dry_run):
|
|
"""Apply patches for a specific feature"""
|
|
# Implementation...
|
|
```
|
|
|
|
#### 3.2.3 Feature Module (build/modules/dev_cli/feature.py)
|
|
|
|
```python
|
|
import click
|
|
import yaml
|
|
from pathlib import Path
|
|
from context import BuildContext
|
|
from utils import log_info, log_error, log_success
|
|
|
|
@click.group(name='feature')
|
|
def feature_group():
|
|
"""Manage feature-to-file mappings"""
|
|
pass
|
|
|
|
@feature_group.command(name='add')
|
|
@click.argument('feature_name')
|
|
@click.argument('commit')
|
|
@click.pass_context
|
|
def add_feature(ctx, feature_name, commit):
|
|
"""Add files from a commit to a feature"""
|
|
# Implementation...
|
|
|
|
@feature_group.command(name='list')
|
|
@click.pass_context
|
|
def list_features(ctx):
|
|
"""List all features"""
|
|
build_ctx = BuildContext(
|
|
root_dir=Path.cwd(),
|
|
chromium_src=Path.cwd(), # Not used for listing
|
|
architecture="",
|
|
build_type="debug"
|
|
)
|
|
|
|
features = load_features_yaml(build_ctx)
|
|
for name, data in features.items():
|
|
file_count = len(data.get('files', []))
|
|
description = data.get('description', 'No description')
|
|
log_info(f" {name} ({file_count} files) - {description}")
|
|
|
|
@feature_group.command(name='show')
|
|
@click.argument('feature_name')
|
|
@click.pass_context
|
|
def show_feature(ctx, feature_name):
|
|
"""Show details of a specific feature"""
|
|
# Implementation...
|
|
```
|
|
|
|
## 4. Command Specifications
|
|
|
|
### 4.1 Extract Commands
|
|
|
|
#### 4.1.1 `extract commit` - Extract patches from a single commit
|
|
```bash
|
|
dev-cli --chromium-src <path> extract commit <commit-hash>
|
|
# or
|
|
dev-cli extract commit <commit-hash> # if chromium-src is in config
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Validate commit exists in the repository
|
|
2. Get diff for the commit: `git diff <commit>^..<commit>`
|
|
3. Parse diff output to identify changed files
|
|
4. For each changed file:
|
|
- Extract the file path relative to chromium src
|
|
- Detect if file is new (doesn't exist in parent commit) or modified
|
|
- Create directory structure in `chromium_src/` mirroring the file path
|
|
- Write individual patch file with `.patch` extension
|
|
5. Log summary of extracted patches
|
|
|
|
**Code Structure:**
|
|
```python
|
|
def extract_single_commit(commit_hash: str, chromium_src: Path) -> bool:
|
|
# Step 1: Validate commit
|
|
if not validate_commit_exists(commit_hash, chromium_src):
|
|
return False
|
|
|
|
# Step 2: Get diff
|
|
diff_output = run_git_command(['git', 'diff', f'{commit_hash}^..{commit_hash}'],
|
|
cwd=chromium_src)
|
|
|
|
# Step 3: Parse diff into file patches
|
|
file_patches = parse_diff_output(diff_output)
|
|
|
|
# Step 4: Write individual patches
|
|
for file_path, patch_content in file_patches.items():
|
|
write_patch_file(file_path, patch_content)
|
|
|
|
# Step 5: Log summary
|
|
log_extraction_summary(file_patches)
|
|
return True
|
|
```
|
|
|
|
#### 4.1.2 `extract range` - Extract patches from a range of commits
|
|
```bash
|
|
dev-cli --chromium-src <path> extract range <base-commit> <head-commit>
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Validate both commits exist
|
|
2. Get cumulative diff: `git diff <base>..<head>`
|
|
3. Parse diff to identify all changed files
|
|
4. For each file:
|
|
- Detect if new or modified (check if exists at base commit)
|
|
- Create/update patch file in `chromium_src/`
|
|
5. Handle file deletions (create `.deleted` marker files)
|
|
6. Log summary with statistics
|
|
|
|
**Code Structure:**
|
|
```python
|
|
def extract_commit_range(base_commit: str, head_commit: str, chromium_src: Path) -> bool:
|
|
# Step 1: Validate commits
|
|
if not validate_commit_exists(base_commit, chromium_src):
|
|
return False
|
|
if not validate_commit_exists(head_commit, chromium_src):
|
|
return False
|
|
|
|
# Step 2: Get cumulative diff
|
|
diff_output = run_git_command(['git', 'diff', f'{base_commit}..{head_commit}'],
|
|
cwd=chromium_src)
|
|
|
|
# Step 3-5: Process diff
|
|
file_patches = parse_diff_output(diff_output)
|
|
|
|
for file_path, patch_content in file_patches.items():
|
|
if patch_content is None: # File was deleted
|
|
create_deletion_marker(file_path)
|
|
else:
|
|
write_patch_file(file_path, patch_content)
|
|
|
|
# Step 6: Log summary
|
|
log_extraction_summary(file_patches)
|
|
return True
|
|
```
|
|
|
|
### 4.2 Apply Commands
|
|
|
|
#### 4.2.1 `apply all` - Apply all patches
|
|
```bash
|
|
dev-cli --chromium-src <path> apply all [--commit-each] [--dry-run]
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Validate chromium_src is a git repository
|
|
2. If `--dry-run`, check which patches would apply cleanly
|
|
3. Recursively find all `.patch` files in `chromium_src/` directory
|
|
4. Sort patches alphabetically by path (deterministic order)
|
|
5. For each patch:
|
|
- Apply using `git apply -p1 <patch>`
|
|
- If conflict, handle based on conflict resolution flow
|
|
- If `--commit-each`, create git commit after successful application
|
|
6. Report summary of applied/failed patches
|
|
|
|
**Conflict Resolution Flow:**
|
|
```python
|
|
def handle_patch_conflict(patch_path: Path, chromium_src: Path) -> bool:
|
|
print(f"CONFLICT: {patch_path}")
|
|
print("Options:")
|
|
print(" 1) Fix manually and continue")
|
|
print(" 2) Skip this patch")
|
|
print(" 3) Abort all remaining patches")
|
|
|
|
while True:
|
|
choice = input("Enter choice (1-3): ")
|
|
if choice == "1":
|
|
input("Fix the conflicts and press Enter to continue...")
|
|
return True # Continue with next patch
|
|
elif choice == "2":
|
|
print(f"Skipping {patch_path}")
|
|
return True # Skip but continue
|
|
elif choice == "3":
|
|
return False # Abort
|
|
```
|
|
|
|
#### 4.2.2 `apply feature` - Apply patches for a specific feature
|
|
```bash
|
|
dev-cli --chromium-src <path> apply feature <feature-name> [--commit-each] [--dry-run]
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Load `features.yaml` file
|
|
2. Validate feature exists in the YAML
|
|
3. Get list of files for the feature
|
|
4. For each file in the feature:
|
|
- Construct patch file path in `chromium_src/`
|
|
- Apply patch using same logic as `apply --all`
|
|
5. Handle conflicts with same resolution flow
|
|
6. Report feature-specific summary
|
|
|
|
**Code Structure:**
|
|
```python
|
|
def apply_feature_patches(feature_name: str, chromium_src: Path,
|
|
commit_each: bool = False) -> bool:
|
|
# Step 1-2: Load and validate
|
|
features = load_features_yaml()
|
|
if feature_name not in features:
|
|
print(f"Feature '{feature_name}' not found")
|
|
return False
|
|
|
|
# Step 3: Get file list
|
|
file_list = features[feature_name]['files']
|
|
|
|
# Step 4-5: Apply patches
|
|
success_count = 0
|
|
fail_count = 0
|
|
|
|
for file_path in file_list:
|
|
patch_path = construct_patch_path(file_path)
|
|
if apply_single_patch(patch_path, chromium_src):
|
|
success_count += 1
|
|
if commit_each:
|
|
create_git_commit(f"Apply {feature_name}: {file_path}")
|
|
else:
|
|
fail_count += 1
|
|
|
|
# Step 6: Report
|
|
print(f"Feature '{feature_name}': {success_count} applied, {fail_count} failed")
|
|
return fail_count == 0
|
|
```
|
|
|
|
### 4.3 Feature Management Commands
|
|
|
|
#### 4.3.1 `feature add` - Add files from a commit to a feature
|
|
```bash
|
|
dev-cli --chromium-src <path> feature add <feature-name> <commit-hash>
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Get list of files changed in the commit
|
|
2. Load or create `features.yaml`
|
|
3. Add/update feature entry with file list
|
|
4. Detect duplicates and warn
|
|
5. Save updated YAML
|
|
|
|
**Code Structure:**
|
|
```python
|
|
def add_feature_from_commit(feature_name: str, commit_hash: str,
|
|
chromium_src: Path) -> bool:
|
|
# Step 1: Get changed files
|
|
changed_files = get_commit_changed_files(commit_hash, chromium_src)
|
|
|
|
# Step 2: Load features
|
|
features = load_features_yaml()
|
|
|
|
# Step 3-4: Update feature
|
|
if feature_name in features:
|
|
existing_files = set(features[feature_name]['files'])
|
|
new_files = set(changed_files) - existing_files
|
|
if new_files:
|
|
features[feature_name]['files'].extend(new_files)
|
|
print(f"Added {len(new_files)} new files to '{feature_name}'")
|
|
else:
|
|
print(f"All files already in '{feature_name}'")
|
|
else:
|
|
features[feature_name] = {
|
|
'description': f"Feature added from commit {commit_hash}",
|
|
'files': changed_files
|
|
}
|
|
print(f"Created new feature '{feature_name}' with {len(changed_files)} files")
|
|
|
|
# Step 5: Save
|
|
save_features_yaml(features)
|
|
return True
|
|
```
|
|
|
|
#### 4.3.2 `feature list` - List all features
|
|
```bash
|
|
dev-cli feature list
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Load `features.yaml`
|
|
2. Display formatted list of features with file counts
|
|
3. Show features sorted alphabetically
|
|
|
|
**Output Example:**
|
|
```
|
|
Features:
|
|
llm-chat (4 files) - LLM chat integration in side panel
|
|
another-feature (2 files) - Another feature description
|
|
|
|
Total: 2 features, 6 unique files
|
|
```
|
|
|
|
#### 4.3.3 `feature show` - Show details of a feature
|
|
```bash
|
|
dev-cli feature show <feature-name>
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Load `features.yaml`
|
|
2. Validate feature exists
|
|
3. Display feature details and file list
|
|
4. Check patch status for each file
|
|
|
|
**Output Example:**
|
|
```
|
|
Feature: llm-chat
|
|
Description: LLM chat integration in side panel
|
|
Files (4):
|
|
✓ chrome/app/chrome_command_ids.h
|
|
✓ chrome/browser/ui/ui_features.h
|
|
✗ chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_panel_coordinator.cc (patch missing)
|
|
✓ chrome/browser/ui/views/side_panel/third_party_llm/third_party_llm_view.cc
|
|
```
|
|
|
|
#### 4.3.4 `feature generate-patch` - Generate combined patch for a feature
|
|
```bash
|
|
dev-cli feature generate-patch <feature-name> [--output <path>]
|
|
```
|
|
|
|
**Implementation Steps:**
|
|
1. Load feature file list from YAML
|
|
2. Concatenate all individual patches in order
|
|
3. Generate unified patch header
|
|
4. Write to output file or stdout
|
|
|
|
## 4. Core Modules Implementation
|
|
|
|
### 4.1 Diff Parser Module
|
|
```python
|
|
# modules/diff_parser.py
|
|
|
|
def parse_diff_output(diff_output: str) -> Dict[str, Optional[str]]:
|
|
"""
|
|
Parse git diff output into individual file patches.
|
|
|
|
Returns:
|
|
Dict mapping file path to patch content.
|
|
None value indicates file was deleted.
|
|
"""
|
|
patches = {}
|
|
current_file = None
|
|
current_patch = []
|
|
|
|
for line in diff_output.splitlines():
|
|
if line.startswith('diff --git'):
|
|
# Save previous patch if exists
|
|
if current_file:
|
|
patches[current_file] = '\n'.join(current_patch)
|
|
|
|
# Parse new file path
|
|
parts = line.split()
|
|
current_file = parts[2][2:] if parts[2].startswith('a/') else parts[2]
|
|
current_patch = [line]
|
|
|
|
elif line.startswith('deleted file'):
|
|
patches[current_file] = None # Mark as deleted
|
|
|
|
elif current_file:
|
|
current_patch.append(line)
|
|
|
|
# Save last patch
|
|
if current_file:
|
|
patches[current_file] = '\n'.join(current_patch) if current_patch else None
|
|
|
|
return patches
|
|
```
|
|
|
|
### 4.2 Patch Writer Module
|
|
```python
|
|
# modules/patch_writer.py
|
|
|
|
def write_patch_file(file_path: str, patch_content: str) -> bool:
|
|
"""
|
|
Write a patch file to chromium_src directory structure.
|
|
"""
|
|
# Construct output path
|
|
output_path = Path('chromium_src') / file_path
|
|
output_path = output_path.with_suffix(output_path.suffix + '.patch')
|
|
|
|
# Create directory structure
|
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Write patch content
|
|
output_path.write_text(patch_content)
|
|
|
|
print(f" Written: {output_path}")
|
|
return True
|
|
|
|
def create_deletion_marker(file_path: str) -> bool:
|
|
"""
|
|
Create a marker file for deleted files.
|
|
"""
|
|
marker_path = Path('chromium_src') / file_path
|
|
marker_path = marker_path.with_suffix(marker_path.suffix + '.deleted')
|
|
|
|
marker_path.parent.mkdir(parents=True, exist_ok=True)
|
|
marker_path.write_text(f"File deleted in patch\n")
|
|
|
|
print(f" Marked deleted: {marker_path}")
|
|
return True
|
|
```
|
|
|
|
### 4.3 Patch Application Module
|
|
```python
|
|
# modules/patch_apply.py
|
|
|
|
def apply_single_patch(patch_path: Path, chromium_src: Path,
|
|
interactive: bool = True) -> bool:
|
|
"""
|
|
Apply a single patch file to chromium source.
|
|
"""
|
|
if not patch_path.exists():
|
|
print(f"Patch file not found: {patch_path}")
|
|
return False
|
|
|
|
# Try standard apply
|
|
cmd = ['git', 'apply', '-p1', str(patch_path)]
|
|
result = run_command(cmd, cwd=chromium_src, capture=True)
|
|
|
|
if result.returncode == 0:
|
|
print(f" ✓ Applied: {patch_path.name}")
|
|
return True
|
|
|
|
# Try 3-way merge
|
|
cmd.append('--3way')
|
|
result = run_command(cmd, cwd=chromium_src, capture=True)
|
|
|
|
if result.returncode == 0:
|
|
print(f" ✓ Applied (3-way): {patch_path.name}")
|
|
return True
|
|
|
|
# Handle conflict
|
|
if interactive:
|
|
return handle_patch_conflict(patch_path, chromium_src)
|
|
else:
|
|
print(f" ✗ Failed: {patch_path.name}")
|
|
return False
|
|
```
|
|
|
|
### 4.4 Feature Manager Module
|
|
```python
|
|
# modules/feature_manager.py
|
|
|
|
def load_features_yaml() -> Dict:
|
|
"""Load features.yaml file."""
|
|
features_path = Path('features.yaml')
|
|
if not features_path.exists():
|
|
return {'version': '1.0', 'features': {}}
|
|
|
|
with open(features_path) as f:
|
|
data = yaml.safe_load(f)
|
|
|
|
return data.get('features', {})
|
|
|
|
def save_features_yaml(features: Dict) -> None:
|
|
"""Save features to YAML file."""
|
|
features_path = Path('features.yaml')
|
|
|
|
data = {
|
|
'version': '1.0',
|
|
'features': features
|
|
}
|
|
|
|
with open(features_path, 'w') as f:
|
|
yaml.dump(data, f, default_flow_style=False, sort_keys=False)
|
|
```
|
|
|
|
## 5. Error Handling
|
|
|
|
### 5.1 Common Error Scenarios
|
|
|
|
1. **Invalid Chromium Source Path**
|
|
- Check if path exists and is a git repository
|
|
- Check for presence of chromium markers (BUILD.gn, chrome/ directory)
|
|
|
|
2. **Git Command Failures**
|
|
- Wrap all git commands in try-except
|
|
- Provide clear error messages with git stderr output
|
|
- Suggest fixes (e.g., "commit not found, check if you're in the right branch")
|
|
|
|
3. **Patch Conflicts**
|
|
- Detect via git apply return code
|
|
- Offer interactive resolution
|
|
- Allow skipping problematic patches
|
|
|
|
4. **File Permission Issues**
|
|
- Check write permissions before operations
|
|
- Provide clear error messages about permission requirements
|
|
|
|
5. **Malformed YAML**
|
|
- Validate YAML structure on load
|
|
- Provide line numbers for syntax errors
|
|
- Create backup before modifying
|
|
|
|
### 5.2 Error Recovery
|
|
```python
|
|
def safe_operation(operation_func, *args, **kwargs):
|
|
"""
|
|
Wrapper for safe execution with rollback capability.
|
|
"""
|
|
backup_created = False
|
|
try:
|
|
# Create backup if modifying files
|
|
if kwargs.get('create_backup', True):
|
|
create_backup()
|
|
backup_created = True
|
|
|
|
# Execute operation
|
|
result = operation_func(*args, **kwargs)
|
|
|
|
# Clean up backup on success
|
|
if backup_created:
|
|
cleanup_backup()
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
|
|
if backup_created:
|
|
if prompt_yes_no("Restore from backup?"):
|
|
restore_backup()
|
|
|
|
raise
|
|
```
|
|
|
|
## 6. Configuration
|
|
|
|
### 6.1 CLI Configuration File (.dev-cli.yaml)
|
|
```yaml
|
|
# Optional configuration file
|
|
defaults:
|
|
chromium_src: /path/to/chromium/src
|
|
auto_commit: false
|
|
interactive: true
|
|
|
|
aliases:
|
|
# Short aliases for common operations
|
|
up: "extract-range HEAD~1 HEAD"
|
|
fix: "apply --all --commit-each"
|
|
```
|
|
|
|
### 6.2 Environment Variables
|
|
```bash
|
|
DEV_CLI_CHROMIUM_SRC=/path/to/chromium/src
|
|
DEV_CLI_AUTO_COMMIT=true
|
|
DEV_CLI_INTERACTIVE=false
|
|
```
|
|
|
|
## 7. Testing Strategy
|
|
|
|
### 7.1 Unit Tests
|
|
- Test diff parsing with various git diff outputs
|
|
- Test patch file writing and directory creation
|
|
- Test YAML loading/saving
|
|
- Test conflict detection
|
|
|
|
### 7.2 Integration Tests
|
|
- Create test repository with known commits
|
|
- Test extract commands with various commit scenarios
|
|
- Test apply commands with clean and conflicting patches
|
|
- Test feature management operations
|
|
|
|
### 7.3 End-to-End Tests
|
|
- Full workflow: extract → apply → fix conflicts → re-extract
|
|
- Chromium version upgrade simulation
|
|
- Multi-feature patch management
|
|
|
|
## 8. Implementation Phases
|
|
|
|
### Phase 1: Core Infrastructure (Foundation)
|
|
1. Create main CLI entry point with argument parsing
|
|
2. Implement configuration loading (CLI args, config file, env vars)
|
|
3. Create basic project structure and modules
|
|
4. Implement logging and error handling framework
|
|
|
|
### Phase 2: Extract Commands
|
|
1. Implement git command execution wrapper
|
|
2. Create diff parser module
|
|
3. Implement `extract` command for single commits
|
|
4. Implement `extract-range` command for commit ranges
|
|
5. Add patch file writer with directory structure creation
|
|
|
|
### Phase 3: Apply Commands
|
|
1. Implement patch discovery (finding .patch files)
|
|
2. Create patch application logic with git apply
|
|
3. Add conflict detection and resolution flow
|
|
4. Implement `apply --all` command
|
|
5. Add `--commit-each` support
|
|
6. Implement `--dry-run` mode
|
|
|
|
### Phase 4: Feature Management
|
|
1. Create YAML file handler for features.yaml
|
|
2. Implement `feature add` command
|
|
3. Implement `feature list` command
|
|
4. Implement `feature show` command
|
|
5. Add `apply --feature` command
|
|
6. Implement `feature generate-patch` command
|
|
|
|
### Phase 5: Polish and Optimization
|
|
1. Add progress bars for long operations
|
|
2. Implement caching for repeated operations
|
|
3. Add verbose and quiet modes
|
|
4. Create comprehensive help documentation
|
|
5. Add shell completion scripts
|
|
|
|
## 9. CLI Usage Examples
|
|
|
|
### 9.1 Initial Setup
|
|
```bash
|
|
# Set chromium source path (can also be in config)
|
|
export CHROMIUM_SRC=~/chromium/src
|
|
|
|
# Extract patches from existing work
|
|
dev-cli --chromium-src $CHROMIUM_SRC extract range chromium-base HEAD
|
|
|
|
# Create feature from recent commit
|
|
dev-cli --chromium-src $CHROMIUM_SRC feature add llm-chat HEAD~1
|
|
```
|
|
|
|
### 9.2 Daily Development
|
|
```bash
|
|
# Make changes in chromium repo, commit them
|
|
cd ~/chromium/src
|
|
git add -A
|
|
git commit -m "Fix: Updated LLM chat UI"
|
|
|
|
# Extract the changes to patches
|
|
dev-cli --chromium-src . extract commit HEAD
|
|
|
|
# Or update patches from a range
|
|
dev-cli --chromium-src . extract range main HEAD
|
|
```
|
|
|
|
### 9.3 Chromium Upgrade Workflow
|
|
```bash
|
|
# Check what will apply cleanly
|
|
dev-cli --chromium-src $CHROMIUM_SRC apply all --dry-run
|
|
|
|
# Apply all patches with commit tracking
|
|
dev-cli --chromium-src $CHROMIUM_SRC apply all --commit-each
|
|
|
|
# If conflicts occur:
|
|
# 1. Fix conflicts in chromium/src
|
|
# 2. Press Enter to continue applying remaining patches
|
|
# 3. Build and test
|
|
# 4. Extract all changes to update patches
|
|
|
|
dev-cli --chromium-src $CHROMIUM_SRC extract range chromium-base HEAD
|
|
```
|
|
|
|
### 9.4 Feature-based Operations
|
|
```bash
|
|
# Apply only LLM chat feature
|
|
dev-cli --chromium-src $CHROMIUM_SRC apply feature llm-chat
|
|
|
|
# See what's in a feature
|
|
dev-cli feature show llm-chat
|
|
|
|
# Generate combined patch for sharing
|
|
dev-cli feature generate-patch llm-chat --output llm-chat-combined.patch
|
|
```
|
|
|
|
## 10. Future Enhancements (Not in MVP)
|
|
|
|
1. **Patch Dependencies**: Track which patches depend on others
|
|
2. **Automatic Conflict Resolution**: Use AI/heuristics for simple conflicts
|
|
3. **Patch Versioning**: Track patch history across Chromium versions
|
|
4. **Collaboration Features**: Share patches via git/cloud
|
|
5. **Build Integration**: Trigger builds after successful patch application
|
|
6. **Patch Validation**: Pre-check if patches will build before applying
|
|
7. **Bisect Support**: Find which patch broke the build
|
|
8. **Patch Statistics**: Track success rates across versions
|
|
|
|
## 11. Success Criteria
|
|
|
|
The dev CLI will be considered successful when:
|
|
1. Developers can extract patches from any git commit or range
|
|
2. Patches can be applied to clean Chromium with clear conflict resolution
|
|
3. Features can be tracked and managed independently
|
|
4. The Chromium upgrade process is streamlined and predictable
|
|
5. The tool requires minimal manual intervention for common operations
|
|
6. Error messages are clear and actionable
|
|
7. The tool is faster than manual patch management
|
|
|
|
## 12. Non-Goals
|
|
|
|
The following are explicitly NOT goals for this tool:
|
|
1. Managing chromium source code fetching/syncing (use depot_tools)
|
|
2. Building chromium (use existing build system)
|
|
3. Managing git branches (use git directly)
|
|
4. Automatic merge conflict resolution (manual resolution required)
|
|
5. Patch optimization or combination (maintain 1:1 file mapping) |