Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import requests | |
| from datetime import datetime, timezone, timedelta | |
| import os | |
| import google.generativeai as genai | |
| def get_all_commits_in_range(owner, repo_name, start_date, end_date, token): | |
| """ | |
| Fetch all unique commits across all branches within a date range. | |
| Uses a more efficient approach by fetching commits once and filtering by date. | |
| """ | |
| headers = {"Authorization": f"token {token}"} | |
| base_url = f"https://api.github.com/repos/{owner}/{repo_name}" | |
| # Get all branches | |
| try: | |
| branches_resp = requests.get(f"{base_url}/branches", headers=headers) | |
| branches_resp.raise_for_status() | |
| branches = [b['name'] for b in branches_resp.json()] | |
| except requests.exceptions.RequestException as e: | |
| return None, f"Error fetching branches: {str(e)}" | |
| # Define date range | |
| since = f"{start_date}T00:00:00Z" | |
| until = f"{end_date}T23:59:59Z" | |
| all_commits = {} | |
| # Fetch commits from all branches | |
| for branch in branches: | |
| try: | |
| params = {"sha": branch, "since": since, "until": until, "per_page": 100} | |
| commits_resp = requests.get(f"{base_url}/commits", headers=headers, params=params) | |
| commits_resp.raise_for_status() | |
| commits = commits_resp.json() | |
| # Add unique commits (keyed by SHA to avoid duplicates) | |
| for commit in commits: | |
| sha = commit["sha"] | |
| if sha not in all_commits: | |
| all_commits[sha] = commit | |
| except requests.exceptions.RequestException as e: | |
| continue # Skip branches that cause errors | |
| # Sort by commit date | |
| sorted_commits = sorted( | |
| all_commits.values(), | |
| key=lambda c: datetime.strptime(c["commit"]["author"]["date"], "%Y-%m-%dT%H:%M:%SZ") | |
| ) | |
| return sorted_commits, None | |
| def format_commits(commits, timezone_offset_hours=5, timezone_offset_minutes=45): | |
| """ | |
| Format commits with timezone conversion and readable output. | |
| """ | |
| formatted = [] | |
| for commit in commits: | |
| sha = commit["sha"][:8] # Short SHA for readability | |
| message = commit["commit"]["message"].split('\n')[0] # First line only | |
| author = commit["commit"]["author"]["name"] | |
| utc_time_str = commit["commit"]["author"]["date"] | |
| # Parse and convert timezone | |
| utc_time = datetime.strptime(utc_time_str, "%Y-%m-%dT%H:%M:%SZ") | |
| utc_time = utc_time.replace(tzinfo=timezone.utc) | |
| # Apply timezone offset | |
| local_offset = timedelta(hours=timezone_offset_hours, minutes=timezone_offset_minutes) | |
| local_time = utc_time.astimezone(timezone(local_offset)) | |
| formatted_line = ( | |
| f"{sha} | {author:20s} | {local_time.strftime('%Y-%m-%d %H:%M:%S')} | {message}" | |
| ) | |
| formatted.append(formatted_line) | |
| return "\n".join(formatted) | |
| def summarize_with_gemini(repo_name, owner, commit_lines, start_date, end_date, api_key): | |
| """ | |
| Generate a comprehensive summary using Gemini with an improved prompt. | |
| """ | |
| genai.configure(api_key=api_key) | |
| model = genai.GenerativeModel("gemini-2.0-flash-exp") | |
| date_range = f"{start_date}" if start_date == end_date else f"{start_date} to {end_date}" | |
| prompt = f"""You are a technical project analyst reviewing Git commit history. | |
| Repository: {owner}/{repo_name} | |
| Date Range: {date_range} | |
| Total Commits: {len(commit_lines.split(chr(10)))} | |
| Commit History: | |
| {commit_lines} | |
| Please provide a comprehensive analysis with the following sections: | |
| 1. **Executive Summary**: A 2-3 sentence overview of the work accomplished during this period. | |
| 2. **Key Developments**: Highlight the most significant changes, features, or fixes (3-5 bullet points). | |
| 3. **Development Timeline**: Chronological narrative of how the work progressed throughout the day/period. | |
| 4. **Author Contributions**: For each author, provide: | |
| - Total number of commits | |
| - Time range of their work | |
| - Main focus areas and specific accomplishments | |
| - Notable commits with their purpose | |
| 5. **Technical Insights**: Identify patterns such as: | |
| - Types of work (features, bugs, refactoring, documentation) | |
| - Areas of the codebase that received attention | |
| - Any potential concerns or areas needing follow-up | |
| 6. **Impact Assessment**: Evaluate the overall progress and its significance to the project. | |
| Format your response with clear headings and bullet points for readability.""" | |
| try: | |
| response = model.generate_content(prompt) | |
| return response.text | |
| except Exception as e: | |
| return f"Error generating summary: {str(e)}" | |
| def analyze_commits(repo_url, start_date, end_date, github_token, gemini_key): | |
| """ | |
| Main function to orchestrate the commit analysis. | |
| """ | |
| # Parse repository URL | |
| try: | |
| if "github.com/" in repo_url: | |
| parts = repo_url.rstrip('/').split('github.com/')[-1].split('/') | |
| owner = parts[0] | |
| repo_name = parts[1].replace('.git', '') | |
| else: | |
| # Assume format: owner/repo | |
| owner, repo_name = repo_url.split('/') | |
| except: | |
| return "β Invalid repository format. Use 'owner/repo' or full GitHub URL." | |
| # Validate tokens | |
| if not github_token or not gemini_key: | |
| return "β Please provide both GitHub and Gemini API tokens." | |
| # Fetch commits | |
| status_msg = f"π Fetching commits from {owner}/{repo_name}...\n" | |
| commits, error = get_all_commits_in_range(owner, repo_name, start_date, end_date, github_token) | |
| if error: | |
| return f"{status_msg}β {error}" | |
| if not commits: | |
| return f"{status_msg}βΉοΈ No commits found in the specified date range." | |
| status_msg += f"β Found {len(commits)} unique commits across all branches.\n\n" | |
| # Format commits | |
| formatted_commits = format_commits(commits) | |
| status_msg += "π Commit History:\n" + "="*80 + "\n" | |
| status_msg += formatted_commits + "\n\n" | |
| # Generate summary | |
| status_msg += "π€ Generating AI summary...\n" + "="*80 + "\n" | |
| summary = summarize_with_gemini(repo_name, owner, formatted_commits, start_date, end_date, gemini_key) | |
| status_msg += summary | |
| return status_msg | |
| # Gradio Interface | |
| with gr.Blocks(title="Git Commit Story Analyzer", theme=gr.themes.Soft()) as app: | |
| gr.Markdown(""" | |
| # π Git Commit Story Analyzer | |
| Transform your Git commit history into comprehensive narratives using AI. | |
| This tool fetches commits from **all branches** and generates detailed insights. | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| repo_input = gr.Textbox( | |
| label="Repository", | |
| placeholder="owner/repo or https://github.com/owner/repo", | |
| info="Enter GitHub repository in format 'owner/repo' or paste the full URL" | |
| ) | |
| with gr.Row(): | |
| start_date_input = gr.Textbox( | |
| label="Start Date", | |
| value=datetime.now().strftime("%Y-%m-%d"), | |
| placeholder="YYYY-MM-DD" | |
| ) | |
| end_date_input = gr.Textbox( | |
| label="End Date", | |
| value=datetime.now().strftime("%Y-%m-%d"), | |
| placeholder="YYYY-MM-DD" | |
| ) | |
| github_token_input = gr.Textbox( | |
| label="GitHub Personal Access Token", | |
| type="password", | |
| placeholder="ghp_xxxxxxxxxxxx", | |
| info="Required for API access. Generate at: Settings β Developer settings β Personal access tokens" | |
| ) | |
| gemini_key_input = gr.Textbox( | |
| label="Gemini API Key", | |
| type="password", | |
| placeholder="AIzaSy...", | |
| info="Get your API key from Google AI Studio: https://makersuite.google.com/app/apikey" | |
| ) | |
| analyze_btn = gr.Button("π Analyze Commits", variant="primary", size="lg") | |
| with gr.Column(scale=1): | |
| gr.Markdown(""" | |
| ### π‘ Tips | |
| - **Single Day**: Set both dates to the same day | |
| - **Date Range**: Use different start and end dates | |
| - **All Branches**: Automatically analyzes commits from all branches | |
| - **Timezone**: Displays times in Nepal timezone (UTC+5:45) | |
| ### π Privacy | |
| Your tokens are only used for this session and are never stored. | |
| """) | |
| output = gr.Textbox( | |
| label="Analysis Results", | |
| lines=25, | |
| max_lines=50, | |
| show_copy_button=True | |
| ) | |
| analyze_btn.click( | |
| fn=analyze_commits, | |
| inputs=[repo_input, start_date_input, end_date_input, github_token_input, gemini_key_input], | |
| outputs=output | |
| ) | |
| gr.Markdown(""" | |
| --- | |
| ### π How to Use | |
| 1. **Enter Repository**: Provide the GitHub repository (e.g., `torvalds/linux` or full URL) | |
| 2. **Select Date Range**: Choose single day or date range for analysis | |
| 3. **Add Tokens**: Provide your GitHub Personal Access Token and Gemini API Key | |
| 4. **Analyze**: Click the button to generate your commit story | |
| **Note**: The tool fetches commits from all branches and removes duplicates automatically. | |
| """) | |
| if __name__ == "__main__": | |
| app.launch() |