nushell-plugins/scripts/safe_merge_upstream.nu

506 lines
15 KiB
Plaintext
Raw Normal View History

feat: major repository modernization and tracking cleanup ## Summary Comprehensive repository cleanup focusing on plugin dependency management, documentation improvements, and git tracking optimization. ## Key Changes ### 🔧 Core Infrastructure - Synchronized all nu-* dependencies across plugins for version consistency - Enhanced upstream tracking and automation systems - Removed nushell directory from git tracking for cleaner repository management ### 📚 Documentation - Significantly expanded README.md with comprehensive development guides - Added detailed workflow documentation and command references - Improved plugin collection overview and usage examples ### 🧹 Repository Cleanup - Removed legacy bash scripts (build-all.sh, collect-install.sh, make_plugin.sh) - Streamlined automation through unified justfile and nushell script approach - Updated .gitignore with nushell directory and archive patterns - Removed nushell directory from git tracking to prevent unwanted changes ### 🔌 Plugin Updates - **nu_plugin_image**: Major refactoring with modular architecture improvements - **nu_plugin_hashes**: Enhanced functionality and build system improvements - **nu_plugin_highlight**: Updated for new plugin API compatibility - **nu_plugin_clipboard**: Dependency synchronization - **nu_plugin_desktop_notifications**: Version alignment - **nu_plugin_port_extension & nu_plugin_qr_maker**: Consistency updates - **nu_plugin_kcl & nu_plugin_tera**: Submodule synchronization ### 🏗️ Git Tracking Optimization - Removed nushell directory from version control for cleaner repository management - Added comprehensive .gitignore patterns for build artifacts and archives ## Statistics - 2,082 files changed - 2,373 insertions, 339,936 deletions - Net reduction of 337,563 lines (primarily from removing nushell directory tracking) ## Benefits - Complete version consistency across all plugins - Cleaner repository with optimized git tracking - Improved developer experience with streamlined workflows - Enhanced documentation and automation - Reduced repository size and complexity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-20 15:18:58 +01:00
#!/usr/bin/env nu
# Safe Merge Upstream Script
# Safely merges upstream changes while preserving local nu_* dependency versions
use lib/cargo_toml_diff.nu *
# Version check - mandatory for all plugin operations
def version_check [] {
try {
nu scripts/check_version.nu --quiet | ignore
} catch {
print "❌ Nushell version mismatch detected!"
print "🔧 Run: nu scripts/check_version.nu --fix"
exit 1
}
}
# Load plugin registry
def load_registry [] {
let registry_path = "etc/plugin_registry.toml"
if not ($registry_path | path exists) {
error make {msg: "Plugin registry not found. Please run from repository root directory."}
}
open $registry_path
}
# Load exclusions configuration
def load_exclusions [] {
let exclude_path = "etc/upstream_exclude.toml"
if ($exclude_path | path exists) {
let config = open $exclude_path
{
all: ($config.exclude.plugins? | default []),
check: ($config.exclude.check.plugins? | default []),
merge: ($config.exclude.merge.plugins? | default []),
patterns: ($config.exclude.patterns.plugins? | default [])
}
} else {
{all: [], check: [], merge: [], patterns: []}
}
}
# Check if plugin should be excluded
def is_plugin_excluded [plugin_name: string, operation: string, exclusions: record] {
# Check direct exclusions
if $plugin_name in $exclusions.all {
return true
}
# Check operation-specific exclusions
let op_exclusions = match $operation {
"check" => $exclusions.check,
"merge" => $exclusions.merge,
_ => []
}
if $plugin_name in $op_exclusions {
return true
}
# Check pattern exclusions
for pattern in $exclusions.patterns {
if ($plugin_name | str contains ($pattern | str replace "*" "")) {
return true
}
}
false
}
# Save plugin registry
def save_registry [registry: record] {
$registry | to toml | save -f "etc/plugin_registry.toml"
}
# Get current date in ISO format
def current_date [] {
date now | format date "%Y-%m-%d %H:%M:%S"
}
# Create backup branch
def create_backup [plugin_path: string] {
cd $plugin_path
let timestamp = date now | format date "%Y%m%d_%H%M%S"
let backup_branch = $"backup_before_merge_($timestamp)"
try {
git checkout -b $backup_branch
git checkout - # Return to original branch
print $" 📋 Created backup branch: ($backup_branch)"
$backup_branch
} catch {
error make {msg: "Failed to create backup branch"}
}
}
# Create temporary merge branch
def create_merge_branch [plugin_path: string] {
cd $plugin_path
let timestamp = date now | format date "%Y%m%d_%H%M%S"
let merge_branch = $"temp_merge_($timestamp)"
try {
git checkout -b $merge_branch
print $" 🔀 Created merge branch: ($merge_branch)"
$merge_branch
} catch {
error make {msg: "Failed to create merge branch"}
}
}
# Extract and preserve local nu dependencies
def preserve_local_nu_deps [plugin_path: string] {
cd $plugin_path
let cargo_toml = "Cargo.toml"
if not ($cargo_toml | path exists) {
error make {msg: "Cargo.toml not found"}
}
# Parse current local dependencies
let local_cargo = parse_cargo_toml $cargo_toml
let local_nu_deps = extract_nu_dependencies $local_cargo
print $" 💾 Preserved ($local_nu_deps | columns | length) local nu_* dependencies"
$local_nu_deps
}
# Restore local nu dependencies after merge
def restore_nu_deps [plugin_path: string, local_nu_deps: record] {
cd $plugin_path
let cargo_toml = "Cargo.toml"
let content = open $cargo_toml
# Update each nu dependency with local version
mut updated_content = $content
for dep in ($local_nu_deps | transpose key value) {
let dep_name = $dep.key
let dep_value = $dep.value
# Update the dependency in the TOML structure
$updated_content = ($updated_content | upsert dependencies.($dep_name) $dep_value)
}
# Save updated Cargo.toml
$updated_content | to toml | save -f $cargo_toml
print $" 🔧 Restored ($local_nu_deps | columns | length) local nu_* dependency versions"
}
# Perform the merge
def perform_merge [plugin_path: string, upstream_branch: string] {
cd $plugin_path
try {
print $" 🔀 Merging upstream/($upstream_branch)..."
git merge $"upstream/($upstream_branch)" --no-edit
# Check if merge was successful
let merge_status = git status --porcelain | lines
if ($merge_status | length) > 0 {
# Check if there are conflict markers
let conflicts = $merge_status | where ($it | str starts-with "UU")
if ($conflicts | length) > 0 {
print $" ❌ Merge conflicts detected"
return false
}
}
print $" ✅ Merge completed successfully"
true
} catch {
print $" ❌ Merge failed"
false
}
}
# Test if the project compiles after merge
def test_compilation [plugin_path: string] {
cd $plugin_path
print $" 🧪 Testing compilation..."
try {
let result = cargo check --quiet
print $" ✅ Compilation successful"
true
} catch {
print $" ❌ Compilation failed"
false
}
}
# Run tests if they exist
def run_tests [plugin_path: string] {
cd $plugin_path
# Check if there are tests
let test_files = glob "**/*test*.rs" | length
if $test_files == 0 {
print $" ⏭️ No tests found, skipping"
return true
}
print $" 🧪 Running tests..."
try {
cargo test --quiet
print $" ✅ All tests passed"
true
} catch {
print $" ❌ Tests failed"
false
}
}
# Rollback to original state
def rollback_changes [plugin_path: string, original_branch: string, merge_branch: string] {
cd $plugin_path
print $" 🔄 Rolling back changes..."
try {
git checkout $original_branch
git branch -D $merge_branch
print $" ✅ Rollback completed"
} catch {
print $" ⚠️ Rollback may have failed - manual intervention needed"
}
}
# Apply successful merge to main branch
def apply_merge [plugin_path: string, original_branch: string, merge_branch: string] {
cd $plugin_path
print $" ✅ Applying successful merge..."
try {
git checkout $original_branch
git merge $merge_branch --no-edit
git branch -D $merge_branch
print $" ✅ Merge applied to ($original_branch)"
true
} catch {
print $" ❌ Failed to apply merge"
false
}
}
# Process a single plugin merge
def process_plugin_merge [plugin_name: string, plugin_config: record, force: bool, exclusions: record] {
print $"\n🔀 Processing merge for ($plugin_name)..."
# Check if plugin is excluded
if (is_plugin_excluded $plugin_name "merge" $exclusions) {
print $" 🚫 Skipping ($plugin_name) - excluded from merging"
return $plugin_config
}
let plugin_path = $plugin_config.local_path
# Validation checks
if ($plugin_config.upstream_url | is-empty) {
print $" ⏭️ Skipping ($plugin_name) - no upstream URL"
return $plugin_config
}
if not ($plugin_path | path exists) {
print $" ❌ Plugin directory not found: ($plugin_path)"
return $plugin_config
}
if ($plugin_config.status == "ok") and not $force {
print $" ✅ Plugin already marked as OK, use --force to merge anyway"
return $plugin_config
}
if ($plugin_config.status == "local_only") {
print $" 🏠 Local-only plugin, no upstream to merge"
return $plugin_config
}
cd $plugin_path
# Get current branch
let original_branch = git branch --show-current | str trim
# Check for uncommitted changes
let uncommitted = git status --porcelain | lines
if ($uncommitted | length) > 0 {
print $" ⚠️ Uncommitted changes detected. Please commit or stash them first."
return $plugin_config
}
# Fetch latest upstream
try {
git fetch upstream $plugin_config.upstream_branch
} catch {
print $" ❌ Failed to fetch upstream"
return $plugin_config
}
# Check if there are actually changes to merge
let changes = git diff $"HEAD..upstream/($plugin_config.upstream_branch)" --name-only | lines
if ($changes | length) == 0 {
print $" ✅ No changes to merge"
return ($plugin_config | upsert status "ok")
}
print $" 📋 Found ($changes | length) changed files: ($changes | str join ', ')"
# Create backup branch
let backup_branch = create_backup $plugin_path
# Preserve local nu dependencies
let local_nu_deps = preserve_local_nu_deps $plugin_path
# Create merge branch
let merge_branch = create_merge_branch $plugin_path
# Perform merge
let merge_success = perform_merge $plugin_path $plugin_config.upstream_branch
if not $merge_success {
print $" ❌ Merge failed, cleaning up..."
rollback_changes $plugin_path $original_branch $merge_branch
return ($plugin_config | upsert status "conflict")
}
# Restore local nu dependencies
restore_nu_deps $plugin_path $local_nu_deps
# Test compilation
let compile_success = test_compilation $plugin_path
if not $compile_success {
print $" ❌ Compilation failed after merge, rolling back..."
rollback_changes $plugin_path $original_branch $merge_branch
return ($plugin_config | upsert status "error")
}
# Run tests
let test_success = run_tests $plugin_path
if not $test_success {
print $" ❌ Tests failed after merge, rolling back..."
rollback_changes $plugin_path $original_branch $merge_branch
return ($plugin_config | upsert status "error")
}
# Apply the successful merge
let apply_success = apply_merge $plugin_path $original_branch $merge_branch
if not $apply_success {
print $" ❌ Failed to apply merge"
return ($plugin_config | upsert status "error")
}
# Update plugin configuration
let upstream_commit = git rev-parse $"upstream/($plugin_config.upstream_branch)" | str trim
print $" ✅ Successfully merged upstream changes!"
($plugin_config
| upsert status "ok"
| upsert last_checked_commit $upstream_commit
| upsert last_checked_date (current_date))
}
# Show merge preview
def show_merge_preview [plugin_name: string, plugin_config: record] {
let plugin_path = $plugin_config.local_path
if not ($plugin_path | path exists) {
print $"Plugin directory not found: ($plugin_path)"
return
}
cd $plugin_path
print $"🔍 Merge preview for ($plugin_name):"
print "=" * 50
try {
let changes = git diff $"HEAD..upstream/($plugin_config.upstream_branch)" --name-only | lines
print $"Files that will be changed: ($changes | length)"
for file in $changes {
print $" 📄 ($file)"
}
print "\nSample diff (first 20 lines):"
git diff $"HEAD..upstream/($plugin_config.upstream_branch)" | lines | first 20 | each {|line| print $" ($line)"}
if ($changes | length) > 20 {
print " ... (output truncated)"
}
} catch {
print "Failed to generate preview"
}
}
# Main function
def main [
plugin?: string # Specific plugin to merge
--force (-f) # Force merge even if status is OK
--preview (-p) # Show merge preview only
--all (-a) # Process all pending plugins
--help (-h) # Show help
] {
# Mandatory version check before any plugin operations
version_check
if $help {
print "Safe Merge Upstream - Nushell Plugin Upstream Merger"
print ""
print "USAGE:"
print " nu safe_merge_upstream.nu [OPTIONS] [PLUGIN]"
print ""
print "OPTIONS:"
print " --force, -f Force merge even if plugin status is OK"
print " --preview, -p Show merge preview without performing merge"
print " --all, -a Process all plugins with pending status"
print " --help, -h Show this help message"
print ""
print "EXAMPLES:"
print " nu safe_merge_upstream.nu highlight # Merge specific plugin"
print " nu safe_merge_upstream.nu --preview highlight # Preview changes"
print " nu safe_merge_upstream.nu --all # Merge all pending"
print " nu safe_merge_upstream.nu --force highlight # Force merge"
return
}
# Ensure we're in the repository root directory
if not ("nu_plugin_clipboard" | path exists) {
error make {msg: "Please run this script from the nushell-plugins repository root directory"}
}
# Load registry and exclusions
let registry = load_registry
let exclusions = load_exclusions
# Determine which plugins to process
let plugins_to_process = if $all {
$registry.plugins
| transpose key value
| where $it.value.status in ["pending", "error", "conflict"]
# Filter out excluded plugins
| where not (is_plugin_excluded $it.key "merge" $exclusions)
} else if ($plugin | is-not-empty) {
if $plugin in ($registry.plugins | columns) {
[{key: $plugin, value: ($registry.plugins | get $plugin)}]
} else {
error make {msg: $"Plugin '($plugin)' not found in registry"}
}
} else {
error make {msg: "Please specify a plugin name or use --all flag"}
}
if ($plugins_to_process | length) == 0 {
print "No plugins found to process"
return
}
# Preview mode
if $preview {
for plugin_entry in $plugins_to_process {
show_merge_preview $plugin_entry.key $plugin_entry.value
print ""
}
return
}
# Confirmation for multiple plugins
if ($plugins_to_process | length) > 1 {
print $"About to process ($plugins_to_process | length) plugins:"
for plugin_entry in $plugins_to_process {
print $" - ($plugin_entry.key) (status: ($plugin_entry.value.status))"
}
print ""
let confirm = input "Continue? [y/N]: "
if ($confirm | str downcase) != "y" {
print "Aborted"
return
}
}
# Process plugins
mut updated_registry = $registry
for plugin_entry in $plugins_to_process {
let plugin_name = $plugin_entry.key
let plugin_config = $plugin_entry.value
let updated_config = process_plugin_merge $plugin_name $plugin_config $force $exclusions
$updated_registry = ($updated_registry | upsert plugins.($plugin_name) $updated_config)
}
# Save updated registry
save_registry $updated_registry
print $"\n✅ Merge process completed!"
print "📊 Run 'nu plugin_status.nu' to see updated status"
}
if ($env.NUSHELL_EXECUTION_CONTEXT? | default "" | str contains "run") {
main
}