77from rich .console import Console
88from rich .progress import Progress , SpinnerColumn , TextColumn
99
10- from smartem_workspace .config .schema import ReposConfig
11- from smartem_workspace .setup .repos import get_local_dir
10+ from smartem_workspace .config .schema import Organization , ReposConfig , Repository
11+ from smartem_workspace .setup .repos import clone_repo , get_local_dir
1212from smartem_workspace .utils .git import (
13+ check_github_ssh_auth ,
1314 fetch_remote ,
1415 get_commits_behind ,
1516 get_current_branch ,
1617 has_uncommitted_changes ,
1718 run_git_command ,
1819)
1920
20- console = Console ()
21-
2221
2322@dataclass
2423class SyncResult :
2524 repo_name : str
2625 org_name : str
27- status : Literal ["updated" , "up-to-date" , "error" , "skipped" , "dry-run" ]
26+ status : Literal ["updated" , "up-to-date" , "error" , "skipped" , "dry-run" , "cloned" ]
2827 message : str
2928 commits_behind : int = 0
3029
@@ -69,41 +68,80 @@ def sync_single_repo(repo_path: Path, dry_run: bool = False) -> SyncResult:
6968def sync_all_repos (
7069 workspace_path : Path ,
7170 config : ReposConfig ,
71+ console : Console ,
7272 dry_run : bool = False ,
73+ use_ssh : bool | None = None ,
7374) -> list [SyncResult ]:
7475 repos_dir = workspace_path / "repos"
75- results = []
76+ results : list [ SyncResult ] = []
7677
7778 if not repos_dir .exists ():
78- console .print ("[red]repos directory not found[/red]" )
79- return results
79+ repos_dir .mkdir (parents = True , exist_ok = True )
80+
81+ missing_repos : list [tuple [Organization , Repository , Path ]] = []
82+ existing_repos : list [tuple [str , str , Path ]] = []
8083
81- repo_paths = []
8284 for org in config .organizations :
8385 local_dir = get_local_dir (org )
8486 org_dir = repos_dir / local_dir
8587
8688 for repo in org .repos :
8789 repo_path = org_dir / repo .name
8890 if repo_path .exists ():
89- repo_paths .append ((org .name , repo .name , repo_path ))
90-
91- if not repo_paths :
92- console .print ("[yellow]No cloned repositories found[/yellow]" )
91+ existing_repos .append ((org .name , repo .name , repo_path ))
92+ else :
93+ missing_repos .append ((org , repo , repo_path ))
94+
95+ if missing_repos :
96+ github_ssh_ok : bool | None = None
97+ if use_ssh is None :
98+ has_github = any (org .provider == "github" for org , _ , _ in missing_repos )
99+ if has_github :
100+ github_ssh_ok = check_github_ssh_auth ()
101+
102+ action = "Would clone" if dry_run else "Cloning"
103+ console .print (f"\n [bold]{ action } { len (missing_repos )} missing repositories...[/bold]\n " )
104+
105+ with Progress (
106+ SpinnerColumn (),
107+ TextColumn ("[progress.description]{task.description}" ),
108+ console = console ,
109+ transient = True ,
110+ ) as progress :
111+ task = progress .add_task ("Starting..." , total = len (missing_repos ))
112+
113+ for org , repo , repo_path in missing_repos :
114+ progress .update (task , description = f"{ org .name } /{ repo .name } " )
115+
116+ if dry_run :
117+ results .append (SyncResult (repo .name , org .name , "dry-run" , "Would clone" ))
118+ else :
119+ success = clone_repo (repo , org , repos_dir , use_ssh , github_ssh_ok )
120+ if success :
121+ results .append (SyncResult (repo .name , org .name , "cloned" , "Cloned successfully" ))
122+ existing_repos .append ((org .name , repo .name , repo_path ))
123+ else :
124+ results .append (SyncResult (repo .name , org .name , "error" , "Clone failed" ))
125+
126+ progress .advance (task )
127+
128+ if not existing_repos :
129+ if not missing_repos :
130+ console .print ("[yellow]No repositories configured[/yellow]" )
93131 return results
94132
95133 action = "Checking" if dry_run else "Syncing"
96- console .print (f"\n [bold]{ action } { len (repo_paths )} repositories...[/bold]\n " )
134+ console .print (f"\n [bold]{ action } { len (existing_repos )} repositories...[/bold]\n " )
97135
98136 with Progress (
99137 SpinnerColumn (),
100138 TextColumn ("[progress.description]{task.description}" ),
101139 console = console ,
102140 transient = True ,
103141 ) as progress :
104- task = progress .add_task ("Starting..." , total = len (repo_paths ))
142+ task = progress .add_task ("Starting..." , total = len (existing_repos ))
105143
106- for org_name , repo_name , repo_path in repo_paths :
144+ for org_name , repo_name , repo_path in existing_repos :
107145 progress .update (task , description = f"{ org_name } /{ repo_name } " )
108146 result = sync_single_repo (repo_path , dry_run = dry_run )
109147 results .append (result )
@@ -112,7 +150,8 @@ def sync_all_repos(
112150 return results
113151
114152
115- def print_sync_results (results : list [SyncResult ]) -> None :
153+ def print_sync_results (results : list [SyncResult ], console : Console ) -> None :
154+ cloned = sum (1 for r in results if r .status == "cloned" )
116155 updated = sum (1 for r in results if r .status == "updated" )
117156 up_to_date = sum (1 for r in results if r .status == "up-to-date" )
118157 skipped = sum (1 for r in results if r .status == "skipped" )
@@ -122,7 +161,9 @@ def print_sync_results(results: list[SyncResult]) -> None:
122161 for result in results :
123162 full_name = f"{ result .org_name } /{ result .repo_name } "
124163
125- if result .status == "updated" :
164+ if result .status == "cloned" :
165+ console .print (f" [green]+[/green] { full_name } : { result .message } " )
166+ elif result .status == "updated" :
126167 console .print (f" [green]\u2713 [/green] { full_name } : { result .message } " )
127168 elif result .status == "up-to-date" :
128169 console .print (f" [dim]\u2713 { full_name } : { result .message } [/dim]" )
@@ -135,6 +176,8 @@ def print_sync_results(results: list[SyncResult]) -> None:
135176
136177 console .print ()
137178 parts = []
179+ if cloned :
180+ parts .append (f"[green]{ cloned } cloned[/green]" )
138181 if updated :
139182 parts .append (f"[green]{ updated } updated[/green]" )
140183 if would_update :
0 commit comments