Claude Code Plugins

Community-maintained marketplace

Feedback

git-workflow-management

@findinfinitelabs/chuuk
0
0

Automate frequent Git operations including adding changes, committing with meaningful messages, and pushing to remote repositories. Use when you need to save progress, commit code changes, or maintain regular version control workflow with proper commit practices.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name git-workflow-management
description Automate frequent Git operations including adding changes, committing with meaningful messages, and pushing to remote repositories. Use when you need to save progress, commit code changes, or maintain regular version control workflow with proper commit practices.

Git Workflow Management

Overview

A comprehensive skill for automating and streamlining Git version control operations, focusing on frequent commits with meaningful messages, proper staging practices, and efficient push workflows. Designed to maintain clean commit history and regular backup of development progress.

Capabilities

  • Smart Change Detection: Automatically identify modified, added, and deleted files
  • Intelligent Commit Messaging: Generate meaningful commit messages based on changes
  • Selective Staging: Stage specific files or file types based on context
  • Branch Management: Handle branch creation, switching, and merging workflows
  • Push Automation: Automated pushing with conflict detection and resolution
  • Commit History Cleanup: Squashing and organizing commits for clean history

Core Components

1. Automated Change Detection and Staging

#!/bin/bash
# Smart staging script for different file types

detect_changes() {
    echo "šŸ” Detecting changes..."
    
    # Get status of all files
    git status --porcelain | while read -r line; do
        status="${line:0:2}"
        file="${line:3}"
        
        case "$status" in
            "M "|" M") echo "Modified: $file" ;;
            "A "|" A") echo "Added: $file" ;;
            "D "|" D") echo "Deleted: $file" ;;
            "??") echo "Untracked: $file" ;;
            "R ") echo "Renamed: $file" ;;
        esac
    done
}

stage_by_type() {
    local file_pattern="$1"
    local description="$2"
    
    if git diff --name-only --cached | grep -q "$file_pattern"; then
        echo "āœ… $description files already staged"
        return 0
    fi
    
    if git diff --name-only | grep -q "$file_pattern"; then
        echo "šŸ“ Staging $description files..."
        git add $(git diff --name-only | grep "$file_pattern")
        return 0
    fi
    
    echo "ā„¹ļø  No $description files to stage"
    return 1
}

smart_staging() {
    echo "šŸŽÆ Smart staging workflow..."
    
    # Stage different file types with appropriate grouping
    stage_by_type "\.py$" "Python"
    stage_by_type "\.md$" "Markdown"
    stage_by_type "\.js$\|\.ts$\|\.jsx$\|\.tsx$" "JavaScript/TypeScript"
    stage_by_type "\.css$\|\.scss$\|\.sass$" "Stylesheet"
    stage_by_type "\.html$\|\.htm$" "HTML"
    stage_by_type "\.json$\|\.yml$\|\.yaml$" "Configuration"
    stage_by_type "\.sql$" "Database"
    stage_by_type "requirements\.txt\|package\.json\|Pipfile" "Dependencies"
    
    # Show current staging status
    echo "šŸ“‹ Currently staged files:"
    git diff --cached --name-only | sed 's/^/  āœ“ /'
}

2. Intelligent Commit Message Generation

generate_commit_message() {
    local commit_type="$1"
    local scope="$2"
    local description="$3"
    
    # Get staged files for context
    local staged_files=$(git diff --cached --name-only)
    local file_count=$(echo "$staged_files" | wc -l | xargs)
    
    # Analyze changes for automatic message generation
    if [ -z "$description" ]; then
        description=$(analyze_changes_for_message)
    fi
    
    # Generate conventional commit message
    if [ -n "$scope" ]; then
        echo "${commit_type}(${scope}): ${description}"
    else
        echo "${commit_type}: ${description}"
    fi
}

