machineconfig 5.25__py3-none-any.whl → 5.27__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of machineconfig might be problematic. Click here for more details.
- machineconfig/jobs/installer/custom/hx.py +1 -1
- machineconfig/jobs/installer/custom_dev/alacritty.py +4 -4
- machineconfig/jobs/installer/installer_data.json +17 -0
- machineconfig/jobs/installer/linux_scripts/wezterm.sh +1 -1
- machineconfig/jobs/installer/package_groups.py +70 -111
- machineconfig/scripts/python/agents.py +4 -4
- machineconfig/scripts/python/choose_wezterm_theme.py +2 -2
- machineconfig/scripts/python/devops.py +6 -6
- machineconfig/scripts/python/{devops_update_repos.py → devops_helpers/devops_update_repos.py} +1 -1
- machineconfig/scripts/python/fire_jobs.py +3 -3
- machineconfig/scripts/python/helpers/repo_sync_helpers.py +14 -1
- machineconfig/scripts/python/helpers_fire/__init__.py +0 -0
- machineconfig/scripts/python/{fire_agents_help_launch.py → helpers_fire/fire_agents_help_launch.py} +1 -1
- machineconfig/scripts/python/helpers_fire_command/__init__.py +0 -0
- machineconfig/scripts/python/helpers_fire_command/fire_jobs_streamlit_helper.py +0 -0
- machineconfig/scripts/python/helpers_repos/grource.py +341 -0
- machineconfig/scripts/python/interactive.py +1 -1
- machineconfig/scripts/python/repos.py +75 -73
- machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py} +1 -1
- machineconfig/scripts/python/{repos_helper.py → repos_helpers/repos_helper.py} +4 -12
- machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py} +1 -1
- machineconfig/scripts/python/sessions_multiprocess.py +1 -1
- machineconfig/utils/files/ouch/__init__.py +0 -0
- machineconfig/utils/files/ouch/decompress.py +45 -0
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +1 -1
- machineconfig/utils/source_of_truth.py +0 -1
- machineconfig/utils/ssh.py +33 -19
- {machineconfig-5.25.dist-info → machineconfig-5.27.dist-info}/METADATA +3 -1
- {machineconfig-5.25.dist-info → machineconfig-5.27.dist-info}/RECORD +48 -44
- machineconfig/scripts/python/get_zellij_cmd.py +0 -15
- machineconfig/scripts/python/t4.py +0 -17
- /machineconfig/jobs/{python → installer}/check_installations.py +0 -0
- /machineconfig/scripts/python/{fire_jobs_streamlit_helper.py → devops_helpers/__init__.py} +0 -0
- /machineconfig/scripts/python/{devops_add_identity.py → devops_helpers/devops_add_identity.py} +0 -0
- /machineconfig/scripts/python/{devops_add_ssh_key.py → devops_helpers/devops_add_ssh_key.py} +0 -0
- /machineconfig/scripts/python/{devops_backup_retrieve.py → devops_helpers/devops_backup_retrieve.py} +0 -0
- /machineconfig/scripts/python/{devops_status.py → devops_helpers/devops_status.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_help_search.py → helpers_fire/fire_agents_help_search.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_helper_types.py → helpers_fire/fire_agents_helper_types.py} +0 -0
- /machineconfig/scripts/python/{fire_agents_load_balancer.py → helpers_fire/fire_agents_load_balancer.py} +0 -0
- /machineconfig/scripts/python/{cloud_manager.py → helpers_fire_command/cloud_manager.py} +0 -0
- /machineconfig/scripts/python/{fire_jobs_args_helper.py → helpers_fire_command/fire_jobs_args_helper.py} +0 -0
- /machineconfig/scripts/python/{fire_jobs_route_helper.py → helpers_fire_command/fire_jobs_route_helper.py} +0 -0
- /machineconfig/scripts/python/{count_lines.py → repos_helpers/count_lines.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_clone.py → repos_helpers/repos_helper_clone.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_record.py → repos_helpers/repos_helper_record.py} +0 -0
- /machineconfig/scripts/python/{repos_helper_update.py → repos_helpers/repos_helper_update.py} +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.27.dist-info}/WHEEL +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.27.dist-info}/entry_points.txt +0 -0
- {machineconfig-5.25.dist-info → machineconfig-5.27.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
"""Gource visualization tool for git repositories."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
|
+
import subprocess
|
|
6
|
+
import platform
|
|
7
|
+
import zipfile
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_gource_install_dir() -> Path:
|
|
12
|
+
"""Get the installation directory for portable Gource."""
|
|
13
|
+
if platform.system() == "Windows":
|
|
14
|
+
appdata = Path.home() / "AppData" / "Local"
|
|
15
|
+
return appdata / "gource"
|
|
16
|
+
else:
|
|
17
|
+
return Path.home() / ".local" / "bin" / "gource"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_gource_executable() -> Path:
|
|
21
|
+
"""Get the path to the gource executable (inside the extracted directory with DLLs)."""
|
|
22
|
+
install_dir = get_gource_install_dir()
|
|
23
|
+
if platform.system() == "Windows":
|
|
24
|
+
possible_paths = [
|
|
25
|
+
install_dir / "gource.exe",
|
|
26
|
+
install_dir / f"gource-{get_default_version()}.win64" / "gource.exe",
|
|
27
|
+
]
|
|
28
|
+
for path in possible_paths:
|
|
29
|
+
if path.exists():
|
|
30
|
+
return path
|
|
31
|
+
return install_dir / f"gource-{get_default_version()}.win64" / "gource.exe"
|
|
32
|
+
else:
|
|
33
|
+
return install_dir / "gource"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_default_version() -> str:
|
|
37
|
+
"""Get the default gource version."""
|
|
38
|
+
return "0.53"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def install_gource_windows(version: Optional[str] = None) -> None:
|
|
42
|
+
"""Install portable Gource on Windows by downloading and extracting the zip archive."""
|
|
43
|
+
if platform.system() != "Windows":
|
|
44
|
+
raise OSError(f"This installer is for Windows only. Current OS: {platform.system()}")
|
|
45
|
+
|
|
46
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
47
|
+
from machineconfig.utils.source_of_truth import INSTALL_TMP_DIR
|
|
48
|
+
|
|
49
|
+
print("\n" + "=" * 80)
|
|
50
|
+
print("🚀 GOURCE PORTABLE INSTALLATION 🚀")
|
|
51
|
+
print("=" * 80 + "\n")
|
|
52
|
+
|
|
53
|
+
version_str = version or get_default_version()
|
|
54
|
+
portable_url = f"https://github.com/acaudwell/Gource/releases/download/gource-{version_str}/gource-{version_str}.win64.zip"
|
|
55
|
+
install_dir = get_gource_install_dir()
|
|
56
|
+
|
|
57
|
+
print(f"📥 Downloading portable Gource from: {portable_url}")
|
|
58
|
+
downloaded_zip = PathExtended(portable_url).download(folder=INSTALL_TMP_DIR)
|
|
59
|
+
print(f"✅ Downloaded to: {downloaded_zip}")
|
|
60
|
+
|
|
61
|
+
print(f"\n� Extracting to: {install_dir}")
|
|
62
|
+
install_dir.mkdir(parents=True, exist_ok=True)
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
with zipfile.ZipFile(downloaded_zip, 'r') as zip_ref:
|
|
66
|
+
zip_ref.extractall(install_dir)
|
|
67
|
+
print(f"✅ Extracted successfully to: {install_dir}")
|
|
68
|
+
print(f" (The zip contains gource-{version_str}.win64/ directory with exe and DLL dependencies)")
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"❌ Extraction failed with error: {e}")
|
|
71
|
+
raise
|
|
72
|
+
|
|
73
|
+
print("\n🗑️ Cleaning up zip file...")
|
|
74
|
+
try:
|
|
75
|
+
downloaded_zip.unlink()
|
|
76
|
+
print(f"✅ Removed zip file: {downloaded_zip}")
|
|
77
|
+
except Exception as e:
|
|
78
|
+
print(f"⚠️ Warning: Could not remove zip file: {e}")
|
|
79
|
+
|
|
80
|
+
gource_exe = get_gource_executable()
|
|
81
|
+
if gource_exe.exists():
|
|
82
|
+
print(f"\n✅ Gource executable found at: {gource_exe}")
|
|
83
|
+
dll_dir = gource_exe.parent
|
|
84
|
+
dll_count = len(list(dll_dir.glob("*.dll")))
|
|
85
|
+
print(f" Found {dll_count} DLL dependencies in: {dll_dir}")
|
|
86
|
+
else:
|
|
87
|
+
print(f"\n⚠️ Warning: Expected executable not found at: {gource_exe}")
|
|
88
|
+
print(f" Contents of {install_dir}:")
|
|
89
|
+
for item in install_dir.rglob("*"):
|
|
90
|
+
if item.is_file():
|
|
91
|
+
print(f" - {item.relative_to(install_dir)}")
|
|
92
|
+
|
|
93
|
+
print("\n" + "=" * 80)
|
|
94
|
+
print("✅ GOURCE PORTABLE INSTALLATION COMPLETED")
|
|
95
|
+
print("=" * 80)
|
|
96
|
+
print(f"\n📌 Gource installed to: {install_dir}")
|
|
97
|
+
print(f" Executable: {gource_exe}")
|
|
98
|
+
print(" All DLL dependencies are kept together in the same directory.")
|
|
99
|
+
print(" This script will automatically use the portable version.")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def visualize(
|
|
103
|
+
repo: Annotated[str, typer.Option("--repo", "-r", help="Path to git repository to visualize")] = Path.cwd().__str__(),
|
|
104
|
+
output_file: Annotated[Optional[Path], typer.Option("--output", "-o", help="Output video file (e.g., output.mp4). If specified, gource will render to video.")] = None,
|
|
105
|
+
resolution: Annotated[str, typer.Option("--resolution", "-res", help="Video resolution (e.g., 1920x1080, 1280x720)")] = "1920x1080",
|
|
106
|
+
seconds_per_day: Annotated[float, typer.Option("--seconds-per-day", "-spd", help="Speed of simulation (lower = faster)")] = 0.1,
|
|
107
|
+
auto_skip_seconds: Annotated[float, typer.Option("--auto-skip-seconds", "-as", help="Skip to next entry if nothing happens for X seconds")] = 1.0,
|
|
108
|
+
title: Annotated[Optional[str], typer.Option("--title", "-t", help="Title for the visualization")] = None,
|
|
109
|
+
hide_items: Annotated[list[str], typer.Option("--hide", "-h", help="Items to hide: bloom, date, dirnames, files, filenames, mouse, progress, root, tree, users, usernames")] = [],
|
|
110
|
+
key_items: Annotated[bool, typer.Option("--key", "-k", help="Show file extension key")] = False,
|
|
111
|
+
fullscreen: Annotated[bool, typer.Option("--fullscreen", "-f", help="Run in fullscreen mode")] = False,
|
|
112
|
+
viewport: Annotated[Optional[str], typer.Option("--viewport", "-v", help="Camera viewport (e.g., '1000x1000')")] = None,
|
|
113
|
+
start_date: Annotated[Optional[str], typer.Option("--start-date", help="Start date (YYYY-MM-DD)")] = None,
|
|
114
|
+
stop_date: Annotated[Optional[str], typer.Option("--stop-date", help="Stop date (YYYY-MM-DD)")] = None,
|
|
115
|
+
user_image_dir: Annotated[Optional[Path], typer.Option("--user-image-dir", help="Directory with user avatar images")] = None,
|
|
116
|
+
max_files: Annotated[int, typer.Option("--max-files", help="Maximum number of files to show (0 = no limit)")] = 0,
|
|
117
|
+
max_file_lag: Annotated[float, typer.Option("--max-file-lag", help="Max time files remain on screen after last change")] = 5.0,
|
|
118
|
+
file_idle_time: Annotated[int, typer.Option("--file-idle-time", help="Time in seconds files remain idle before being removed")] = 0,
|
|
119
|
+
framerate: Annotated[int, typer.Option("--framerate", help="Frames per second for video output")] = 60,
|
|
120
|
+
background_color: Annotated[str, typer.Option("--background-color", help="Background color in hex (e.g., 000000 for black)")] = "000000",
|
|
121
|
+
font_size: Annotated[int, typer.Option("--font-size", help="Font size")] = 22,
|
|
122
|
+
camera_mode: Annotated[str, typer.Option("--camera-mode", help="Camera mode: overview or track")] = "overview",
|
|
123
|
+
) -> None:
|
|
124
|
+
"""
|
|
125
|
+
Visualize git repository history using Gource with reasonable defaults.
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
# Basic visualization of current directory
|
|
129
|
+
python grource.py visualize
|
|
130
|
+
|
|
131
|
+
# Visualize specific repository
|
|
132
|
+
python grource.py visualize --repo-path /path/to/repo
|
|
133
|
+
|
|
134
|
+
# Create video output
|
|
135
|
+
python grource.py visualize --output output.mp4
|
|
136
|
+
|
|
137
|
+
# Fast visualization with custom title
|
|
138
|
+
python grource.py visualize --seconds-per-day 0.01 --title "My Project"
|
|
139
|
+
|
|
140
|
+
# Hide specific elements
|
|
141
|
+
python grource.py visualize --hide filenames --hide date
|
|
142
|
+
|
|
143
|
+
# Custom resolution and viewport
|
|
144
|
+
python grource.py visualize --resolution 2560x1440 --viewport 1200x1200
|
|
145
|
+
"""
|
|
146
|
+
print("\n" + "=" * 80)
|
|
147
|
+
print("🎬 GOURCE VISUALIZATION 🎬")
|
|
148
|
+
print("=" * 80 + "\n")
|
|
149
|
+
repo_path: Path = Path(repo).expanduser().resolve()
|
|
150
|
+
if not repo_path.exists():
|
|
151
|
+
print(f"❌ Error: Repository path does not exist: {repo_path}")
|
|
152
|
+
raise typer.Exit(1)
|
|
153
|
+
|
|
154
|
+
if not repo_path.joinpath(".git").exists():
|
|
155
|
+
print(f"❌ Error: Not a git repository: {repo_path}")
|
|
156
|
+
raise typer.Exit(1)
|
|
157
|
+
|
|
158
|
+
print(f"📁 Repository: {repo_path}")
|
|
159
|
+
print("⚙️ Configuration:")
|
|
160
|
+
print(f" - Resolution: {resolution}")
|
|
161
|
+
print(f" - Speed: {seconds_per_day} seconds per day")
|
|
162
|
+
print(f" - Auto-skip: {auto_skip_seconds} seconds")
|
|
163
|
+
if output_file:
|
|
164
|
+
print(f" - Output: {output_file}")
|
|
165
|
+
print()
|
|
166
|
+
|
|
167
|
+
gource_exe: Path = get_gource_executable()
|
|
168
|
+
if not gource_exe.exists():
|
|
169
|
+
if platform.system() == "Windows":
|
|
170
|
+
print(f"⚠️ Portable gource not found at {gource_exe}, installing...")
|
|
171
|
+
install_gource_windows()
|
|
172
|
+
# Check again after installation
|
|
173
|
+
if gource_exe.exists():
|
|
174
|
+
print(f"✅ Gource installed successfully at: {gource_exe}")
|
|
175
|
+
gource_cmd: str = str(gource_exe)
|
|
176
|
+
else:
|
|
177
|
+
print("❌ Installation failed, falling back to system gource")
|
|
178
|
+
gource_cmd = "gource"
|
|
179
|
+
else:
|
|
180
|
+
gource_cmd = "gource"
|
|
181
|
+
print(f"⚠️ Portable gource not found at {gource_exe}, using system gource")
|
|
182
|
+
else:
|
|
183
|
+
gource_cmd = str(gource_exe)
|
|
184
|
+
|
|
185
|
+
cmd: list[str] = [gource_cmd, str(repo_path)]
|
|
186
|
+
|
|
187
|
+
cmd.extend(["--seconds-per-day", str(seconds_per_day)])
|
|
188
|
+
cmd.extend(["--auto-skip-seconds", str(auto_skip_seconds)])
|
|
189
|
+
|
|
190
|
+
if resolution:
|
|
191
|
+
width, height = resolution.split("x")
|
|
192
|
+
cmd.extend(["-{}x{}".format(width, height)])
|
|
193
|
+
|
|
194
|
+
if title:
|
|
195
|
+
cmd.extend(["--title", title])
|
|
196
|
+
elif not title and not output_file:
|
|
197
|
+
cmd.extend(["--title", repo_path.name])
|
|
198
|
+
|
|
199
|
+
for hide_item in hide_items:
|
|
200
|
+
cmd.extend(["--hide", hide_item])
|
|
201
|
+
|
|
202
|
+
if key_items:
|
|
203
|
+
cmd.append("--key")
|
|
204
|
+
|
|
205
|
+
if fullscreen and not output_file:
|
|
206
|
+
cmd.append("--fullscreen")
|
|
207
|
+
|
|
208
|
+
if viewport:
|
|
209
|
+
cmd.extend(["--viewport", viewport])
|
|
210
|
+
|
|
211
|
+
if start_date:
|
|
212
|
+
cmd.extend(["--start-date", start_date])
|
|
213
|
+
|
|
214
|
+
if stop_date:
|
|
215
|
+
cmd.extend(["--stop-date", stop_date])
|
|
216
|
+
|
|
217
|
+
if user_image_dir and user_image_dir.exists():
|
|
218
|
+
cmd.extend(["--user-image-dir", str(user_image_dir)])
|
|
219
|
+
|
|
220
|
+
if max_files > 0:
|
|
221
|
+
cmd.extend(["--max-files", str(max_files)])
|
|
222
|
+
|
|
223
|
+
cmd.extend(["--max-file-lag", str(max_file_lag)])
|
|
224
|
+
|
|
225
|
+
if file_idle_time > 0:
|
|
226
|
+
cmd.extend(["--file-idle-time", str(file_idle_time)])
|
|
227
|
+
|
|
228
|
+
cmd.extend(["--background-colour", background_color])
|
|
229
|
+
cmd.extend(["--font-size", str(font_size)])
|
|
230
|
+
cmd.extend(["--camera-mode", camera_mode])
|
|
231
|
+
|
|
232
|
+
if output_file:
|
|
233
|
+
cmd.extend(["-r", str(framerate)])
|
|
234
|
+
if platform.system() == "Windows":
|
|
235
|
+
cmd.extend(["-o", "-"])
|
|
236
|
+
ffmpeg_cmd: list[str] = [
|
|
237
|
+
"ffmpeg",
|
|
238
|
+
"-y",
|
|
239
|
+
"-r", str(framerate),
|
|
240
|
+
"-f", "image2pipe",
|
|
241
|
+
"-vcodec", "ppm",
|
|
242
|
+
"-i", "-",
|
|
243
|
+
"-vcodec", "libx264",
|
|
244
|
+
"-preset", "medium",
|
|
245
|
+
"-pix_fmt", "yuv420p",
|
|
246
|
+
"-crf", "23",
|
|
247
|
+
str(output_file),
|
|
248
|
+
]
|
|
249
|
+
print("🎥 Rendering video...")
|
|
250
|
+
print(f" Command: {' '.join(cmd)} | {' '.join(ffmpeg_cmd)}")
|
|
251
|
+
print()
|
|
252
|
+
try:
|
|
253
|
+
gource_proc: subprocess.Popen[bytes] = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
254
|
+
ffmpeg_proc: subprocess.Popen[bytes] = subprocess.Popen(ffmpeg_cmd, stdin=gource_proc.stdout)
|
|
255
|
+
if gource_proc.stdout:
|
|
256
|
+
gource_proc.stdout.close()
|
|
257
|
+
ffmpeg_proc.communicate()
|
|
258
|
+
print(f"\n✅ Video saved to: {output_file}")
|
|
259
|
+
except subprocess.CalledProcessError as e:
|
|
260
|
+
print(f"❌ Error during video rendering: {e}")
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
except FileNotFoundError:
|
|
263
|
+
print("❌ Error: ffmpeg not found. Please install ffmpeg to create video output.")
|
|
264
|
+
print(" Download from: https://ffmpeg.org/download.html")
|
|
265
|
+
raise typer.Exit(1)
|
|
266
|
+
else:
|
|
267
|
+
cmd.extend(["-o", "-"])
|
|
268
|
+
ffmpeg_cmd = [
|
|
269
|
+
"ffmpeg",
|
|
270
|
+
"-y",
|
|
271
|
+
"-r", str(framerate),
|
|
272
|
+
"-f", "image2pipe",
|
|
273
|
+
"-vcodec", "ppm",
|
|
274
|
+
"-i", "-",
|
|
275
|
+
"-vcodec", "libx264",
|
|
276
|
+
"-preset", "medium",
|
|
277
|
+
"-pix_fmt", "yuv420p",
|
|
278
|
+
"-crf", "23",
|
|
279
|
+
str(output_file),
|
|
280
|
+
]
|
|
281
|
+
print("🎥 Rendering video...")
|
|
282
|
+
print(f" Command: {' '.join(cmd)} | {' '.join(ffmpeg_cmd)}")
|
|
283
|
+
print()
|
|
284
|
+
try:
|
|
285
|
+
gource_proc = subprocess.Popen(cmd, stdout=subprocess.PIPE)
|
|
286
|
+
ffmpeg_proc = subprocess.Popen(ffmpeg_cmd, stdin=gource_proc.stdout)
|
|
287
|
+
if gource_proc.stdout:
|
|
288
|
+
gource_proc.stdout.close()
|
|
289
|
+
ffmpeg_proc.communicate()
|
|
290
|
+
print(f"\n✅ Video saved to: {output_file}")
|
|
291
|
+
except subprocess.CalledProcessError as e:
|
|
292
|
+
print(f"❌ Error during video rendering: {e}")
|
|
293
|
+
raise typer.Exit(1)
|
|
294
|
+
except FileNotFoundError:
|
|
295
|
+
print("❌ Error: ffmpeg not found. Please install ffmpeg to create video output.")
|
|
296
|
+
raise typer.Exit(1)
|
|
297
|
+
else:
|
|
298
|
+
print("🎬 Launching interactive visualization...")
|
|
299
|
+
print(f" Command: {' '.join(cmd)}")
|
|
300
|
+
print()
|
|
301
|
+
try:
|
|
302
|
+
subprocess.run(cmd, check=True)
|
|
303
|
+
except subprocess.CalledProcessError as e:
|
|
304
|
+
print(f"❌ Error running gource: {e}")
|
|
305
|
+
raise typer.Exit(1)
|
|
306
|
+
except FileNotFoundError:
|
|
307
|
+
print("❌ Error: gource not found. Please install gource first.")
|
|
308
|
+
if platform.system() == "Windows":
|
|
309
|
+
print(" Run: uv run python src/machineconfig/scripts/python/grource.py install")
|
|
310
|
+
else:
|
|
311
|
+
print(" For Linux/Mac, use your package manager:")
|
|
312
|
+
print(" - Ubuntu/Debian: sudo apt install gource")
|
|
313
|
+
print(" - macOS: brew install gource")
|
|
314
|
+
print(" - Fedora: sudo dnf install gource")
|
|
315
|
+
raise typer.Exit(1)
|
|
316
|
+
|
|
317
|
+
print("\n" + "=" * 80)
|
|
318
|
+
print("✅ VISUALIZATION COMPLETED")
|
|
319
|
+
print("=" * 80)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def install(
|
|
323
|
+
version: Optional[str] = typer.Option("0.53", "--version", "-v", help="Gource version to install"),
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Install portable Gource on Windows (no admin privileges required)."""
|
|
326
|
+
if platform.system() == "Windows":
|
|
327
|
+
install_gource_windows(version=version)
|
|
328
|
+
else:
|
|
329
|
+
print(f"❌ Portable installer currently supports Windows only. Current OS: {platform.system()}")
|
|
330
|
+
print("For Linux/Mac, please use your package manager:")
|
|
331
|
+
print(" - Ubuntu/Debian: sudo apt install gource")
|
|
332
|
+
print(" - macOS: brew install gource")
|
|
333
|
+
print(" - Fedora: sudo dnf install gource")
|
|
334
|
+
raise typer.Exit(1)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
if __name__ == "__main__":
|
|
338
|
+
app = typer.Typer(help="Gource visualization tool for git repositories")
|
|
339
|
+
app.command()(install)
|
|
340
|
+
app.command()(visualize)
|
|
341
|
+
app()
|
|
@@ -182,7 +182,7 @@ Set-Service -Name sshd -StartupType 'Automatic'"""
|
|
|
182
182
|
console.print(Panel("💾 [bold bright_cyan]DATA RETRIEVAL[/bold bright_cyan]\n[italic]Backup restoration[/italic]", border_style="bright_cyan"))
|
|
183
183
|
console.print("🔧 Retrieving backup data", style="bold cyan")
|
|
184
184
|
try:
|
|
185
|
-
from machineconfig.scripts.python.devops_backup_retrieve import main_backup_retrieve
|
|
185
|
+
from machineconfig.scripts.python.devops_helpers.devops_backup_retrieve import main_backup_retrieve
|
|
186
186
|
main_backup_retrieve(direction="RETRIEVE")
|
|
187
187
|
console.print("✅ Backup data retrieved successfully", style="bold green")
|
|
188
188
|
except Exception as e:
|
|
@@ -5,128 +5,130 @@ in the event that username@github.com is not mentioned in the remote url.
|
|
|
5
5
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
from pathlib import Path
|
|
9
9
|
from typing import Annotated, Optional
|
|
10
10
|
import typer
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
|
|
14
13
|
app = typer.Typer(help="� Manage development repositories", no_args_is_help=True)
|
|
15
14
|
sync_app = typer.Typer(help="� Manage repository specifications and syncing", no_args_is_help=True)
|
|
16
15
|
app.add_typer(sync_app, name="sync", help="� Sync repositories using saved specs")
|
|
17
16
|
|
|
18
17
|
|
|
19
|
-
DirectoryArgument = Annotated[
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
]
|
|
23
|
-
RecursiveOption = Annotated[
|
|
24
|
-
bool,
|
|
25
|
-
typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories."),
|
|
26
|
-
]
|
|
27
|
-
NoSyncOption = Annotated[
|
|
28
|
-
bool,
|
|
29
|
-
typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls."),
|
|
30
|
-
]
|
|
31
|
-
CloudOption = Annotated[
|
|
32
|
-
Optional[str],
|
|
33
|
-
typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote."),
|
|
34
|
-
]
|
|
35
|
-
|
|
18
|
+
DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Folder containing repos or the specs JSON file to use.")]
|
|
19
|
+
RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
|
|
20
|
+
NoSyncOption = Annotated[bool, typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls.")]
|
|
21
|
+
CloudOption = Annotated[Optional[str], typer.Option("--cloud", "-c", help="☁️ Upload to or download from this cloud remote.")]
|
|
36
22
|
|
|
37
23
|
|
|
38
24
|
@app.command(no_args_is_help=True)
|
|
39
|
-
def push(directory: DirectoryArgument = None,
|
|
40
|
-
recursive: RecursiveOption = False,
|
|
41
|
-
no_sync: NoSyncOption = False,
|
|
42
|
-
) -> None:
|
|
25
|
+
def push(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
43
26
|
"""🚀 Push changes across repositories."""
|
|
44
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
27
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
28
|
+
|
|
45
29
|
git_operations(directory, pull=False, commit=False, push=True, recursive=recursive, no_sync=no_sync)
|
|
30
|
+
|
|
31
|
+
|
|
46
32
|
@app.command(no_args_is_help=True)
|
|
47
|
-
def pull(
|
|
48
|
-
directory: DirectoryArgument = None,
|
|
49
|
-
recursive: RecursiveOption = False,
|
|
50
|
-
no_sync: NoSyncOption = False,
|
|
51
|
-
) -> None:
|
|
33
|
+
def pull(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
52
34
|
"""⬇️ Pull changes across repositories."""
|
|
53
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
35
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
36
|
+
|
|
54
37
|
git_operations(directory, pull=True, commit=False, push=False, recursive=recursive, no_sync=no_sync)
|
|
38
|
+
|
|
39
|
+
|
|
55
40
|
@app.command(no_args_is_help=True)
|
|
56
|
-
def commit(
|
|
57
|
-
directory: DirectoryArgument = None,
|
|
58
|
-
recursive: RecursiveOption = False,
|
|
59
|
-
no_sync: NoSyncOption = False,
|
|
60
|
-
) -> None:
|
|
41
|
+
def commit(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
61
42
|
"""💾 Commit changes across repositories."""
|
|
62
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
43
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
44
|
+
|
|
63
45
|
git_operations(directory, pull=False, commit=True, push=False, recursive=recursive, no_sync=no_sync)
|
|
46
|
+
|
|
47
|
+
|
|
64
48
|
@app.command(no_args_is_help=True)
|
|
65
|
-
def cleanup(
|
|
66
|
-
directory: DirectoryArgument = None,
|
|
67
|
-
recursive: RecursiveOption = False,
|
|
68
|
-
no_sync: NoSyncOption = False,
|
|
69
|
-
) -> None:
|
|
49
|
+
def cleanup(directory: DirectoryArgument = None, recursive: RecursiveOption = False, no_sync: NoSyncOption = False) -> None:
|
|
70
50
|
"""🔄 Pull, commit, and push changes across repositories."""
|
|
71
|
-
from machineconfig.scripts.python.repos_helper import git_operations
|
|
51
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import git_operations
|
|
52
|
+
|
|
72
53
|
git_operations(directory, pull=True, commit=True, push=True, recursive=recursive, no_sync=no_sync)
|
|
73
54
|
|
|
74
55
|
|
|
75
56
|
@sync_app.command(no_args_is_help=True)
|
|
76
|
-
def capture(
|
|
77
|
-
directory: DirectoryArgument = None,
|
|
78
|
-
cloud: CloudOption = None,
|
|
79
|
-
) -> None:
|
|
57
|
+
def capture(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
80
58
|
"""📝 Record repositories into a repos.json specification."""
|
|
81
|
-
from machineconfig.scripts.python.repos_helper import
|
|
82
|
-
print_banner()
|
|
59
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import resolve_directory
|
|
83
60
|
repos_root = resolve_directory(directory)
|
|
84
|
-
from machineconfig.scripts.python.repos_helper_record import main as record_repos
|
|
61
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_record import main as record_repos
|
|
62
|
+
|
|
85
63
|
save_path = record_repos(repos_root=repos_root)
|
|
86
64
|
from machineconfig.utils.path_extended import PathExtended
|
|
65
|
+
|
|
87
66
|
if cloud is not None:
|
|
88
67
|
PathExtended(save_path).to_cloud(rel2home=True, cloud=cloud)
|
|
68
|
+
|
|
69
|
+
|
|
89
70
|
@sync_app.command(no_args_is_help=True)
|
|
90
|
-
def clone(
|
|
91
|
-
directory: DirectoryArgument = None,
|
|
92
|
-
cloud: CloudOption = None,
|
|
93
|
-
) -> None:
|
|
71
|
+
def clone(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
94
72
|
"""📥 Clone repositories described by a repos.json specification."""
|
|
95
|
-
from machineconfig.scripts.python.repos_helper import
|
|
96
|
-
|
|
73
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
74
|
+
|
|
75
|
+
|
|
97
76
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=False)
|
|
98
77
|
|
|
99
78
|
|
|
100
79
|
@sync_app.command(name="checkout-to-commit", no_args_is_help=True)
|
|
101
|
-
def checkout_command(
|
|
102
|
-
directory: DirectoryArgument = None,
|
|
103
|
-
cloud: CloudOption = None,
|
|
104
|
-
) -> None:
|
|
80
|
+
def checkout_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
105
81
|
"""🔀 Check out specific commits listed in the specification."""
|
|
106
|
-
from machineconfig.scripts.python.repos_helper import
|
|
107
|
-
|
|
82
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
83
|
+
|
|
84
|
+
|
|
108
85
|
clone_from_specs(directory, cloud, checkout_branch_flag=False, checkout_commit_flag=True)
|
|
109
86
|
|
|
110
87
|
|
|
111
88
|
@sync_app.command(name="checkout-to-branch", no_args_is_help=True)
|
|
112
|
-
def checkout_to_branch_command(
|
|
113
|
-
directory: DirectoryArgument = None,
|
|
114
|
-
cloud: CloudOption = None,
|
|
115
|
-
) -> None:
|
|
89
|
+
def checkout_to_branch_command(directory: DirectoryArgument = None, cloud: CloudOption = None) -> None:
|
|
116
90
|
"""🔀 Check out to the main branch defined in the specification."""
|
|
117
|
-
from machineconfig.scripts.python.repos_helper import
|
|
118
|
-
print_banner()
|
|
91
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper import clone_from_specs
|
|
119
92
|
clone_from_specs(directory, cloud, checkout_branch_flag=True, checkout_commit_flag=False)
|
|
120
93
|
|
|
121
94
|
|
|
122
95
|
@app.command(no_args_is_help=True)
|
|
123
|
-
def analyze(
|
|
124
|
-
directory: DirectoryArgument = None,
|
|
125
|
-
) -> None:
|
|
96
|
+
def analyze(directory: DirectoryArgument = None) -> None:
|
|
126
97
|
"""📊 Analyze repository development over time."""
|
|
127
|
-
from machineconfig.scripts.python.repos_helper import print_banner
|
|
128
|
-
print_banner()
|
|
129
98
|
repo_path = directory if directory is not None else "."
|
|
130
|
-
from machineconfig.scripts.python.count_lines_frontend import analyze_repo_development
|
|
99
|
+
from machineconfig.scripts.python.repos_helpers.count_lines_frontend import analyze_repo_development
|
|
100
|
+
|
|
131
101
|
analyze_repo_development(repo_path=repo_path)
|
|
132
102
|
|
|
103
|
+
|
|
104
|
+
@app.command(no_args_is_help=True)
|
|
105
|
+
def viz(
|
|
106
|
+
repo: str = typer.Option(Path.cwd().__str__(), "--repo", "-r", help="Path to git repository to visualize"),
|
|
107
|
+
output_file: Optional[Path] = typer.Option(None, "--output", "-o", help="Output video file (e.g., output.mp4). If specified, gource will render to video."),
|
|
108
|
+
resolution: str = typer.Option("1920x1080", "--resolution", "-res", help="Video resolution (e.g., 1920x1080, 1280x720)"),
|
|
109
|
+
seconds_per_day: float = typer.Option(0.1, "--seconds-per-day", "-spd", help="Speed of simulation (lower = faster)"),
|
|
110
|
+
auto_skip_seconds: float = typer.Option(1.0, "--auto-skip-seconds", "-as", help="Skip to next entry if nothing happens for X seconds"),
|
|
111
|
+
title: Optional[str] = typer.Option(None, "--title", "-t", help="Title for the visualization"),
|
|
112
|
+
hide_items: list[str] = typer.Option([], "--hide", "-h", help="Items to hide: bloom, date, dirnames, files, filenames, mouse, progress, root, tree, users, usernames"),
|
|
113
|
+
key_items: bool = typer.Option(False, "--key", "-k", help="Show file extension key"),
|
|
114
|
+
fullscreen: bool = typer.Option(False, "--fullscreen", "-f", help="Run in fullscreen mode"),
|
|
115
|
+
viewport: Optional[str] = typer.Option(None, "--viewport", "-v", help="Camera viewport (e.g., '1000x1000')"),
|
|
116
|
+
start_date: Optional[str] = typer.Option(None, "--start-date", help="Start date (YYYY-MM-DD)"),
|
|
117
|
+
stop_date: Optional[str] = typer.Option(None, "--stop-date", help="Stop date (YYYY-MM-DD)"),
|
|
118
|
+
user_image_dir: Optional[Path] = typer.Option(None, "--user-image-dir", help="Directory with user avatar images"),
|
|
119
|
+
max_files: int = typer.Option(0, "--max-files", help="Maximum number of files to show (0 = no limit)"),
|
|
120
|
+
max_file_lag: float = typer.Option(5.0, "--max-file-lag", help="Max time files remain on screen after last change"),
|
|
121
|
+
file_idle_time: int = typer.Option(0, "--file-idle-time", help="Time in seconds files remain idle before being removed"),
|
|
122
|
+
framerate: int = typer.Option(60, "--framerate", help="Frames per second for video output"),
|
|
123
|
+
background_color: str = typer.Option("000000", "--background-color", help="Background color in hex (e.g., 000000 for black)"),
|
|
124
|
+
font_size: int = typer.Option(22, "--font-size", help="Font size"),
|
|
125
|
+
camera_mode: str = typer.Option("overview", "--camera-mode", help="Camera mode: overview or track"),
|
|
126
|
+
) -> None:
|
|
127
|
+
"""🎬 Visualize repository activity using Gource."""
|
|
128
|
+
from machineconfig.scripts.python.helpers_repos.grource import visualize
|
|
129
|
+
visualize(repo=repo, output_file=output_file, resolution=resolution, seconds_per_day=seconds_per_day,
|
|
130
|
+
auto_skip_seconds=auto_skip_seconds, title=title, hide_items=hide_items, key_items=key_items,
|
|
131
|
+
fullscreen=fullscreen, viewport=viewport, start_date=start_date, stop_date=stop_date,
|
|
132
|
+
user_image_dir=user_image_dir, max_files=max_files, max_file_lag=max_file_lag,
|
|
133
|
+
file_idle_time=file_idle_time, framerate=framerate, background_color=background_color,
|
|
134
|
+
font_size=font_size, camera_mode=camera_mode)
|
machineconfig/scripts/python/{count_lines_frontend.py → repos_helpers/count_lines_frontend.py}
RENAMED
|
@@ -3,7 +3,7 @@ import typer
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
def analyze_repo_development(repo_path: str = typer.Argument(..., help="Path to the git repository")):
|
|
6
|
-
from machineconfig.scripts.python import count_lines
|
|
6
|
+
from machineconfig.scripts.python.repos_helpers import count_lines
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
count_lines_path = Path(count_lines.__file__)
|
|
9
9
|
# --project $HOME/code/machineconfig --group plot
|
|
@@ -8,14 +8,6 @@ from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
def print_banner() -> None:
|
|
13
|
-
typer.echo("\n" + "=" * 50)
|
|
14
|
-
typer.echo("📂 Welcome to the Repository Manager")
|
|
15
|
-
typer.echo("=" * 50 + "\n")
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
11
|
def resolve_directory(directory: Optional[str]) -> Path:
|
|
20
12
|
if directory is None:
|
|
21
13
|
directory = Path.cwd().as_posix()
|
|
@@ -30,10 +22,10 @@ def git_operations(
|
|
|
30
22
|
recursive: bool,
|
|
31
23
|
no_sync: bool,
|
|
32
24
|
) -> None:
|
|
33
|
-
|
|
25
|
+
|
|
34
26
|
repos_root = resolve_directory(directory)
|
|
35
27
|
auto_sync = not no_sync
|
|
36
|
-
from machineconfig.scripts.python.repos_helper_action import perform_git_operations
|
|
28
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_action import perform_git_operations
|
|
37
29
|
from machineconfig.utils.path_extended import PathExtended
|
|
38
30
|
perform_git_operations(
|
|
39
31
|
repos_root=PathExtended(repos_root),
|
|
@@ -73,10 +65,10 @@ def clone_from_specs(
|
|
|
73
65
|
checkout_branch_flag: bool,
|
|
74
66
|
checkout_commit_flag: bool,
|
|
75
67
|
) -> None:
|
|
76
|
-
|
|
68
|
+
|
|
77
69
|
typer.echo("\n📥 Cloning or checking out repositories...")
|
|
78
70
|
spec_path = resolve_spec_path(directory, cloud)
|
|
79
|
-
from machineconfig.scripts.python.repos_helper_clone import clone_repos
|
|
71
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_clone import clone_repos
|
|
80
72
|
clone_repos(
|
|
81
73
|
spec_path=spec_path,
|
|
82
74
|
preferred_remote=None,
|
machineconfig/scripts/python/{repos_helper_action.py → repos_helpers/repos_helper_action.py}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from machineconfig.utils.path_extended import PathExtended
|
|
2
2
|
from machineconfig.utils.accessories import randstr
|
|
3
|
-
from machineconfig.scripts.python.repos_helper_update import update_repository
|
|
3
|
+
from machineconfig.scripts.python.repos_helpers.repos_helper_update import update_repository
|
|
4
4
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
from dataclasses import dataclass
|
|
@@ -41,7 +41,7 @@ def create_from_function(
|
|
|
41
41
|
|
|
42
42
|
# ========================= choosing function to run
|
|
43
43
|
if function is None or function.strip() == "":
|
|
44
|
-
from machineconfig.scripts.python.fire_jobs_route_helper import choose_function_or_lines
|
|
44
|
+
from machineconfig.scripts.python.helpers_fire_command.fire_jobs_route_helper import choose_function_or_lines
|
|
45
45
|
choice_function, choice_file, _kwargs_dict = choose_function_or_lines(choice_file, kwargs_dict={})
|
|
46
46
|
else:
|
|
47
47
|
choice_function = function
|
|
File without changes
|