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.
@@ -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
- from dataclasses import dataclass
7
- from enum import Enum
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
- repo_path=path,
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
- operations_performed = []
318
- # Determine which operations to perform
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
- for a_path in repos_root.search("*"):
327
- print(f"{('Handling ' + str(a_path)).center(80, '-')}")
328
- summary.total_paths_processed += 1
329
-
330
- # Check if this is a git repository first
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
- summary.git_repos_found += 1
337
-
338
- # Track repos without remotes
339
- if len(repo.remotes) == 0:
340
- summary.repos_without_remotes.append(a_path)
341
-
342
- # Now perform the actual operations
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
- result = git_action(path=a_path, action=GitAction.pull, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
345
- summary.pulls_attempted += 1
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
- result = git_action(a_path, action=GitAction.commit, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
354
- summary.commits_attempted += 1
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
- result = git_action(a_path, action=GitAction.push, mess=None, r=recursive, auto_uv_sync=auto_uv_sync)
366
- summary.pushes_attempted += 1
367
- if result.success:
368
- summary.pushes_successful += 1
369
- else:
370
- summary.pushes_failed += 1
371
- summary.failed_operations.append(result)
372
-
373
- except InvalidGitRepositoryError:
374
- summary.non_git_paths += 1
375
- pprint(f"⚠️ Skipping {a_path} because it is not a git repository.")
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]")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 6.81
3
+ Version: 6.82
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -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=9AxWy8mB9HFeV5t11-qD_l-KA5jkUmm0pXVKT1L6-Qk,14894
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.81.dist-info/METADATA,sha256=akyrWPX9TB4A7xBMBUNJOW7CgsRn4W2ra1dNUug_Y88,2928
437
- machineconfig-6.81.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
438
- machineconfig-6.81.dist-info/entry_points.txt,sha256=2OetLXw0yXtfG6MmJXbzZipCQfPueeM3oMgTORSEfYs,465
439
- machineconfig-6.81.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
440
- machineconfig-6.81.dist-info/RECORD,,
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,,