analyze_changes_for_message() {
    local staged_files=$(git diff --cached --name-only)
    local added_files=$(git diff --cached --name-status | grep '^A' | wc -l | xargs)
    local modified_files=$(git diff --cached --name-status | grep '^M' | wc -l | xargs)
    local deleted_files=$(git diff --cached --name-status | grep '^D' | wc -l | xargs)
    
    # Determine primary change type
    if [ "$added_files" -gt 0 ] && [ "$modified_files" -eq 0 ] && [ "$deleted_files" -eq 0 ]; then
        if echo "$staged_files" | grep -q "\.md$"; then
            echo "add documentation and skill definitions"
        elif echo "$staged_files" | grep -q "\.py$"; then
            echo "add new Python modules and functionality"
        else
            echo "add new files and resources"
        fi
    elif [ "$modified_files" -gt 0 ] && [ "$added_files" -eq 0 ] && [ "$deleted_files" -eq 0 ]; then
        if echo "$staged_files" | grep -q "SKILL\.md"; then
            echo "update Claude Skills definitions and capabilities"
        elif echo "$staged_files" | grep -q "\.py$"; then
            echo "improve Python code and functionality"
        elif echo "$staged_files" | grep -q "\.md$"; then
            echo "update documentation and guides"
        else
            echo "update existing functionality"
        fi
    elif [ "$deleted_files" -gt 0 ]; then
        echo "remove obsolete files and cleanup codebase"
    else
        echo "update multiple components and files"
    fi
}

commit_with_message() {
    local message_type="${1:-feat}"
    local scope="$2"
    local custom_message="$3"
    
    # Ensure we have staged changes
    if [ -z "$(git diff --cached --name-only)" ]; then
        echo "āŒ No staged changes to commit"
        return 1
    fi
    
    # Generate or use provided commit message
    local commit_message
    if [ -n "$custom_message" ]; then
        commit_message="$custom_message"
    else
        commit_message=$(generate_commit_message "$message_type" "$scope")
    fi
    
    echo "šŸ’¬ Commit message: $commit_message"
    
    # Show what will be committed
    echo "šŸ“ Files to commit:"
    git diff --cached --name-only | sed 's/^/  • /'
    
    # Create commit
    git commit -m "$commit_message"
    
    if [ $? -eq 0 ]; then
        echo "āœ… Commit created successfully"
        return 0
    else
        echo "āŒ Commit failed"
        return 1
    fi
}

3. Comprehensive Git Workflow Functions

import subprocess
import os
import sys
from datetime import datetime
from typing import List, Dict, Optional, Tuple

