update.py
1 """ 2 Self-update functionality for Kamaji. 3 """ 4 5 import os 6 import sys 7 import subprocess 8 from pathlib import Path 9 10 11 def get_source_path() -> Path: 12 """ 13 Get the source directory path where Kamaji is installed from. 14 15 Returns: 16 Path to the source directory 17 """ 18 # The source path is stored during installation 19 # It's the directory containing setup.py 20 import kamaji 21 kamaji_module_path = Path(kamaji.__file__).parent 22 source_path = kamaji_module_path.parent 23 return source_path 24 25 26 def check_git_repo(source_path: Path) -> bool: 27 """ 28 Check if the source path is a git repository. 29 30 Args: 31 source_path: Path to check 32 33 Returns: 34 True if it's a git repo 35 """ 36 git_dir = source_path / ".git" 37 return git_dir.exists() and git_dir.is_dir() 38 39 40 def run_update(verbose: bool = False) -> int: 41 """ 42 Update Kamaji by pulling latest changes and reinstalling. 43 44 Args: 45 verbose: Show detailed output 46 47 Returns: 48 Exit code (0 for success) 49 """ 50 try: 51 source_path = get_source_path() 52 53 print(f"š Kamaji source location: {source_path}") 54 55 # Check if it's a git repository 56 if not check_git_repo(source_path): 57 print("ā Error: Source directory is not a git repository") 58 print(f" Location: {source_path}") 59 print("\nTo enable updates, install Kamaji from a git clone:") 60 print(" git clone git@github.com:TransformerOS/Kamaji.git") 61 print(" cd Kamaji") 62 print(" make install") 63 return 1 64 65 print("š” Checking for updates...") 66 67 # Check current branch 68 result = subprocess.run( 69 ["git", "rev-parse", "--abbrev-ref", "HEAD"], 70 cwd=source_path, 71 capture_output=True, 72 text=True 73 ) 74 branch = result.stdout.strip() 75 print(f"š Current branch: {branch}") 76 77 # Check current commit 78 result = subprocess.run( 79 ["git", "rev-parse", "--short", "HEAD"], 80 cwd=source_path, 81 capture_output=True, 82 text=True 83 ) 84 current_commit = result.stdout.strip() 85 print(f"š Current commit: {current_commit}") 86 87 # Fetch updates 88 print("\nš Fetching updates from remote...") 89 result = subprocess.run( 90 ["git", "fetch", "origin"], 91 cwd=source_path, 92 capture_output=not verbose, 93 text=True 94 ) 95 96 if result.returncode != 0: 97 print("ā Error: Failed to fetch updates") 98 if not verbose: 99 print(result.stderr) 100 return 1 101 102 # Check if updates are available 103 result = subprocess.run( 104 ["git", "rev-list", f"HEAD..origin/{branch}", "--count"], 105 cwd=source_path, 106 capture_output=True, 107 text=True 108 ) 109 110 commits_behind = int(result.stdout.strip()) if result.returncode == 0 else 0 111 112 if commits_behind == 0: 113 print("ā Already up to date!") 114 return 0 115 116 print(f"š¦ {commits_behind} update(s) available") 117 118 # Show what will be updated 119 print("\nš Changes:") 120 result = subprocess.run( 121 ["git", "log", f"HEAD..origin/{branch}", "--oneline", "--no-decorate"], 122 cwd=source_path, 123 capture_output=True, 124 text=True 125 ) 126 if result.returncode == 0: 127 for line in result.stdout.strip().split('\n')[:5]: 128 print(f" ⢠{line}") 129 if commits_behind > 5: 130 print(f" ... and {commits_behind - 5} more") 131 132 # Ask for confirmation 133 print("\nā Update Kamaji?") 134 response = input(" Type 'yes' to continue: ").strip().lower() 135 136 if response != 'yes': 137 print("ā Update cancelled") 138 return 0 139 140 # Pull updates 141 print("\nā¬ļø Pulling updates...") 142 result = subprocess.run( 143 ["git", "pull", "origin", branch], 144 cwd=source_path, 145 capture_output=not verbose, 146 text=True 147 ) 148 149 if result.returncode != 0: 150 print("ā Error: Failed to pull updates") 151 if not verbose: 152 print(result.stderr) 153 return 1 154 155 # Get new commit 156 result = subprocess.run( 157 ["git", "rev-parse", "--short", "HEAD"], 158 cwd=source_path, 159 capture_output=True, 160 text=True 161 ) 162 new_commit = result.stdout.strip() 163 print(f"ā Updated to commit: {new_commit}") 164 165 # Check if Makefile exists 166 makefile = source_path / "Makefile" 167 168 if makefile.exists(): 169 print("\nšØ Rebuilding and reinstalling...") 170 print(" Running: make install") 171 result = subprocess.run( 172 ["make", "install"], 173 cwd=source_path, 174 capture_output=False, # Always show output 175 text=True 176 ) 177 178 if result.returncode != 0: 179 print("ā Error: Failed to reinstall") 180 return 1 181 182 print("ā Installation complete") 183 else: 184 # Fallback to pip install 185 print("\nšØ Rebuilding and reinstalling...") 186 venv_pip = source_path / "venv" / "bin" / "pip" 187 188 if venv_pip.exists(): 189 print(f" Running: {venv_pip} install -e .") 190 result = subprocess.run( 191 [str(venv_pip), "install", "-e", "."], 192 cwd=source_path, 193 capture_output=False, # Always show output 194 text=True 195 ) 196 197 if result.returncode != 0: 198 print("ā Error: Failed to reinstall") 199 return 1 200 201 print("ā Installation complete") 202 else: 203 print("ā ļø Warning: Could not find pip to reinstall") 204 print(" Please run 'make install' manually in:") 205 print(f" {source_path}") 206 return 1 207 208 print("\nš Kamaji successfully updated!") 209 print(f" {current_commit} ā {new_commit}") 210 print("\nš” Restart any running Kamaji sessions to use the new version") 211 212 return 0 213 214 except KeyboardInterrupt: 215 print("\n\nā Update cancelled by user") 216 return 130 217 except Exception as e: 218 print(f"\nā Error: {e}") 219 if verbose: 220 import traceback 221 traceback.print_exc() 222 return 1