machineconfig 6.81__py3-none-any.whl → 6.82__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.
- machineconfig/scripts/python/helpers_repos/action.py +101 -270
- machineconfig/scripts/python/helpers_repos/action_helper.py +150 -0
- {machineconfig-6.81.dist-info → machineconfig-6.82.dist-info}/METADATA +1 -1
- {machineconfig-6.81.dist-info → machineconfig-6.82.dist-info}/RECORD +7 -6
- {machineconfig-6.81.dist-info → machineconfig-6.82.dist-info}/WHEEL +0 -0
- {machineconfig-6.81.dist-info → machineconfig-6.82.dist-info}/entry_points.txt +0 -0
- {machineconfig-6.81.dist-info → machineconfig-6.82.dist-info}/top_level.txt +0 -0
|
@@ -1,60 +1,13 @@
|
|
|
1
|
+
from machineconfig.scripts.python.helpers_repos.action_helper import GitAction, GitOperationResult, GitOperationSummary, print_git_operations_summary
|
|
1
2
|
from machineconfig.utils.path_extended import PathExtended
|
|
2
3
|
from machineconfig.utils.accessories import randstr
|
|
3
4
|
from machineconfig.scripts.python.helpers_repos.update import update_repository
|
|
4
5
|
|
|
5
|
-
from typing import Optional
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
from typing import Optional, Dict, Any, List, cast
|
|
7
|
+
import concurrent.futures
|
|
8
|
+
import os
|
|
8
9
|
|
|
9
10
|
from rich import print as pprint
|
|
10
|
-
from rich.table import Table
|
|
11
|
-
from rich.panel import Panel
|
|
12
|
-
from rich.columns import Columns
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class GitAction(Enum):
|
|
16
|
-
commit = "commit"
|
|
17
|
-
push = "push"
|
|
18
|
-
pull = "pull"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
@dataclass
|
|
22
|
-
class GitOperationResult:
|
|
23
|
-
"""Result of a git operation on a single repository."""
|
|
24
|
-
repo_path: PathExtended
|
|
25
|
-
action: str
|
|
26
|
-
success: bool
|
|
27
|
-
message: str
|
|
28
|
-
is_git_repo: bool = True
|
|
29
|
-
had_changes: bool = False
|
|
30
|
-
remote_count: int = 0
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
@dataclass
|
|
34
|
-
class GitOperationSummary:
|
|
35
|
-
"""Summary of all git operations performed."""
|
|
36
|
-
# Basic statistics
|
|
37
|
-
total_paths_processed: int = 0
|
|
38
|
-
git_repos_found: int = 0
|
|
39
|
-
non_git_paths: int = 0
|
|
40
|
-
|
|
41
|
-
# Per-operation statistics
|
|
42
|
-
commits_attempted: int = 0
|
|
43
|
-
commits_successful: int = 0
|
|
44
|
-
commits_no_changes: int = 0
|
|
45
|
-
commits_failed: int = 0
|
|
46
|
-
|
|
47
|
-
pulls_attempted: int = 0
|
|
48
|
-
pulls_successful: int = 0
|
|
49
|
-
pulls_failed: int = 0
|
|
50
|
-
|
|
51
|
-
pushes_attempted: int = 0
|
|
52
|
-
pushes_successful: int = 0
|
|
53
|
-
pushes_failed: int = 0
|
|
54
|
-
|
|
55
|
-
def __post_init__(self):
|
|
56
|
-
self.failed_operations: list[GitOperationResult] = []
|
|
57
|
-
self.repos_without_remotes: list[PathExtended] = []
|
|
58
11
|
|
|
59
12
|
|
|
60
13
|
def git_action(path: PathExtended, action: GitAction, mess: Optional[str], r: bool, auto_uv_sync: bool) -> GitOperationResult:
|
|
@@ -76,16 +29,10 @@ def git_action(path: PathExtended, action: GitAction, mess: Optional[str], r: bo
|
|
|
76
29
|
action=action.value,
|
|
77
30
|
success=all_successful,
|
|
78
31
|
message=f"Recursive operation: {len([r for r in results if r.success])}/{len(results)} succeeded",
|
|
79
|
-
is_git_repo=False
|
|
32
|
+
is_git_repo=False,
|
|
80
33
|
)
|
|
81
34
|
else:
|
|
82
|
-
return GitOperationResult(
|
|
83
|
-
repo_path=path,
|
|
84
|
-
action=action.value,
|
|
85
|
-
success=False,
|
|
86
|
-
message="Not a git repository",
|
|
87
|
-
is_git_repo=False
|
|
88
|
-
)
|
|
35
|
+
return GitOperationResult(repo_path=path, action=action.value, success=False, message="Not a git repository", is_git_repo=False)
|
|
89
36
|
|
|
90
37
|
print(f">>>>>>>>> 🔧{action} - {path}")
|
|
91
38
|
remote_count = len(repo.remotes)
|
|
@@ -106,30 +53,19 @@ def git_action(path: PathExtended, action: GitAction, mess: Optional[str], r: bo
|
|
|
106
53
|
success=True,
|
|
107
54
|
message=f"Committed changes with message: {mess}",
|
|
108
55
|
had_changes=True,
|
|
109
|
-
remote_count=remote_count
|
|
56
|
+
remote_count=remote_count,
|
|
110
57
|
)
|
|
111
58
|
else:
|
|
112
59
|
print("ℹ️ No changes to commit")
|
|
113
60
|
return GitOperationResult(
|
|
114
|
-
repo_path=path,
|
|
115
|
-
action=action.value,
|
|
116
|
-
success=True,
|
|
117
|
-
message="No changes to commit",
|
|
118
|
-
had_changes=False,
|
|
119
|
-
remote_count=remote_count
|
|
61
|
+
repo_path=path, action=action.value, success=True, message="No changes to commit", had_changes=False, remote_count=remote_count
|
|
120
62
|
)
|
|
121
63
|
|
|
122
64
|
elif action == GitAction.push:
|
|
123
65
|
if not repo.remotes:
|
|
124
66
|
print("⚠️ No remotes configured for push")
|
|
125
|
-
return GitOperationResult(
|
|
126
|
-
|
|
127
|
-
action=action.value,
|
|
128
|
-
success=False,
|
|
129
|
-
message="No remotes configured",
|
|
130
|
-
remote_count=0
|
|
131
|
-
)
|
|
132
|
-
|
|
67
|
+
return GitOperationResult(repo_path=path, action=action.value, success=False, message="No remotes configured", remote_count=0)
|
|
68
|
+
|
|
133
69
|
success = True
|
|
134
70
|
failed_remotes = []
|
|
135
71
|
for remote in repo.remotes:
|
|
@@ -141,15 +77,9 @@ def git_action(path: PathExtended, action: GitAction, mess: Optional[str], r: bo
|
|
|
141
77
|
print(f"❌ Failed to push to {remote.name}: {e}")
|
|
142
78
|
failed_remotes.append(f"{remote.name}: {str(e)}")
|
|
143
79
|
success = False
|
|
144
|
-
|
|
80
|
+
|
|
145
81
|
message = "Push successful" if success else f"Push failed for: {', '.join(failed_remotes)}"
|
|
146
|
-
return GitOperationResult(
|
|
147
|
-
repo_path=path,
|
|
148
|
-
action=action.value,
|
|
149
|
-
success=success,
|
|
150
|
-
message=message,
|
|
151
|
-
remote_count=remote_count
|
|
152
|
-
)
|
|
82
|
+
return GitOperationResult(repo_path=path, action=action.value, success=success, message=message, remote_count=remote_count)
|
|
153
83
|
|
|
154
84
|
elif action == GitAction.pull:
|
|
155
85
|
# Use the enhanced update function with uv sync support
|
|
@@ -157,222 +87,123 @@ def git_action(path: PathExtended, action: GitAction, mess: Optional[str], r: bo
|
|
|
157
87
|
update_repository(repo, auto_uv_sync=auto_uv_sync, allow_password_prompt=False)
|
|
158
88
|
print("✅ Pull completed")
|
|
159
89
|
return GitOperationResult(
|
|
160
|
-
repo_path=path,
|
|
161
|
-
action=action.value,
|
|
162
|
-
success=True,
|
|
163
|
-
message="Pull completed successfully",
|
|
164
|
-
remote_count=remote_count
|
|
90
|
+
repo_path=path, action=action.value, success=True, message="Pull completed successfully", remote_count=remote_count
|
|
165
91
|
)
|
|
166
92
|
except Exception as e:
|
|
167
93
|
print(f"❌ Pull failed: {e}")
|
|
168
94
|
return GitOperationResult(
|
|
169
|
-
repo_path=path,
|
|
170
|
-
action=action.value,
|
|
171
|
-
success=False,
|
|
172
|
-
message=f"Pull failed: {str(e)}",
|
|
173
|
-
remote_count=remote_count
|
|
95
|
+
repo_path=path, action=action.value, success=False, message=f"Pull failed: {str(e)}", remote_count=remote_count
|
|
174
96
|
)
|
|
175
97
|
|
|
176
98
|
except Exception as e:
|
|
177
99
|
print(f"❌ Error performing {action} on {path}: {e}")
|
|
178
|
-
return GitOperationResult(
|
|
179
|
-
repo_path=path,
|
|
180
|
-
action=action.value,
|
|
181
|
-
success=False,
|
|
182
|
-
message=f"Error: {str(e)}",
|
|
183
|
-
remote_count=remote_count
|
|
184
|
-
)
|
|
100
|
+
return GitOperationResult(repo_path=path, action=action.value, success=False, message=f"Error: {str(e)}", remote_count=remote_count)
|
|
185
101
|
|
|
186
102
|
# This should never be reached, but just in case
|
|
187
|
-
return GitOperationResult(
|
|
188
|
-
repo_path=path,
|
|
189
|
-
action=action.value,
|
|
190
|
-
success=False,
|
|
191
|
-
message="Unknown error",
|
|
192
|
-
remote_count=remote_count
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def print_git_operations_summary(summary: GitOperationSummary, operations_performed: list[str]) -> None:
|
|
197
|
-
"""Print a detailed summary of git operations with rich formatting and tables."""
|
|
198
|
-
from rich.console import Console
|
|
199
|
-
console = Console()
|
|
200
|
-
|
|
201
|
-
# Main summary panel
|
|
202
|
-
summary_stats = [
|
|
203
|
-
f"Total paths processed: {summary.total_paths_processed}",
|
|
204
|
-
f"Git repositories found: {summary.git_repos_found}",
|
|
205
|
-
f"Non-git paths skipped: {summary.non_git_paths}"
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
console.print(Panel.fit(
|
|
209
|
-
"\n".join(summary_stats),
|
|
210
|
-
title="[bold blue]📊 Git Operations Summary[/bold blue]",
|
|
211
|
-
border_style="blue"
|
|
212
|
-
))
|
|
213
|
-
|
|
214
|
-
# Statistics panels in columns
|
|
215
|
-
stat_panels = []
|
|
216
|
-
|
|
217
|
-
if "commit" in operations_performed:
|
|
218
|
-
commit_stats = [
|
|
219
|
-
f"Attempted: {summary.commits_attempted}",
|
|
220
|
-
f"Successful: {summary.commits_successful}",
|
|
221
|
-
f"No changes: {summary.commits_no_changes}",
|
|
222
|
-
f"Failed: {summary.commits_failed}"
|
|
223
|
-
]
|
|
224
|
-
stat_panels.append(Panel.fit(
|
|
225
|
-
"\n".join(commit_stats),
|
|
226
|
-
title="[bold green]💾 Commit Operations[/bold green]",
|
|
227
|
-
border_style="green"
|
|
228
|
-
))
|
|
229
|
-
|
|
230
|
-
if "pull" in operations_performed:
|
|
231
|
-
pull_stats = [
|
|
232
|
-
f"Attempted: {summary.pulls_attempted}",
|
|
233
|
-
f"Successful: {summary.pulls_successful}",
|
|
234
|
-
f"Failed: {summary.pulls_failed}"
|
|
235
|
-
]
|
|
236
|
-
stat_panels.append(Panel.fit(
|
|
237
|
-
"\n".join(pull_stats),
|
|
238
|
-
title="[bold cyan]⬇️ Pull Operations[/bold cyan]",
|
|
239
|
-
border_style="cyan"
|
|
240
|
-
))
|
|
241
|
-
|
|
242
|
-
if "push" in operations_performed:
|
|
243
|
-
push_stats = [
|
|
244
|
-
f"Attempted: {summary.pushes_attempted}",
|
|
245
|
-
f"Successful: {summary.pushes_successful}",
|
|
246
|
-
f"Failed: {summary.pushes_failed}"
|
|
247
|
-
]
|
|
248
|
-
stat_panels.append(Panel.fit(
|
|
249
|
-
"\n".join(push_stats),
|
|
250
|
-
title="[bold magenta]🚀 Push Operations[/bold magenta]",
|
|
251
|
-
border_style="magenta"
|
|
252
|
-
))
|
|
253
|
-
|
|
254
|
-
if stat_panels:
|
|
255
|
-
console.print(Columns(stat_panels, equal=True, expand=True))
|
|
256
|
-
|
|
257
|
-
# Repositories without remotes warning
|
|
258
|
-
if summary.repos_without_remotes:
|
|
259
|
-
repos_table = Table(title="[bold yellow]⚠️ Repositories Without Remotes[/bold yellow]")
|
|
260
|
-
repos_table.add_column("Repository Name", style="cyan", no_wrap=True)
|
|
261
|
-
repos_table.add_column("Full Path", style="dim")
|
|
262
|
-
|
|
263
|
-
for repo_path in summary.repos_without_remotes:
|
|
264
|
-
repos_table.add_row(repo_path.name, str(repo_path))
|
|
265
|
-
|
|
266
|
-
console.print(repos_table)
|
|
267
|
-
console.print("[yellow]These repositories cannot be pushed to remote servers.[/yellow]")
|
|
268
|
-
elif "push" in operations_performed:
|
|
269
|
-
console.print("[green]✅ All repositories have remote configurations.[/green]")
|
|
270
|
-
|
|
271
|
-
# Failed operations table
|
|
272
|
-
if summary.failed_operations:
|
|
273
|
-
failed_table = Table(title=f"[bold red]❌ Failed Operations ({len(summary.failed_operations)} total)[/bold red]")
|
|
274
|
-
failed_table.add_column("Action", style="bold red", no_wrap=True)
|
|
275
|
-
failed_table.add_column("Repository", style="cyan", no_wrap=True)
|
|
276
|
-
failed_table.add_column("Problem", style="red")
|
|
277
|
-
|
|
278
|
-
# Group failed operations by type for better organization
|
|
279
|
-
failed_by_action = {}
|
|
280
|
-
for failed_op in summary.failed_operations:
|
|
281
|
-
if failed_op.action not in failed_by_action:
|
|
282
|
-
failed_by_action[failed_op.action] = []
|
|
283
|
-
failed_by_action[failed_op.action].append(failed_op)
|
|
284
|
-
|
|
285
|
-
for action, failures in failed_by_action.items():
|
|
286
|
-
for failure in failures:
|
|
287
|
-
repo_name = failure.repo_path.name if failure.is_git_repo else f"{failure.repo_path.name} (not git repo)"
|
|
288
|
-
problem = failure.message if failure.is_git_repo else "Not a git repository"
|
|
289
|
-
failed_table.add_row(action.upper(), repo_name, problem)
|
|
290
|
-
|
|
291
|
-
console.print(failed_table)
|
|
292
|
-
else:
|
|
293
|
-
console.print("[green]✅ All git operations completed successfully![/green]")
|
|
294
|
-
|
|
295
|
-
# Overall success assessment
|
|
296
|
-
total_failed = len(summary.failed_operations)
|
|
297
|
-
total_operations = (summary.commits_attempted + summary.pulls_attempted +
|
|
298
|
-
summary.pushes_attempted)
|
|
299
|
-
|
|
300
|
-
if total_failed == 0 and total_operations > 0:
|
|
301
|
-
console.print(f"\n[bold green]🎉 SUCCESS: All {total_operations} operations completed successfully![/bold green]")
|
|
302
|
-
elif total_operations == 0:
|
|
303
|
-
console.print("\n[blue]📝 No git operations were performed.[/blue]")
|
|
304
|
-
else:
|
|
305
|
-
success_rate = ((total_operations - total_failed) / total_operations * 100) if total_operations > 0 else 0
|
|
306
|
-
if total_failed > 0:
|
|
307
|
-
console.print(f"\n[bold yellow]⚖️ SUMMARY: {total_operations - total_failed}/{total_operations} operations succeeded ({success_rate:.1f}% success rate)[/bold yellow]")
|
|
308
|
-
console.print("[yellow]Review the failed operations table above for details on what needs attention.[/yellow]")
|
|
309
|
-
else:
|
|
310
|
-
console.print(f"\n[bold green]⚖️ SUMMARY: {total_operations}/{total_operations} operations succeeded (100% success rate)[/bold green]")
|
|
103
|
+
return GitOperationResult(repo_path=path, action=action.value, success=False, message="Unknown error", remote_count=remote_count)
|
|
311
104
|
|
|
312
105
|
|
|
313
106
|
def perform_git_operations(repos_root: PathExtended, pull: bool, commit: bool, push: bool, recursive: bool, auto_uv_sync: bool) -> None:
|
|
314
107
|
"""Perform git operations on all repositories and provide detailed summary."""
|
|
315
108
|
print(f"\n🔄 Performing Git actions on repositories @ `{repos_root}`...")
|
|
316
109
|
summary = GitOperationSummary()
|
|
317
|
-
|
|
318
|
-
|
|
110
|
+
# Keep track of which operations we are performing
|
|
111
|
+
operations_performed: List[str] = []
|
|
319
112
|
if pull:
|
|
320
113
|
operations_performed.append("pull")
|
|
321
114
|
if commit:
|
|
322
115
|
operations_performed.append("commit")
|
|
323
116
|
if push:
|
|
324
117
|
operations_performed.append("push")
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
118
|
+
|
|
119
|
+
# Collect all candidate paths first
|
|
120
|
+
paths = list(repos_root.search("*"))
|
|
121
|
+
|
|
122
|
+
def _process_path(a_path: PathExtended) -> Dict[str, Any]:
|
|
123
|
+
"""Worker that processes a single path and returns metadata and results."""
|
|
331
124
|
from git.exc import InvalidGitRepositoryError
|
|
332
125
|
from git.repo import Repo
|
|
333
|
-
|
|
126
|
+
|
|
127
|
+
result_payload: Dict[str, Any] = {"path": a_path, "is_git": False, "results": [], "repo_remotes_count": 0}
|
|
128
|
+
print(f"{('Handling ' + str(a_path)).center(80, '-')}")
|
|
129
|
+
|
|
334
130
|
try:
|
|
335
131
|
repo = Repo(str(a_path), search_parent_directories=False)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
132
|
+
except InvalidGitRepositoryError:
|
|
133
|
+
result_payload["non_git"] = True
|
|
134
|
+
pprint(f"⚠️ Skipping {a_path} because it is not a git repository.")
|
|
135
|
+
return result_payload
|
|
136
|
+
|
|
137
|
+
# It's a git repo
|
|
138
|
+
result_payload["is_git"] = True
|
|
139
|
+
result_payload["repo_remotes_count"] = len(repo.remotes)
|
|
140
|
+
|
|
141
|
+
# Perform configured operations sequentially for this repo (the repo-level work is done concurrently between repos)
|
|
142
|
+
try:
|
|
343
143
|
if pull:
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if result.success:
|
|
347
|
-
summary.pulls_successful += 1
|
|
348
|
-
else:
|
|
349
|
-
summary.pulls_failed += 1
|
|
350
|
-
summary.failed_operations.append(result)
|
|
351
|
-
|
|
144
|
+
r = git_action(path=a_path, action=GitAction.pull, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
|
|
145
|
+
result_payload["results"].append(r)
|
|
352
146
|
if commit:
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if result.success:
|
|
356
|
-
if result.had_changes:
|
|
357
|
-
summary.commits_successful += 1
|
|
358
|
-
else:
|
|
359
|
-
summary.commits_no_changes += 1
|
|
360
|
-
else:
|
|
361
|
-
summary.commits_failed += 1
|
|
362
|
-
summary.failed_operations.append(result)
|
|
363
|
-
|
|
147
|
+
r = git_action(path=a_path, action=GitAction.commit, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
|
|
148
|
+
result_payload["results"].append(r)
|
|
364
149
|
if push:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
150
|
+
r = git_action(path=a_path, action=GitAction.push, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
|
|
151
|
+
result_payload["results"].append(r)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
# Capture any unexpected exception for this path
|
|
154
|
+
pprint(f"❌ Error processing {a_path}: {e}")
|
|
155
|
+
|
|
156
|
+
return result_payload
|
|
157
|
+
|
|
158
|
+
# Choose a reasonable number of workers
|
|
159
|
+
max_workers = min(32, (os.cpu_count() or 1) * 5, len(paths) or 1)
|
|
160
|
+
|
|
161
|
+
# Run the workers in parallel and aggregate results
|
|
162
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as exc:
|
|
163
|
+
future_to_path = {exc.submit(_process_path, p): p for p in paths}
|
|
164
|
+
for fut in concurrent.futures.as_completed(future_to_path):
|
|
165
|
+
payload = fut.result()
|
|
166
|
+
a_path = cast(PathExtended, payload.get("path"))
|
|
167
|
+
summary.total_paths_processed += 1
|
|
168
|
+
|
|
169
|
+
if not payload.get("is_git"):
|
|
170
|
+
summary.non_git_paths += 1
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
# git repo found
|
|
174
|
+
summary.git_repos_found += 1
|
|
175
|
+
if payload.get("repo_remotes_count", 0) == 0:
|
|
176
|
+
summary.repos_without_remotes.append(a_path)
|
|
177
|
+
|
|
178
|
+
for r in payload.get("results", []):
|
|
179
|
+
action_name = r.action if hasattr(r, "action") else ""
|
|
180
|
+
# Pull
|
|
181
|
+
if action_name == "pull":
|
|
182
|
+
summary.pulls_attempted += 1
|
|
183
|
+
if r.success:
|
|
184
|
+
summary.pulls_successful += 1
|
|
185
|
+
else:
|
|
186
|
+
summary.pulls_failed += 1
|
|
187
|
+
summary.failed_operations.append(r)
|
|
188
|
+
# Commit
|
|
189
|
+
elif action_name == "commit":
|
|
190
|
+
summary.commits_attempted += 1
|
|
191
|
+
if r.success:
|
|
192
|
+
if getattr(r, "had_changes", False):
|
|
193
|
+
summary.commits_successful += 1
|
|
194
|
+
else:
|
|
195
|
+
summary.commits_no_changes += 1
|
|
196
|
+
else:
|
|
197
|
+
summary.commits_failed += 1
|
|
198
|
+
summary.failed_operations.append(r)
|
|
199
|
+
# Push
|
|
200
|
+
elif action_name == "push":
|
|
201
|
+
summary.pushes_attempted += 1
|
|
202
|
+
if r.success:
|
|
203
|
+
summary.pushes_successful += 1
|
|
204
|
+
else:
|
|
205
|
+
summary.pushes_failed += 1
|
|
206
|
+
summary.failed_operations.append(r)
|
|
376
207
|
|
|
377
208
|
# Print the detailed summary
|
|
378
|
-
print_git_operations_summary(summary, operations_performed)
|
|
209
|
+
print_git_operations_summary(summary, operations_performed)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from machineconfig.utils.path_extended import PathExtended
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from rich.columns import Columns
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class GitOperationResult:
|
|
14
|
+
"""Result of a git operation on a single repository."""
|
|
15
|
+
repo_path: PathExtended
|
|
16
|
+
action: str
|
|
17
|
+
success: bool
|
|
18
|
+
message: str
|
|
19
|
+
is_git_repo: bool = True
|
|
20
|
+
had_changes: bool = False
|
|
21
|
+
remote_count: int = 0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GitAction(Enum):
|
|
25
|
+
commit = "commit"
|
|
26
|
+
push = "push"
|
|
27
|
+
pull = "pull"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class GitOperationSummary:
|
|
32
|
+
"""Summary of all git operations performed."""
|
|
33
|
+
|
|
34
|
+
# Basic statistics
|
|
35
|
+
total_paths_processed: int = 0
|
|
36
|
+
git_repos_found: int = 0
|
|
37
|
+
non_git_paths: int = 0
|
|
38
|
+
|
|
39
|
+
# Per-operation statistics
|
|
40
|
+
commits_attempted: int = 0
|
|
41
|
+
commits_successful: int = 0
|
|
42
|
+
commits_no_changes: int = 0
|
|
43
|
+
commits_failed: int = 0
|
|
44
|
+
|
|
45
|
+
pulls_attempted: int = 0
|
|
46
|
+
pulls_successful: int = 0
|
|
47
|
+
pulls_failed: int = 0
|
|
48
|
+
|
|
49
|
+
pushes_attempted: int = 0
|
|
50
|
+
pushes_successful: int = 0
|
|
51
|
+
pushes_failed: int = 0
|
|
52
|
+
|
|
53
|
+
def __post_init__(self):
|
|
54
|
+
self.failed_operations: list[GitOperationResult] = []
|
|
55
|
+
self.repos_without_remotes: list[PathExtended] = []
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def print_git_operations_summary(summary: GitOperationSummary, operations_performed: list[str]) -> None:
|
|
59
|
+
"""Print a detailed summary of git operations with rich formatting and tables."""
|
|
60
|
+
from rich.console import Console
|
|
61
|
+
|
|
62
|
+
console = Console()
|
|
63
|
+
|
|
64
|
+
# Main summary panel
|
|
65
|
+
summary_stats = [
|
|
66
|
+
f"Total paths processed: {summary.total_paths_processed}",
|
|
67
|
+
f"Git repositories found: {summary.git_repos_found}",
|
|
68
|
+
f"Non-git paths skipped: {summary.non_git_paths}",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
console.print(Panel.fit("\n".join(summary_stats), title="[bold blue]📊 Git Operations Summary[/bold blue]", border_style="blue"))
|
|
72
|
+
|
|
73
|
+
# Statistics panels in columns
|
|
74
|
+
stat_panels = []
|
|
75
|
+
|
|
76
|
+
if "commit" in operations_performed:
|
|
77
|
+
commit_stats = [
|
|
78
|
+
f"Attempted: {summary.commits_attempted}",
|
|
79
|
+
f"Successful: {summary.commits_successful}",
|
|
80
|
+
f"No changes: {summary.commits_no_changes}",
|
|
81
|
+
f"Failed: {summary.commits_failed}",
|
|
82
|
+
]
|
|
83
|
+
stat_panels.append(Panel.fit("\n".join(commit_stats), title="[bold green]💾 Commit Operations[/bold green]", border_style="green"))
|
|
84
|
+
|
|
85
|
+
if "pull" in operations_performed:
|
|
86
|
+
pull_stats = [f"Attempted: {summary.pulls_attempted}", f"Successful: {summary.pulls_successful}", f"Failed: {summary.pulls_failed}"]
|
|
87
|
+
stat_panels.append(Panel.fit("\n".join(pull_stats), title="[bold cyan]⬇️ Pull Operations[/bold cyan]", border_style="cyan"))
|
|
88
|
+
|
|
89
|
+
if "push" in operations_performed:
|
|
90
|
+
push_stats = [f"Attempted: {summary.pushes_attempted}", f"Successful: {summary.pushes_successful}", f"Failed: {summary.pushes_failed}"]
|
|
91
|
+
stat_panels.append(Panel.fit("\n".join(push_stats), title="[bold magenta]🚀 Push Operations[/bold magenta]", border_style="magenta"))
|
|
92
|
+
|
|
93
|
+
if stat_panels:
|
|
94
|
+
console.print(Columns(stat_panels, equal=True, expand=True))
|
|
95
|
+
|
|
96
|
+
# Repositories without remotes warning
|
|
97
|
+
if summary.repos_without_remotes:
|
|
98
|
+
repos_table = Table(title="[bold yellow]⚠️ Repositories Without Remotes[/bold yellow]")
|
|
99
|
+
repos_table.add_column("Repository Name", style="cyan", no_wrap=True)
|
|
100
|
+
repos_table.add_column("Full Path", style="dim")
|
|
101
|
+
|
|
102
|
+
for repo_path in summary.repos_without_remotes:
|
|
103
|
+
repos_table.add_row(repo_path.name, str(repo_path))
|
|
104
|
+
|
|
105
|
+
console.print(repos_table)
|
|
106
|
+
console.print("[yellow]These repositories cannot be pushed to remote servers.[/yellow]")
|
|
107
|
+
elif "push" in operations_performed:
|
|
108
|
+
console.print("[green]✅ All repositories have remote configurations.[/green]")
|
|
109
|
+
|
|
110
|
+
# Failed operations table
|
|
111
|
+
if summary.failed_operations:
|
|
112
|
+
failed_table = Table(title=f"[bold red]❌ Failed Operations ({len(summary.failed_operations)} total)[/bold red]")
|
|
113
|
+
failed_table.add_column("Action", style="bold red", no_wrap=True)
|
|
114
|
+
failed_table.add_column("Repository", style="cyan", no_wrap=True)
|
|
115
|
+
failed_table.add_column("Problem", style="red")
|
|
116
|
+
|
|
117
|
+
# Group failed operations by type for better organization
|
|
118
|
+
failed_by_action = {}
|
|
119
|
+
for failed_op in summary.failed_operations:
|
|
120
|
+
if failed_op.action not in failed_by_action:
|
|
121
|
+
failed_by_action[failed_op.action] = []
|
|
122
|
+
failed_by_action[failed_op.action].append(failed_op)
|
|
123
|
+
|
|
124
|
+
for action, failures in failed_by_action.items():
|
|
125
|
+
for failure in failures:
|
|
126
|
+
repo_name = failure.repo_path.name if failure.is_git_repo else f"{failure.repo_path.name} (not git repo)"
|
|
127
|
+
problem = failure.message if failure.is_git_repo else "Not a git repository"
|
|
128
|
+
failed_table.add_row(action.upper(), repo_name, problem)
|
|
129
|
+
|
|
130
|
+
console.print(failed_table)
|
|
131
|
+
else:
|
|
132
|
+
console.print("[green]✅ All git operations completed successfully![/green]")
|
|
133
|
+
|
|
134
|
+
# Overall success assessment
|
|
135
|
+
total_failed = len(summary.failed_operations)
|
|
136
|
+
total_operations = summary.commits_attempted + summary.pulls_attempted + summary.pushes_attempted
|
|
137
|
+
|
|
138
|
+
if total_failed == 0 and total_operations > 0:
|
|
139
|
+
console.print(f"\n[bold green]🎉 SUCCESS: All {total_operations} operations completed successfully![/bold green]")
|
|
140
|
+
elif total_operations == 0:
|
|
141
|
+
console.print("\n[blue]📝 No git operations were performed.[/blue]")
|
|
142
|
+
else:
|
|
143
|
+
success_rate = ((total_operations - total_failed) / total_operations * 100) if total_operations > 0 else 0
|
|
144
|
+
if total_failed > 0:
|
|
145
|
+
console.print(
|
|
146
|
+
f"\n[bold yellow]⚖️ SUMMARY: {total_operations - total_failed}/{total_operations} operations succeeded ({success_rate:.1f}% success rate)[/bold yellow]"
|
|
147
|
+
)
|
|
148
|
+
console.print("[yellow]Review the failed operations table above for details on what needs attention.[/yellow]")
|
|
149
|
+
else:
|
|
150
|
+
console.print(f"\n[bold green]⚖️ SUMMARY: {total_operations}/{total_operations} operations succeeded (100% success rate)[/bold green]")
|
|
@@ -219,7 +219,8 @@ machineconfig/scripts/python/helpers_navigator/command_tree.py,sha256=Kw673rk7Qb
|
|
|
219
219
|
machineconfig/scripts/python/helpers_navigator/data_models.py,sha256=62CIZ01rfCD2mKX_ihEVuhNzZ8FDnRSEIIQuyKOtmOg,533
|
|
220
220
|
machineconfig/scripts/python/helpers_navigator/main_app.py,sha256=R1vOBMUKaiFHOg0D4SzTcu48Wsc8lO0LKAzrZxCsCtg,8849
|
|
221
221
|
machineconfig/scripts/python/helpers_navigator/search_bar.py,sha256=kDi8Jhxap8wdm7YpDBtfhwcPnSqDPFrV2LqbcSBWMT4,414
|
|
222
|
-
machineconfig/scripts/python/helpers_repos/action.py,sha256=
|
|
222
|
+
machineconfig/scripts/python/helpers_repos/action.py,sha256=8je051kpGZ7A_GRsQyWKhPZ8xVW7tSm4bnPu6VjxaXk,9755
|
|
223
|
+
machineconfig/scripts/python/helpers_repos/action_helper.py,sha256=XRCtkGkNrxauqUd9qkxtfJt02Mx2gejSYDLL0jyWn24,6176
|
|
223
224
|
machineconfig/scripts/python/helpers_repos/clone.py,sha256=UULEG5xJuXlPGU0nqXH6U45jA9DOFqLw8B4iPytCwOQ,5471
|
|
224
225
|
machineconfig/scripts/python/helpers_repos/cloud_repo_sync.py,sha256=y3FC57KBKY0w2MwYb6P8uJus_S0ZYalTHvTWsdu6I34,10450
|
|
225
226
|
machineconfig/scripts/python/helpers_repos/count_lines.py,sha256=Q5c7b-DxvTlQmljoic7niTuiAVyFlwYvkVQ7uRJHiTo,16009
|
|
@@ -433,8 +434,8 @@ machineconfig/utils/schemas/installer/installer_types.py,sha256=QClRY61QaduBPJoS
|
|
|
433
434
|
machineconfig/utils/schemas/layouts/layout_types.py,sha256=TcqlZdGVoH8htG5fHn1KWXhRdPueAcoyApppZsPAPto,2020
|
|
434
435
|
machineconfig/utils/schemas/repos/repos_types.py,sha256=ECVr-3IVIo8yjmYmVXX2mnDDN1SLSwvQIhx4KDDQHBQ,405
|
|
435
436
|
machineconfig/utils/ssh_utils/utils.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
436
|
-
machineconfig-6.
|
|
437
|
-
machineconfig-6.
|
|
438
|
-
machineconfig-6.
|
|
439
|
-
machineconfig-6.
|
|
440
|
-
machineconfig-6.
|
|
437
|
+
machineconfig-6.82.dist-info/METADATA,sha256=q-NJd5CHqxz35g6iTMIjy12YkOrRWiBgoZuMMteBxmA,2928
|
|
438
|
+
machineconfig-6.82.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
439
|
+
machineconfig-6.82.dist-info/entry_points.txt,sha256=2OetLXw0yXtfG6MmJXbzZipCQfPueeM3oMgTORSEfYs,465
|
|
440
|
+
machineconfig-6.82.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
|
|
441
|
+
machineconfig-6.82.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|