class GitWorkflowManager:
    def __init__(self, repository_path: str = "."):
        """Initialize Git workflow manager."""
        self.repo_path = repository_path
        self.ensure_git_repository()
    
    def ensure_git_repository(self):
        """Ensure we're in a Git repository."""
        try:
            subprocess.run(['git', 'rev-parse', '--git-dir'], 
                         cwd=self.repo_path, 
                         capture_output=True, 
                         check=True)
        except subprocess.CalledProcessError:
            raise RuntimeError(f"Not a Git repository: {self.repo_path}")
    
    def get_status(self) -> Dict[str, List[str]]:
        """Get comprehensive repository status."""
        try:
            result = subprocess.run(['git', 'status', '--porcelain'], 
                                  cwd=self.repo_path, 
                                  capture_output=True, 
                                  text=True, 
                                  check=True)
            
            status = {
                'modified': [],
                'added': [],
                'deleted': [],
                'untracked': [],
                'renamed': [],
                'staged': []
            }
            
            for line in result.stdout.strip().split('\n'):
                if not line:
                    continue
                
                status_code = line[:2]
                filename = line[3:]
                
                # Check staging area (first character)
                if status_code[0] in ['M', 'A', 'D', 'R', 'C']:
                    status['staged'].append(filename)
                
                # Check working directory (second character)
                if status_code[1] == 'M':
                    status['modified'].append(filename)
                elif status_code[1] == 'D':
                    status['deleted'].append(filename)
                elif status_code == '??':
                    status['untracked'].append(filename)
                elif status_code[0] == 'R':
                    status['renamed'].append(filename)
                elif status_code[0] == 'A':
                    status['added'].append(filename)
            
            return status
            
        except subprocess.CalledProcessError as e:
            raise RuntimeError(f"Failed to get Git status: {e}")
    
    def stage_files(self, patterns: List[str] = None, all_files: bool = False) -> bool:
        """Stage files based on patterns or all files."""
        try:
            if all_files:
                subprocess.run(['git', 'add', '.'], cwd=self.repo_path, check=True)
                print("āœ… All files staged")
                return True
            
            if patterns:
                for pattern in patterns:
                    subprocess.run(['git', 'add', pattern], cwd=self.repo_path, check=True)
                print(f"āœ… Files matching {patterns} staged")
                return True
            
            # Interactive staging
            return self._interactive_staging()
            
        except subprocess.CalledProcessError as e:
            print(f"āŒ Failed to stage files: {e}")
            return False
    
    def _interactive_staging(self) -> bool:
        """Interactive file staging based on file types."""
        status = self.get_status()
        
        if not any(status.values()):
            print("ā„¹ļø  No changes to stage")
            return False
        
        # Group files by type for smart staging
        file_groups = {
            'Python files': [f for f in status['modified'] + status['untracked'] if f.endswith('.py')],
            'Documentation': [f for f in status['modified'] + status['untracked'] if f.endswith(('.md', '.txt', '.rst'))],
            'Configuration': [f for f in status['modified'] + status['untracked'] if f.endswith(('.json', '.yml', '.yaml', '.toml'))],
            'Web files': [f for f in status['modified'] + status['untracked'] if f.endswith(('.html', '.css', '.js', '.ts'))],
            'Data files': [f for f in status['modified'] + status['untracked'] if f.endswith(('.csv', '.tsv', '.sql'))],
            'Other files': []
        }
        
        # Add remaining files to 'Other files'
        all_categorized = set()
        for files in file_groups.values():
            all_categorized.update(files)
        
        all_changes = set(status['modified'] + status['untracked'])
        file_groups['Other files'] = list(all_changes - all_categorized)
        
        # Stage files by groups
        staged_any = False
        for group_name, files in file_groups.items():
            if files:
                print(f"šŸ“ {group_name}: {len(files)} files")
                for file in files:
                    print(f"  • {file}")
                
                try:
                    subprocess.run(['git', 'add'] + files, cwd=self.repo_path, check=True)
                    print(f"āœ… Staged {group_name}")
                    staged_any = True
                except subprocess.CalledProcessError:
                    print(f"āŒ Failed to stage {group_name}")
        
        return staged_any
    
    def commit_changes(self, message: str = None, commit_type: str = "feat", 
                      scope: str = None, auto_message: bool = True) -> bool:
        """Create a commit with proper message formatting."""
        
        # Check for staged changes
        try:
            result = subprocess.run(['git', 'diff', '--cached', '--name-only'], 
                                  cwd=self.repo_path, 
                                  capture_output=True, 
                                  text=True, 
                                  check=True)
            
            if not result.stdout.strip():
                print("āŒ No staged changes to commit")
                return False
            
        except subprocess.CalledProcessError:
            print("āŒ Failed to check staged changes")
            return False
        
        # Generate commit message if not provided
        if not message and auto_message:
            message = self._generate_auto_commit_message(commit_type, scope)
        elif not message:
            message = f"{commit_type}: update files and functionality"
        
        # Create commit
        try:
            subprocess.run(['git', 'commit', '-m', message], 
                         cwd=self.repo_path, 
                         check=True)
            print(f"āœ… Committed: {message}")
            return True
            
        except subprocess.CalledProcessError as e:
            print(f"āŒ Commit failed: {e}")
            return False
    
    def _generate_auto_commit_message(self, commit_type: str, scope: str = None) -> str:
        """Generate automatic commit message based on changes."""
        try:
            # Get staged files
            result = subprocess.run(['git', 'diff', '--cached', '--name-only'], 
                                  cwd=self.repo_path, 
                                  capture_output=True, 
                                  text=True, 
                                  check=True)
            
            staged_files = result.stdout.strip().split('\n')
            
            # Analyze file types and changes
            file_analysis = {
                'skills': len([f for f in staged_files if 'skill' in f.lower() or f.endswith('SKILL.md')]),
                'python': len([f for f in staged_files if f.endswith('.py')]),
                'docs': len([f for f in staged_files if f.endswith(('.md', '.txt', '.rst'))]),
                'config': len([f for f in staged_files if f.endswith(('.json', '.yml', '.yaml', '.toml'))]),
                'web': len([f for f in staged_files if f.endswith(('.html', '.css', '.js', '.ts'))]),
                'data': len([f for f in staged_files if f.endswith(('.csv', '.tsv', '.sql'))])
            }
            
            # Generate description based on primary change type
            if file_analysis['skills'] > 0:
                description = "update Claude Skills definitions and capabilities"
            elif file_analysis['python'] > file_analysis['docs']:
                description = "improve Python functionality and code structure"
            elif file_analysis['docs'] > 0:
                description = "update documentation and project guides"
            elif file_analysis['config'] > 0:
                description = "update configuration and settings"
            elif file_analysis['web'] > 0:
                description = "improve web interface and styling"
            elif file_analysis['data'] > 0:
                description = "update data files and database structure"
            else:
                description = "update project files and resources"
            
            # Format commit message
            if scope:
                return f"{commit_type}({scope}): {description}"
            else:
                return f"{commit_type}: {description}"
                
        except subprocess.CalledProcessError:
            return f"{commit_type}: update files and functionality"
    
    def push_changes(self, remote: str = "origin", branch: str = None, 
                    force: bool = False) -> bool:
        """Push changes to remote repository."""
        try:
            # Get current branch if not specified
            if not branch:
                result = subprocess.run(['git', 'branch', '--show-current'], 
                                      cwd=self.repo_path, 
                                      capture_output=True, 
                                      text=True, 
                                      check=True)
                branch = result.stdout.strip()
            
            # Check if there are commits to push
            try:
                subprocess.run(['git', 'diff', f'{remote}/{branch}', 'HEAD', '--quiet'], 
                             cwd=self.repo_path, 
                             check=True)
                print("ā„¹ļø  No new commits to push")
                return True
            except subprocess.CalledProcessError:
                pass  # There are differences, proceed with push
            
            # Push changes
            push_command = ['git', 'push', remote, branch]
            if force:
                push_command.append('--force-with-lease')
            
            subprocess.run(push_command, cwd=self.repo_path, check=True)
            print(f"āœ… Pushed to {remote}/{branch}")
            return True
            
        except subprocess.CalledProcessError as e:
            print(f"āŒ Push failed: {e}")
            print("šŸ’” Try: git pull --rebase before pushing")
            return False
    
    def quick_commit_and_push(self, message: str = None, commit_type: str = "feat", 
                             scope: str = None, stage_all: bool = True) -> bool:
        """Complete workflow: stage, commit, and push in one operation."""
        print("šŸš€ Starting quick commit and push workflow...")
        
        # Stage files
        if stage_all:
            if not self.stage_files(all_files=True):
                return False
        else:
            if not self.stage_files():
                return False
        
        # Commit changes
        if not self.commit_changes(message=message, commit_type=commit_type, scope=scope):
            return False
        
        # Push changes
        if not self.push_changes():
            return False
        
        print("šŸŽ‰ Complete! Changes staged, committed, and pushed successfully.")
        return True
    
    def sync_with_remote(self, remote: str = "origin", branch: str = None) -> bool:
        """Sync local branch with remote (pull with rebase)."""
        try:
            if not branch:
                result = subprocess.run(['git', 'branch', '--show-current'], 
                                      cwd=self.repo_path, 
                                      capture_output=True, 
                                      text=True, 
                                      check=True)
                branch = result.stdout.strip()
            
            print(f"šŸ”„ Syncing with {remote}/{branch}...")
            subprocess.run(['git', 'pull', '--rebase', remote, branch], 
                         cwd=self.repo_path, 
                         check=True)
            print("āœ… Successfully synced with remote")
            return True
            
        except subprocess.CalledProcessError as e:
            print(f"āŒ Sync failed: {e}")
            return False

