@@ -158,6 +158,8 @@ def create_repository_panel(self):
158158 command = self .add_local_repository , style = 'Action.TButton' ).pack (side = 'left' , padx = 5 )
159159 ttk .Button (button_frame , text = "🐙 Add GitHub Repo" ,
160160 command = self .add_github_repository , style = 'Action.TButton' ).pack (side = 'left' , padx = 5 )
161+ ttk .Button (button_frame , text = "📚 Load My Repos" ,
162+ command = self .load_user_repositories , style = 'Action.TButton' ).pack (side = 'left' , padx = 5 )
161163 ttk .Button (button_frame , text = "🗑️ Remove" ,
162164 command = self .remove_repository , style = 'Action.TButton' ).pack (side = 'left' , padx = 5 )
163165
@@ -400,6 +402,58 @@ def add_github_repository(self):
400402 self .update_repository_list ()
401403 self .logger .info (f"Added GitHub repository: { repo_name } " , "GUI" )
402404
405+ def load_user_repositories (self ):
406+ """Load all repositories from the authenticated user."""
407+ if self .github_client is None :
408+ messagebox .showerror ("GitHub Unavailable" ,
409+ "GitHub client not available. Please ensure PyGithub is installed." )
410+ return
411+
412+ # Show loading dialog
413+ loading_dialog = LoadingDialog (self .root , "Loading your GitHub repositories..." )
414+
415+ def load_repos_thread ():
416+ try :
417+ # Get user's repositories
418+ user = self .github_client .get_user ()
419+ repos = list (user .get_repos (type = 'owner' , sort = 'updated' ))
420+
421+ # Add repositories to the list
422+ for repo in repos :
423+ repo_item = RepositoryItem (
424+ name = f"{ repo .owner .login } /{ repo .name } " ,
425+ path = "" ,
426+ url = repo .html_url ,
427+ repo_type = "github"
428+ )
429+ # Check if repo already exists
430+ if not any (r .url == repo .html_url for r in self .repositories ):
431+ self .repositories .append (repo_item )
432+
433+ # Update UI on main thread
434+ self .root .after (0 , lambda : self ._finish_load_repos (loading_dialog , len (repos )))
435+
436+ except Exception as e :
437+ self .root .after (0 , lambda : self ._handle_load_repos_error (loading_dialog , str (e )))
438+
439+ # Start loading in background thread
440+ threading .Thread (target = load_repos_thread , daemon = True ).start ()
441+
442+ def _finish_load_repos (self , loading_dialog , repo_count ):
443+ """Finish loading user repositories."""
444+ loading_dialog .close ()
445+ self .update_repository_list ()
446+ messagebox .showinfo ("Repositories Loaded" ,
447+ f"Successfully loaded { repo_count } repositories from your GitHub account!" )
448+ self .logger .info (f"Loaded { repo_count } user repositories from GitHub" , "GUI" )
449+
450+ def _handle_load_repos_error (self , loading_dialog , error_msg ):
451+ """Handle error during repository loading."""
452+ loading_dialog .close ()
453+ messagebox .showerror ("Loading Failed" ,
454+ f"Failed to load repositories from GitHub:\n { error_msg } " )
455+ self .logger .error (f"Failed to load user repositories: { error_msg } " , "GUI" )
456+
403457 def remove_repository (self ):
404458 """Remove selected repository."""
405459 selection = self .repo_tree .selection ()
@@ -529,10 +583,8 @@ def _analyze_repository_thread(self, repo_item: RepositoryItem):
529583 if repo_item .repo_type == "local" :
530584 metadata = self .analyzer .analyze_repository (repo_item .path , repo_item .name )
531585 else :
532- # For GitHub repos, would need to clone or use API
533- # Simplified for this example
534- metadata = ProjectMetadata ()
535- metadata .name = repo_item .name
586+ # For GitHub repos, use GitHub API to analyze
587+ metadata = self ._analyze_github_repository (repo_item )
536588
537589 repo_item .metadata = metadata
538590 repo_item .status = "completed"
@@ -548,6 +600,93 @@ def _analyze_repository_thread(self, repo_item: RepositoryItem):
548600 finally :
549601 self .is_analyzing = False
550602
603+ def _analyze_github_repository (self , repo_item : RepositoryItem ) -> ProjectMetadata :
604+ """Analyze a GitHub repository using the GitHub API."""
605+ try :
606+ # Parse repository owner and name from URL
607+ url_parts = repo_item .url .rstrip ('/' ).split ('/' )
608+ if len (url_parts ) >= 2 :
609+ owner = url_parts [- 2 ]
610+ repo_name = url_parts [- 1 ]
611+ else :
612+ raise ValueError (f"Invalid GitHub URL format: { repo_item .url } " )
613+
614+ # Get repository information from GitHub API
615+ github_repo = self .github_client .get_repo (f"{ owner } /{ repo_name } " )
616+
617+ # Create metadata from GitHub API data
618+ metadata = ProjectMetadata ()
619+ metadata .name = github_repo .name
620+ metadata .description = github_repo .description or ""
621+ metadata .repository_url = github_repo .html_url
622+ metadata .author = github_repo .owner .login
623+ try :
624+ metadata .license = github_repo .license .name if github_repo .license else ""
625+ except :
626+ metadata .license = ""
627+ metadata .created_date = github_repo .created_at .strftime ("%Y-%m-%d" )
628+ metadata .last_updated = github_repo .updated_at .strftime ("%Y-%m-%d" )
629+
630+ # Get language information
631+ languages = github_repo .get_languages ()
632+ total_bytes = sum (languages .values ())
633+ if total_bytes > 0 :
634+ metadata .languages = {
635+ lang : round ((bytes_count / total_bytes ) * 100 , 1 )
636+ for lang , bytes_count in languages .items ()
637+ }
638+ metadata .primary_language = max (languages .items (), key = lambda x : x [1 ])[0 ]
639+
640+ # Get repository statistics
641+ metadata .total_files = github_repo .size # Approximation
642+ metadata .total_lines = 0 # Not directly available via API
643+ metadata .commits_count = github_repo .get_commits ().totalCount
644+
645+ # Get additional repository information
646+ metadata .stars_count = github_repo .stargazers_count
647+ metadata .forks_count = github_repo .forks_count
648+ metadata .open_issues = github_repo .open_issues_count
649+ metadata .default_branch = github_repo .default_branch
650+
651+ # Detect project type from language and topics
652+ topics = list (github_repo .get_topics ())
653+ metadata .topics = topics
654+
655+ # Basic project type detection
656+ if metadata .primary_language :
657+ lang_lower = metadata .primary_language .lower ()
658+ if lang_lower in ['python' ]:
659+ metadata .project_type = "python"
660+ elif lang_lower in ['javascript' , 'typescript' ]:
661+ metadata .project_type = "web"
662+ elif lang_lower in ['java' ]:
663+ metadata .project_type = "java"
664+ elif lang_lower in ['c' , 'c++' , 'cpp' ]:
665+ metadata .project_type = "cpp"
666+ elif lang_lower in ['go' ]:
667+ metadata .project_type = "go"
668+ else :
669+ metadata .project_type = "other"
670+
671+ # Set has_readme based on GitHub repo data
672+ try :
673+ github_repo .get_readme ()
674+ metadata .has_readme = True
675+ except :
676+ metadata .has_readme = False
677+
678+ self .logger .info (f"Successfully analyzed GitHub repository: { metadata .name } " , "GITHUB_ANALYSIS" )
679+ return metadata
680+
681+ except Exception as e :
682+ self .logger .error (f"Failed to analyze GitHub repository { repo_item .url } : { e } " , "GITHUB_ANALYSIS" )
683+ # Return basic metadata on error
684+ metadata = ProjectMetadata ()
685+ metadata .name = repo_item .name
686+ metadata .repository_url = repo_item .url
687+ metadata .description = f"GitHub repository (analysis failed: { str (e )} )"
688+ return metadata
689+
551690 def _update_analysis_ui (self , repo_item : RepositoryItem , status : str , error_msg : str = "" ):
552691 """Update analysis UI elements."""
553692 repo_item .status = status
@@ -1032,4 +1171,35 @@ def on_add(self):
10321171
10331172 def on_cancel (self ):
10341173 """Handle cancel button click."""
1035- self .dialog .destroy ()
1174+ self .dialog .destroy ()
1175+
1176+
1177+ class LoadingDialog :
1178+ """Simple loading dialog with progress indicator."""
1179+
1180+ def __init__ (self , parent , message = "Loading..." ):
1181+ self .dialog = tk .Toplevel (parent )
1182+ self .dialog .title ("Loading" )
1183+ self .dialog .geometry ("300x100" )
1184+ self .dialog .transient (parent )
1185+ self .dialog .grab_set ()
1186+
1187+ # Center dialog
1188+ self .dialog .update_idletasks ()
1189+ x = parent .winfo_x () + (parent .winfo_width () // 2 ) - 150
1190+ y = parent .winfo_y () + (parent .winfo_height () // 2 ) - 50
1191+ self .dialog .geometry (f"+{ x } +{ y } " )
1192+
1193+ # Content
1194+ ttk .Label (self .dialog , text = message ).pack (pady = 20 )
1195+
1196+ # Progress bar
1197+ self .progress = ttk .Progressbar (self .dialog , mode = 'indeterminate' )
1198+ self .progress .pack (pady = 10 , padx = 20 , fill = 'x' )
1199+ self .progress .start ()
1200+
1201+ def close (self ):
1202+ """Close the loading dialog."""
1203+ if self .dialog .winfo_exists ():
1204+ self .progress .stop ()
1205+ self .dialog .destroy ()
0 commit comments