4. Command-Line Interface and Quick Commands

import argparse
import sys

def create_cli():
    """Create command-line interface for Git workflow management."""
    
    parser = argparse.ArgumentParser(description="Git Workflow Management")
    subparsers = parser.add_subparsers(dest='command', help='Available commands')
    
    # Quick commit command
    quick_parser = subparsers.add_parser('quick', help='Quick stage, commit, and push')
    quick_parser.add_argument('-m', '--message', help='Custom commit message')
    quick_parser.add_argument('-t', '--type', default='feat', 
                            choices=['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'],
                            help='Conventional commit type')
    quick_parser.add_argument('-s', '--scope', help='Commit scope')
    quick_parser.add_argument('--no-stage-all', action='store_true', help='Use interactive staging')
    
    # Stage command
    stage_parser = subparsers.add_parser('stage', help='Stage files')
    stage_parser.add_argument('patterns', nargs='*', help='File patterns to stage')
    stage_parser.add_argument('-a', '--all', action='store_true', help='Stage all files')
    
    # Commit command
    commit_parser = subparsers.add_parser('commit', help='Commit staged changes')
    commit_parser.add_argument('-m', '--message', help='Commit message')
    commit_parser.add_argument('-t', '--type', default='feat', help='Commit type')
    commit_parser.add_argument('-s', '--scope', help='Commit scope')
    
    # Push command
    push_parser = subparsers.add_parser('push', help='Push changes to remote')
    push_parser.add_argument('-r', '--remote', default='origin', help='Remote name')
    push_parser.add_argument('-b', '--branch', help='Branch name')
    push_parser.add_argument('-f', '--force', action='store_true', help='Force push with lease')
    
    # Status command
    status_parser = subparsers.add_parser('status', help='Show repository status')
    
    # Sync command
    sync_parser = subparsers.add_parser('sync', help='Sync with remote repository')
    sync_parser.add_argument('-r', '--remote', default='origin', help='Remote name')
    sync_parser.add_argument('-b', '--branch', help='Branch name')
    
    return parser

def main():
    """Main CLI entry point."""
    parser = create_cli()
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        sys.exit(1)
    
    # Initialize Git workflow manager
    try:
        git_manager = GitWorkflowManager()
    except RuntimeError as e:
        print(f"āŒ {e}")
        sys.exit(1)
    
    # Execute commands
    try:
        if args.command == 'quick':
            success = git_manager.quick_commit_and_push(
                message=args.message,
                commit_type=args.type,
                scope=args.scope,
                stage_all=not args.no_stage_all
            )
        elif args.command == 'stage':
            success = git_manager.stage_files(
                patterns=args.patterns,
                all_files=args.all
            )
        elif args.command == 'commit':
            success = git_manager.commit_changes(
                message=args.message,
                commit_type=args.type,
                scope=args.scope
            )
        elif args.command == 'push':
            success = git_manager.push_changes(
                remote=args.remote,
                branch=args.branch,
                force=args.force
            )
        elif args.command == 'status':
            status = git_manager.get_status()
            print_status(status)
            success = True
        elif args.command == 'sync':
            success = git_manager.sync_with_remote(
                remote=args.remote,
                branch=args.branch
            )
        else:
            parser.print_help()
            success = False
        
        sys.exit(0 if success else 1)
        
    except KeyboardInterrupt:
        print("\nāŒ Operation cancelled")
        sys.exit(1)
    except Exception as e:
        print(f"āŒ Unexpected error: {e}")
        sys.exit(1)

def print_status(status):
    """Print formatted repository status."""
    print("šŸ“Š Repository Status:")
    
    if status['staged']:
        print("āœ… Staged files:")
        for file in status['staged']:
            print(f"  • {file}")
    
    if status['modified']:
        print("šŸ“ Modified files:")
        for file in status['modified']:
            print(f"  • {file}")
    
    if status['untracked']:
        print("ā“ Untracked files:")
        for file in status['untracked']:
            print(f"  • {file}")
    
    if status['deleted']:
        print("šŸ—‘ļø  Deleted files:")
        for file in status['deleted']:
            print(f"  • {file}")
    
    if not any(status.values()):
        print("✨ Working directory clean")

if __name__ == "__main__":
    main()

Usage Examples

1. Quick Workflow (Most Common)

# Stage all changes, commit with auto-generated message, and push
python git_workflow.py quick

# Quick commit with custom message
python git_workflow.py quick -m "Add new Chuukese language processing features"

# Quick commit with specific type and scope
python git_workflow.py quick -t feat -s chuukese -m "Add accent normalization functionality"

2. Step-by-Step Workflow

# Check status
python git_workflow.py status

# Stage specific files
python git_workflow.py stage *.py

# Stage all files
python git_workflow.py stage -a

# Commit with auto-generated message
python git_workflow.py commit -t feat -s skills

# Push to remote
python git_workflow.py push

3. Sync with Remote

# Sync with remote repository
python git_workflow.py sync

# Sync specific remote and branch
python git_workflow.py sync -r upstream -b main

Best Practices

1. Commit Message Conventions

  • feat: New features or functionality
  • fix: Bug fixes
  • docs: Documentation changes
  • style: Code style/formatting changes
  • refactor: Code refactoring without feature changes
  • test: Adding or updating tests
  • chore: Maintenance tasks, dependency updates

2. Frequent Commit Guidelines

  • Commit after completing logical units of work
  • Keep commits focused on single concerns
  • Use descriptive commit messages
  • Commit before switching contexts or taking breaks
  • Push regularly to backup work

3. File Organization

  • Group related changes in single commits
  • Separate feature development from documentation updates
  • Commit configuration changes separately from code changes
  • Keep dependency updates in dedicated commits

4. Workflow Automation

  • Use quick commands for routine work
  • Set up aliases for common operations
  • Automate repetitive staging patterns
  • Implement pre-commit hooks for quality checks

Integration with Development Tools

1. VS Code Integration

// .vscode/tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Quick Git Commit",
            "type": "shell",
            "command": "python",
            "args": ["git_workflow.py", "quick"],
            "group": "build",
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared"
            }
        },
        {
            "label": "Git Status",
            "type": "shell",
            "command": "python",
            "args": ["git_workflow.py", "status"],
            "group": "build"
        }
    ]
}

2. Shell Aliases

# Add to ~/.bashrc or ~/.zshrc
alias gq='python git_workflow.py quick'
alias gs='python git_workflow.py status'
alias gc='python git_workflow.py commit'
alias gp='python git_workflow.py push'
alias gsync='python git_workflow.py sync'

Dependencies

  • subprocess: Process execution for Git commands
  • argparse: Command-line interface
  • os: Operating system interface
  • datetime: Timestamp management

Validation Criteria

A successful implementation should:

  • āœ… Automatically detect and stage relevant file changes
  • āœ… Generate meaningful commit messages based on content
  • āœ… Handle Git conflicts and errors gracefully
  • āœ… Support both automated and interactive workflows
  • āœ… Maintain clean commit history with conventional commits
  • āœ… Provide clear status feedback throughout operations
  • āœ… Support multiple remotes and branches
  • āœ… Include comprehensive error handling and recovery