Initial commit
This commit is contained in:
247
skills/get-git-diff/SKILL.md
Normal file
247
skills/get-git-diff/SKILL.md
Normal file
@@ -0,0 +1,247 @@
|
||||
---
|
||||
name: get-git-diff
|
||||
description: Examines git diffs between commits or branches with intelligent analysis. Provides unified diff format with comprehensive summaries including file statistics, rename detection, and merge commit handling. Outputs formatted diffs to /claudedocs for documentation and review purposes.
|
||||
---
|
||||
|
||||
# Git Diff Analyzer
|
||||
|
||||
## <20> MANDATORY COMPLIANCE <20>
|
||||
|
||||
**CRITICAL**: The 4-step workflow outlined in this document MUST be followed in exact order for EVERY diff analysis. Skipping steps or deviating from the procedure will result in incomplete analysis. This is non-negotiable.
|
||||
|
||||
## File Structure
|
||||
|
||||
- **SKILL.md** (this file): Main instructions and MANDATORY workflow
|
||||
- **examples.md**: Usage scenarios with different diff types
|
||||
- **../../context/git/**: Shared git context files
|
||||
- `git_diff_reference.md`: Unified diff format reference and best practices
|
||||
- `diff_patterns.md`: Common patterns to identify in code changes
|
||||
- **../../memory/skills/get-git-diff/**: Project-specific diff analysis memory
|
||||
- `{project-name}/`: Per-project diff patterns and insights
|
||||
- **scripts/**:
|
||||
- `README.md`: Complete documentation for all helper scripts
|
||||
- `validate.sh`: Git repository and commit validation functions
|
||||
- `commit_info.sh`: Commit metadata retrieval (hash, author, date, message)
|
||||
- `diff_stats.sh`: Diff statistics and line count analysis
|
||||
- `file_operations.sh`: File operation detection (add, modify, delete, rename)
|
||||
- `utils.sh`: General utilities (branch detection, formatting, repo info)
|
||||
- **templates/**:
|
||||
- `output_template.md`: Standard output format template
|
||||
|
||||
## Analysis Focus Areas
|
||||
|
||||
Git diff analysis evaluates 7 critical dimensions:
|
||||
|
||||
1. **Change Scope**: Files affected, lines modified, overall impact radius
|
||||
2. **Change Type**: Feature addition, bug fix, refactoring, configuration change
|
||||
3. **Structural Changes**: File renames, moves, deletions, additions
|
||||
4. **Risk Assessment**: Breaking changes, API modifications, database migrations
|
||||
5. **Code Quality Impact**: Complexity changes, test coverage changes
|
||||
6. **Merge Conflicts**: Merge commit analysis, conflict resolution patterns
|
||||
7. **Performance Impact**: Algorithm changes, database query modifications, resource usage
|
||||
|
||||
**Note**: Analysis depth is summary-level, focusing on what changed and high-level impact.
|
||||
|
||||
---
|
||||
|
||||
## MANDATORY WORKFLOW (MUST FOLLOW EXACTLY)
|
||||
|
||||
### <20> STEP 1: Commit Identification (REQUIRED)
|
||||
|
||||
**YOU MUST:**
|
||||
1. Check if commit hashes/branch names were provided in the triggering prompt
|
||||
2. If NOT provided, ask the user with these options:
|
||||
- **Option A**: Compare specific commit hashes (ask for two commit SHAs)
|
||||
- **Option B**: Compare HEAD of current branch to main/master
|
||||
- **Option C**: Compare two branch names
|
||||
- **Option D**: Compare current changes to a specific commit
|
||||
3. Validate that provided commits/branches exist in the repository
|
||||
4. Use `git rev-parse` to verify and get full commit hashes
|
||||
5. Get commit metadata (author, date, message) for both commits
|
||||
|
||||
**DO NOT PROCEED WITHOUT VALID COMMITS**
|
||||
|
||||
### <20> STEP 2: Execute Git Diff with Special Handling (REQUIRED)
|
||||
|
||||
**YOU MUST:**
|
||||
1. Execute `git diff [commit1]...[commit2]` to get the unified diff
|
||||
2. Check for and handle special cases:
|
||||
- **Large diffs** (>1000 lines): Warn user, offer to summarize only or proceed
|
||||
- **Renamed files**: Use `git diff -M` to detect renames
|
||||
- **Merge commits**: Use `git diff [commit]^...[commit]` for merge commit analysis
|
||||
- **Binary files**: Note binary file changes separately
|
||||
3. Get diff statistics with `git diff --stat`
|
||||
4. Get file list with `git diff --name-status` to identify A/M/D/R operations
|
||||
|
||||
**DO NOT SKIP SPECIAL CASE DETECTION**
|
||||
|
||||
### <20> STEP 3: Analyze and Summarize (REQUIRED)
|
||||
|
||||
**YOU MUST analyze and document**:
|
||||
1. **Commit Metadata**:
|
||||
- Commit hashes (full and short)
|
||||
- Author and date for both commits
|
||||
- Commit messages
|
||||
- Number of commits between the two refs (if applicable)
|
||||
|
||||
2. **Change Statistics**:
|
||||
- Total files changed
|
||||
- Total insertions (+)
|
||||
- Total deletions (-)
|
||||
- Net change
|
||||
|
||||
3. **File Operations**:
|
||||
- Added files (A)
|
||||
- Modified files (M)
|
||||
- Deleted files (D)
|
||||
- Renamed files (R) - show old vs new
|
||||
- Copied files (C)
|
||||
|
||||
4. **Change Categorization**:
|
||||
- Group files by type (source code, tests, docs, config)
|
||||
- Identify potential areas of impact
|
||||
- Flag potentially risky changes
|
||||
|
||||
5. **Special Notes**:
|
||||
- Merge commit indicator (if applicable)
|
||||
- Large diff warning (if >1000 lines)
|
||||
- Binary file changes
|
||||
- Submodule changes
|
||||
|
||||
**DO NOT SKIP ANALYSIS**
|
||||
|
||||
### <20> STEP 4: Generate Output & Update Project Memory (REQUIRED)
|
||||
|
||||
**YOU MUST:**
|
||||
1. Use the template from `templates/output_template.md`
|
||||
2. Create filename: `diff_{short_hash1}_{short_hash2}.md`
|
||||
3. Include all components:
|
||||
- Header with commit information
|
||||
- Summary section with all statistics and analysis
|
||||
- Full unified diff wrapped in markdown code blocks
|
||||
4. Save to `/claudedocs/` directory
|
||||
5. Confirm file was written successfully
|
||||
|
||||
**Output Format Requirements**:
|
||||
- Unified diff must be in triple-backtick code blocks with `diff` language tag
|
||||
- Summary must be in clear markdown sections
|
||||
- File paths must use code formatting
|
||||
- Statistics must be in tables or lists
|
||||
- All sections must be clearly labeled
|
||||
|
||||
**DO NOT OMIT ANY REQUIRED SECTIONS**
|
||||
|
||||
**OPTIONAL: Update Project Memory**
|
||||
|
||||
If patterns emerge during analysis, consider storing insights in `../../memory/skills/get-git-diff/{project-name}/`:
|
||||
- Common file change patterns
|
||||
- Frequently modified areas
|
||||
- Notable commit patterns or conventions
|
||||
|
||||
---
|
||||
|
||||
## Special Case Handling
|
||||
|
||||
### Large Diffs (>1000 lines)
|
||||
|
||||
When encountering large diffs:
|
||||
1. Calculate total line count
|
||||
2. Warn user: "This diff contains [N] lines across [M] files"
|
||||
3. Ask user: "Would you like to proceed with full diff or summary only?"
|
||||
4. If summary only:
|
||||
- Include all metadata and statistics
|
||||
- List all changed files with their line counts
|
||||
- Omit the detailed unified diff
|
||||
- Note: "Full diff omitted due to size. Use `git diff [hash1]...[hash2]` to view."
|
||||
|
||||
### Renamed/Moved Files
|
||||
|
||||
For file renames:
|
||||
1. Use `git diff -M` flag to detect renames (default similarity index: 50%)
|
||||
2. In summary, clearly show: `old/path/file.py <20> new/path/file.py`
|
||||
3. Indicate if content was also modified: `R+M` (renamed and modified)
|
||||
4. In unified diff, show rename header: `rename from/to`
|
||||
|
||||
### Merge Commits
|
||||
|
||||
For merge commits:
|
||||
1. Detect with `git rev-list --merges`
|
||||
2. Note in summary: "This is a merge commit"
|
||||
3. Show both parent commits
|
||||
4. Use `git diff [commit]^...[commit]` to show changes introduced by merge
|
||||
5. Optionally offer to show diff against each parent separately
|
||||
|
||||
---
|
||||
|
||||
## Compliance Checklist
|
||||
|
||||
Before completing ANY diff analysis, verify:
|
||||
- [ ] Step 1: Commits identified and validated
|
||||
- [ ] Step 2: Git diff executed with special case detection
|
||||
- [ ] Step 3: Complete analysis with all statistics and categorization
|
||||
- [ ] Step 4: Output generated in correct format and saved to /claudedocs
|
||||
|
||||
**FAILURE TO COMPLETE ALL STEPS INVALIDATES THE ANALYSIS**
|
||||
|
||||
---
|
||||
|
||||
## Output File Naming Convention
|
||||
|
||||
**Format**: `diff_{short1}_{short2}.md`
|
||||
|
||||
Where:
|
||||
- `{short1}` = First 7 characters of first commit hash
|
||||
- `{short2}` = First 7 characters of second commit hash
|
||||
|
||||
**Examples**:
|
||||
- `diff_a1b2c3d_e4f5g6h.md` (commit to commit)
|
||||
- `diff_main_feature-branch.md` (branch comparison, if hashes not available)
|
||||
|
||||
**Alternative for branches**: If comparing branch tips, you may use branch names if they're short and filesystem-safe.
|
||||
|
||||
---
|
||||
|
||||
## Git Commands Reference
|
||||
|
||||
### Core Commands Used:
|
||||
```bash
|
||||
# Get commit info
|
||||
git rev-parse [commit]
|
||||
git log -1 --format="%H|%h|%an|%ae|%ad|%s" [commit]
|
||||
|
||||
# Generate diff
|
||||
git diff [commit1]...[commit2]
|
||||
git diff --stat [commit1]...[commit2]
|
||||
git diff --name-status [commit1]...[commit2]
|
||||
git diff -M [commit1]...[commit2] # Detect renames
|
||||
|
||||
# Special cases
|
||||
git rev-list --merges [commit] # Check if merge commit
|
||||
git diff [commit]^1..[commit] # Merge commit against first parent
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Further Reading
|
||||
|
||||
Refer to official documentation:
|
||||
- **Git Documentation**:
|
||||
- Git Diff: https://git-scm.com/docs/git-diff
|
||||
- Diff Format: https://git-scm.com/docs/diff-format
|
||||
- **Best Practices**:
|
||||
- Pro Git Book: https://git-scm.com/book/en/v2
|
||||
- Understanding Git Diff: https://git-scm.com/docs/git-diff#_generating_patches_with_p
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
- v1.1.0 (2025-01-XX): Centralized context and project memory
|
||||
- Context files moved to ../../context/git/
|
||||
- Project-specific memory system in ../../memory/skills/get-git-diff/
|
||||
- Optional memory updates for common patterns
|
||||
- v1.0.0 (2025-11-13): Initial release
|
||||
- Mandatory 4-step workflow
|
||||
- Summary-level analysis with statistics
|
||||
- Special handling for large diffs, renames, and merge commits
|
||||
- Unified diff output to /claudedocs
|
||||
510
skills/get-git-diff/examples.md
Normal file
510
skills/get-git-diff/examples.md
Normal file
@@ -0,0 +1,510 @@
|
||||
# Git Diff Analyzer Examples
|
||||
|
||||
This file contains example scenarios demonstrating how to use the get-git-diff skill for various diff analysis tasks.
|
||||
|
||||
---
|
||||
|
||||
## Example 1: Comparing Two Specific Commits
|
||||
|
||||
### Scenario
|
||||
Developer wants to see what changed between two feature commits.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
I need to see the diff between commits a1b2c3d and e4f5g6h
|
||||
```
|
||||
|
||||
### Skill Execution
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
- Commits provided: `a1b2c3d` and `e4f5g6h`
|
||||
- Validate: `git rev-parse a1b2c3d e4f5g6h`
|
||||
- Get metadata: `git log -1 --format="%H|%h|%an|%ae|%ad|%s" [commit]`
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
git diff a1b2c3d...e4f5g6h
|
||||
git diff --stat a1b2c3d...e4f5g6h
|
||||
git diff --name-status a1b2c3d...e4f5g6h
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Commit Metadata:
|
||||
- From: a1b2c3d (John Doe, 2025-11-01, "Add user authentication")
|
||||
- To: e4f5g6h (Jane Smith, 2025-11-13, "Refactor auth module")
|
||||
- Commits between: 15
|
||||
|
||||
Statistics:
|
||||
- Files changed: 8
|
||||
- Insertions: 234
|
||||
- Deletions: 156
|
||||
- Net change: +78
|
||||
|
||||
File Operations:
|
||||
- Modified: src/auth/login.py, src/auth/session.py, tests/test_auth.py
|
||||
- Added: src/auth/jwt_handler.py, src/auth/validators.py
|
||||
- Deleted: src/auth/legacy_auth.py
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_a1b2c3d_e4f5g6h.md`
|
||||
|
||||
---
|
||||
|
||||
## Example 2: Comparing Current Branch to Main
|
||||
|
||||
### Scenario
|
||||
Developer wants to review all changes in feature branch before creating a PR.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Show me the diff for my feature branch compared to main
|
||||
```
|
||||
|
||||
### Skill Response
|
||||
Since no specific commits provided, ask user:
|
||||
- Option A: Compare specific commit hashes
|
||||
- Option B: Compare HEAD of current branch to main ← **User selects this**
|
||||
- Option C: Compare two branch names
|
||||
- Option D: Compare current changes to a specific commit
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
```bash
|
||||
# Get current branch
|
||||
git rev-parse --abbrev-ref HEAD # Returns: feature/new-api
|
||||
|
||||
# Get commit hashes
|
||||
git rev-parse main # Returns: d7e8f9a...
|
||||
git rev-parse HEAD # Returns: m3n4o5p...
|
||||
```
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
git diff main...HEAD
|
||||
git diff --stat main...HEAD
|
||||
git diff --name-status main...HEAD
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Comparing: main (d7e8f9a) → feature/new-api (m3n4o5p)
|
||||
|
||||
Statistics:
|
||||
- Files changed: 12
|
||||
- Insertions: 567
|
||||
- Deletions: 89
|
||||
- Net change: +478
|
||||
|
||||
File Operations:
|
||||
- Added: src/api/v2/endpoints.py, src/api/v2/schemas.py, tests/api/test_v2.py
|
||||
- Modified: src/api/router.py, src/main.py, README.md
|
||||
- Deleted: (none)
|
||||
|
||||
Change Categorization:
|
||||
- Source code: 8 files (src/api/*, src/main.py)
|
||||
- Tests: 3 files (tests/api/*)
|
||||
- Documentation: 1 file (README.md)
|
||||
|
||||
Impact Areas:
|
||||
- API routing layer
|
||||
- Request/response schemas
|
||||
- Test coverage
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_d7e8f9a_m3n4o5p.md`
|
||||
|
||||
---
|
||||
|
||||
## Example 3: Large Diff Handling
|
||||
|
||||
### Scenario
|
||||
Comparing two commits with extensive changes (e.g., major refactoring or dependency update).
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Compare commit abc1234 to def5678
|
||||
```
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
git diff --stat abc1234...def5678
|
||||
# Output shows: 87 files changed, 15234 insertions(+), 12456 deletions(-)
|
||||
```
|
||||
|
||||
**Large Diff Detected**: Total lines = 27,690
|
||||
|
||||
**Skill Response**:
|
||||
⚠️ **Large Diff Warning**
|
||||
|
||||
This diff contains 27,690 lines across 87 files. This is a substantial change set.
|
||||
|
||||
Would you like to:
|
||||
- **Option A**: Proceed with full diff (will be very long)
|
||||
- **Option B**: Summary only (statistics and file list, no line-by-line diff)
|
||||
|
||||
**User selects**: Option B - Summary only
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Commit Metadata:
|
||||
- From: abc1234 (Team Lead, 2025-10-15, "Update dependencies to Python 3.12")
|
||||
- To: def5678 (Team Lead, 2025-11-13, "Complete Python 3.12 migration")
|
||||
|
||||
Statistics:
|
||||
- Files changed: 87
|
||||
- Insertions: 15,234
|
||||
- Deletions: 12,456
|
||||
- Net change: +2,778
|
||||
|
||||
Top Changed Files:
|
||||
1. requirements.txt (+45, -32)
|
||||
2. src/core/utils.py (+234, -189)
|
||||
3. tests/test_compatibility.py (+456, -123)
|
||||
... (84 more files)
|
||||
|
||||
Change Categorization:
|
||||
- Dependency files: 3 files
|
||||
- Source code: 62 files
|
||||
- Tests: 18 files
|
||||
- Documentation: 4 files
|
||||
|
||||
Impact Assessment:
|
||||
⚠️ Major refactoring detected
|
||||
⚠️ Breaking changes likely
|
||||
⚠️ Extensive test updates
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_abc1234_def5678.md`
|
||||
|
||||
**Note in output**: "Full unified diff omitted due to size (27,690 lines). Use `git diff abc1234...def5678` to view complete diff."
|
||||
|
||||
---
|
||||
|
||||
## Example 4: Renamed Files Detection
|
||||
|
||||
### Scenario
|
||||
Developer refactored project structure, moving files to new directories.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Show me what changed in the refactoring commit x9y8z7w
|
||||
```
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
- Comparing: x9y8z7w^ (parent) → x9y8z7w (the refactoring commit)
|
||||
|
||||
**Step 2 - Execute Diff with Rename Detection**:
|
||||
```bash
|
||||
git diff -M x9y8z7w^...x9y8z7w
|
||||
git diff --name-status -M x9y8z7w^...x9y8z7w
|
||||
```
|
||||
|
||||
Output:
|
||||
```
|
||||
R100 src/utils.py → src/core/utils.py
|
||||
R100 src/helpers.py → src/core/helpers.py
|
||||
R085 src/auth.py → src/auth/main.py
|
||||
M src/main.py
|
||||
A src/core/__init__.py
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Commit Metadata:
|
||||
- Commit: x9y8z7w (Developer, 2025-11-12, "Refactor: reorganize project structure")
|
||||
|
||||
Statistics:
|
||||
- Files changed: 6
|
||||
- Insertions: 45
|
||||
- Deletions: 12
|
||||
- Net change: +33
|
||||
|
||||
File Operations:
|
||||
- Renamed (no changes):
|
||||
• src/utils.py → src/core/utils.py
|
||||
• src/helpers.py → src/core/helpers.py
|
||||
|
||||
- Renamed + Modified (85% similarity):
|
||||
• src/auth.py → src/auth/main.py
|
||||
|
||||
- Modified:
|
||||
• src/main.py (updated imports)
|
||||
|
||||
- Added:
|
||||
• src/core/__init__.py (new package init)
|
||||
|
||||
Change Type: Refactoring
|
||||
Risk Assessment: Medium (import paths changed, may break external references)
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_w6v5u4t_x9y8z7w.md`
|
||||
|
||||
---
|
||||
|
||||
## Example 5: Merge Commit Analysis
|
||||
|
||||
### Scenario
|
||||
Developer wants to understand what a merge commit introduced.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Analyze the merge commit m1e2r3g4
|
||||
```
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
```bash
|
||||
git rev-list --merges m1e2r3g4^..m1e2r3g4
|
||||
# Confirms: m1e2r3g4 is a merge commit
|
||||
|
||||
git log -1 --format="%P" m1e2r3g4
|
||||
# Returns: a1b2c3d e4f5g6h (two parents)
|
||||
```
|
||||
|
||||
**Skill Response**:
|
||||
⚠️ **Merge Commit Detected**
|
||||
|
||||
This is a merge commit with two parents:
|
||||
- Parent 1: a1b2c3d (main branch)
|
||||
- Parent 2: e4f5g6h (feature branch)
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
# Show changes introduced by the merge (against first parent)
|
||||
git diff m1e2r3g4^1...m1e2r3g4
|
||||
git diff --stat m1e2r3g4^1...m1e2r3g4
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Merge Commit Metadata:
|
||||
- Commit: m1e2r3g4
|
||||
- Author: John Doe
|
||||
- Date: 2025-11-13
|
||||
- Message: "Merge feature/user-profiles into main"
|
||||
- Parent 1 (base): a1b2c3d (main)
|
||||
- Parent 2 (merged): e4f5g6h (feature/user-profiles)
|
||||
|
||||
Statistics:
|
||||
- Files changed: 15
|
||||
- Insertions: 892
|
||||
- Deletions: 134
|
||||
- Net change: +758
|
||||
|
||||
Merged Branch Summary:
|
||||
- Branch: feature/user-profiles
|
||||
- Commits merged: 23
|
||||
- Primary changes:
|
||||
• New user profile management system
|
||||
• Profile API endpoints
|
||||
• Profile database models
|
||||
• Associated tests
|
||||
|
||||
Change Categorization:
|
||||
- Source code: 9 files (src/profiles/*, src/api/profiles.py)
|
||||
- Database: 2 files (migrations/*, models/profile.py)
|
||||
- Tests: 3 files (tests/profiles/*)
|
||||
- Documentation: 1 file (docs/api.md)
|
||||
|
||||
Merge Notes:
|
||||
✓ No merge conflicts (clean merge)
|
||||
✓ All tests passing (based on commit message)
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_a1b2c3d_m1e2r3g4.md`
|
||||
|
||||
Special note in file: "This is a merge commit analysis showing changes introduced by merging feature/user-profiles into main."
|
||||
|
||||
---
|
||||
|
||||
## Example 6: Comparing Branches by Name
|
||||
|
||||
### Scenario
|
||||
Developer wants to see differences between two branches without knowing commit hashes.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Compare the staging branch to the production branch
|
||||
```
|
||||
|
||||
**Skill Response**:
|
||||
No specific commits provided. Asking user for comparison target...
|
||||
|
||||
User selects: **Option C - Compare two branch names**
|
||||
|
||||
Please provide:
|
||||
- First branch: **production**
|
||||
- Second branch: **staging**
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
```bash
|
||||
git rev-parse production # Returns: p1r2o3d...
|
||||
git rev-parse staging # Returns: s4t5a6g...
|
||||
|
||||
git log --oneline production..staging
|
||||
# Shows: 8 commits ahead
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Comparing Branches:
|
||||
- production (p1r2o3d): "Release v2.1.0" (2025-11-01)
|
||||
- staging (s4t5a6g): "Add feature X" (2025-11-13)
|
||||
- Commits ahead: 8
|
||||
- Commits behind: 0
|
||||
|
||||
Statistics:
|
||||
- Files changed: 24
|
||||
- Insertions: 1,234
|
||||
- Deletions: 456
|
||||
- Net change: +778
|
||||
|
||||
File Operations:
|
||||
- Added: 8 files (new features)
|
||||
- Modified: 14 files (enhancements)
|
||||
- Deleted: 2 files (deprecated code)
|
||||
|
||||
Impact Assessment:
|
||||
✓ Ready for production deployment
|
||||
✓ All features tested in staging
|
||||
⚠️ Database migration required (see migrations/0023_add_feature_x.py)
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_p1r2o3d_s4t5a6g.md`
|
||||
|
||||
---
|
||||
|
||||
## Example 7: Current Working Directory Changes
|
||||
|
||||
### Scenario
|
||||
Developer has uncommitted changes and wants to see diff against last commit.
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Show me my current uncommitted changes
|
||||
```
|
||||
|
||||
**Skill Response**:
|
||||
No specific commits provided. Asking user...
|
||||
|
||||
User selects: **Option D - Compare current changes to a specific commit**
|
||||
|
||||
Please provide the commit to compare against (or press Enter for HEAD): **[User presses Enter]**
|
||||
|
||||
**Step 1 - Commit Identification**:
|
||||
```bash
|
||||
# Comparing working directory against HEAD
|
||||
git rev-parse HEAD # Returns: h3e4a5d...
|
||||
```
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
git diff HEAD
|
||||
git diff --stat HEAD
|
||||
git diff --name-status HEAD
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Working Directory Changes:
|
||||
- Base commit: h3e4a5d (HEAD, "Latest commit")
|
||||
- Status: Uncommitted changes
|
||||
|
||||
Statistics:
|
||||
- Files changed: 3
|
||||
- Insertions: 67
|
||||
- Deletions: 23
|
||||
- Net change: +44
|
||||
|
||||
File Operations:
|
||||
- Modified:
|
||||
• src/api/endpoints.py (+45, -12)
|
||||
• tests/test_api.py (+18, -8)
|
||||
• README.md (+4, -3)
|
||||
|
||||
Change Type: Feature development (in progress)
|
||||
Status: ⚠️ Uncommitted - not yet in version control
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File: `/claudedocs/diff_h3e4a5d_working.md`
|
||||
|
||||
---
|
||||
|
||||
## Example 8: Binary Files in Diff
|
||||
|
||||
### Scenario
|
||||
Diff includes binary file changes (images, PDFs, compiled files).
|
||||
|
||||
### User Prompt
|
||||
```
|
||||
Compare commit old123 to new456
|
||||
```
|
||||
|
||||
**Step 2 - Execute Diff**:
|
||||
```bash
|
||||
git diff --stat old123...new456
|
||||
git diff --name-status old123...new456
|
||||
```
|
||||
|
||||
Output includes:
|
||||
```
|
||||
M docs/architecture.pdf
|
||||
M static/images/logo.png
|
||||
A static/images/hero.jpg
|
||||
```
|
||||
|
||||
**Step 3 - Analysis**:
|
||||
```
|
||||
Statistics:
|
||||
- Files changed: 15
|
||||
- Text files: 12 (analyzed in diff)
|
||||
- Binary files: 3 (listed separately)
|
||||
|
||||
Text File Changes:
|
||||
- Insertions: 234
|
||||
- Deletions: 156
|
||||
- Net change: +78
|
||||
|
||||
Binary File Changes:
|
||||
- Modified:
|
||||
• docs/architecture.pdf (binary file changed)
|
||||
• static/images/logo.png (binary file changed)
|
||||
- Added:
|
||||
• static/images/hero.jpg (new binary file)
|
||||
|
||||
Note: Binary file content changes not shown in diff.
|
||||
Use `git show old123:path` and `git show new456:path` to extract binary files.
|
||||
```
|
||||
|
||||
**Step 4 - Output**:
|
||||
File includes note: "⚠️ This diff contains 3 binary files. Binary content not displayed. See summary for list of affected binary files."
|
||||
|
||||
---
|
||||
|
||||
## Summary of Use Cases
|
||||
|
||||
1. **Two specific commits** - Direct comparison with full metadata
|
||||
2. **Branch to main** - Pre-PR review workflow
|
||||
3. **Large diffs** - Summary-only option for massive changes
|
||||
4. **Renamed files** - Detect and document file restructuring
|
||||
5. **Merge commits** - Special handling with parent information
|
||||
6. **Branch comparison** - Compare branch tips by name
|
||||
7. **Working directory** - Review uncommitted changes
|
||||
8. **Binary files** - Special notation for non-text files
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Always validate commits exist before running diff
|
||||
- For large diffs, offer summary option first
|
||||
- Clearly indicate merge commits with special notation
|
||||
- Show both old and new paths for renamed files
|
||||
- Categorize changes by file type and impact area
|
||||
- Provide actionable insights in the summary
|
||||
- Save output with descriptive filenames
|
||||
- Include enough metadata for audit trail
|
||||
240
skills/get-git-diff/scripts/README.md
Normal file
240
skills/get-git-diff/scripts/README.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Git Diff Helper Scripts
|
||||
|
||||
This directory contains bash utility scripts for git diff operations used by the get-git-diff skill.
|
||||
|
||||
## Scripts Overview
|
||||
|
||||
### validate.sh
|
||||
Validation and verification functions.
|
||||
|
||||
**Functions:**
|
||||
- `is_git_repo()` - Check if in a git repository
|
||||
- `validate_commit <ref>` - Validate a commit reference
|
||||
- `validate_commit_pair <ref1> <ref2>` - Validate two commits
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
source validate.sh
|
||||
if is_git_repo; then
|
||||
validate_commit "HEAD"
|
||||
fi
|
||||
```
|
||||
|
||||
### commit_info.sh
|
||||
Commit information and metadata retrieval.
|
||||
|
||||
**Functions:**
|
||||
- `get_commit_info <ref>` - Get full commit information
|
||||
- `get_short_hash <ref>` - Get 7-character hash
|
||||
- `is_merge_commit <ref>` - Check if merge commit
|
||||
- `get_commits_between <ref1> <ref2>` - Count commits between refs
|
||||
- `get_commit_message <ref>` - Get commit message
|
||||
- `get_commit_author <ref>` - Get commit author
|
||||
- `get_commit_date <ref>` - Get commit date
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
source commit_info.sh
|
||||
info=$(get_commit_info "HEAD")
|
||||
IFS='|' read -r full short author email date message <<< "$info"
|
||||
echo "Commit: $short - $message"
|
||||
```
|
||||
|
||||
### diff_stats.sh
|
||||
Diff statistics and analysis.
|
||||
|
||||
**Functions:**
|
||||
- `get_diff_stats <ref1> <ref2>` - Get diff statistics
|
||||
- `get_file_stats <ref1> <ref2>` - Get per-file statistics
|
||||
- `is_large_diff <ref1> <ref2> [threshold]` - Check if diff is large
|
||||
- `get_total_lines <ref1> <ref2>` - Get total lines changed
|
||||
- `get_files_changed <ref1> <ref2>` - Get file count
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
source diff_stats.sh
|
||||
stats=$(get_diff_stats "HEAD^" "HEAD")
|
||||
IFS=$'\t' read -r files ins del net <<< "$stats"
|
||||
echo "Changed: $files files, +$ins -$del"
|
||||
```
|
||||
|
||||
### file_operations.sh
|
||||
File operation analysis (add, modify, delete, rename).
|
||||
|
||||
**Functions:**
|
||||
- `get_file_operations <ref1> <ref2>` - Get file operations
|
||||
- `count_file_operations <ref1> <ref2>` - Count operations by type
|
||||
- `get_files_by_operation <ref1> <ref2> <type>` - Filter by operation (A/M/D/R)
|
||||
- `get_renamed_files <ref1> <ref2>` - Get renames with similarity
|
||||
- `get_binary_files <ref1> <ref2>` - Get binary files
|
||||
- `count_binary_files <ref1> <ref2>` - Count binary files
|
||||
- `categorize_files` - Categorize files by type (reads from stdin)
|
||||
- `get_categorized_counts <ref1> <ref2>` - Get category counts
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
source file_operations.sh
|
||||
counts=$(count_file_operations "main" "feature-branch")
|
||||
IFS=$'\t' read -r added modified deleted renamed copied <<< "$counts"
|
||||
echo "Added: $added, Modified: $modified"
|
||||
```
|
||||
|
||||
### utils.sh
|
||||
General utility functions.
|
||||
|
||||
**Functions:**
|
||||
- `get_current_branch()` - Get current branch name
|
||||
- `get_default_branch()` - Get default branch (main/master)
|
||||
- `format_diff_filename <hash1> <hash2>` - Format output filename
|
||||
- `get_repo_root()` - Get repository root path
|
||||
- `get_repo_name()` - Get repository name
|
||||
- `get_remote_url [remote]` - Get remote URL
|
||||
- `is_ancestor <ref1> <ref2>` - Check if ref1 is ancestor of ref2
|
||||
- `get_common_ancestor <ref1> <ref2>` - Get merge base
|
||||
- `format_timestamp [format]` - Format current timestamp
|
||||
- `ensure_directory <path>` - Create directory if needed
|
||||
- `get_git_config <key>` - Get git config value
|
||||
- `is_working_tree_clean()` - Check for uncommitted changes
|
||||
- `get_branches [type]` - List branches (local/remote/all)
|
||||
- `ref_exists <ref>` - Check if ref exists
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
source utils.sh
|
||||
branch=$(get_current_branch)
|
||||
filename=$(format_diff_filename "abc123" "def456")
|
||||
```
|
||||
|
||||
## Running Scripts Directly
|
||||
|
||||
All scripts can be sourced for their functions or run directly for examples:
|
||||
|
||||
```bash
|
||||
# Run directly for examples
|
||||
./commit_info.sh HEAD
|
||||
./diff_stats.sh HEAD^ HEAD
|
||||
./file_operations.sh main feature-branch
|
||||
./utils.sh
|
||||
|
||||
# Source for functions
|
||||
source diff_stats.sh
|
||||
if is_large_diff "HEAD^" "HEAD" 500; then
|
||||
echo "Large diff detected!"
|
||||
fi
|
||||
```
|
||||
|
||||
## Complete Example
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Source all helper scripts
|
||||
SCRIPT_DIR="$(dirname "${BASH_SOURCE[0]}")"
|
||||
source "${SCRIPT_DIR}/validate.sh"
|
||||
source "${SCRIPT_DIR}/commit_info.sh"
|
||||
source "${SCRIPT_DIR}/diff_stats.sh"
|
||||
source "${SCRIPT_DIR}/file_operations.sh"
|
||||
source "${SCRIPT_DIR}/utils.sh"
|
||||
|
||||
# Validate we're in a git repo
|
||||
if ! is_git_repo; then
|
||||
echo "Error: Not in a git repository"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get commits to compare
|
||||
commit1="HEAD^"
|
||||
commit2="HEAD"
|
||||
|
||||
# Validate commits
|
||||
if ! validate_commit_pair "$commit1" "$commit2" >/dev/null; then
|
||||
echo "Error: Invalid commits"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get commit info
|
||||
info1=$(get_commit_info "$commit1")
|
||||
info2=$(get_commit_info "$commit2")
|
||||
IFS='|' read -r _ short1 _ _ _ msg1 <<< "$info1"
|
||||
IFS='|' read -r _ short2 _ _ _ msg2 <<< "$info2"
|
||||
|
||||
echo "Comparing: $short1 → $short2"
|
||||
|
||||
# Get statistics
|
||||
stats=$(get_diff_stats "$commit1" "$commit2")
|
||||
IFS=$'\t' read -r files ins del net <<< "$stats"
|
||||
|
||||
echo "Files: $files"
|
||||
echo "Changes: +$ins -$del (net: $net)"
|
||||
|
||||
# Check if large
|
||||
if is_large_diff "$commit1" "$commit2"; then
|
||||
total=$(is_large_diff "$commit1" "$commit2")
|
||||
echo "⚠ Large diff: $total lines"
|
||||
fi
|
||||
|
||||
# File operations
|
||||
ops=$(count_file_operations "$commit1" "$commit2")
|
||||
IFS=$'\t' read -r added modified deleted renamed _ <<< "$ops"
|
||||
|
||||
echo "Added: $added, Modified: $modified"
|
||||
if [[ $renamed -gt 0 ]]; then
|
||||
echo "Renamed files:"
|
||||
get_renamed_files "$commit1" "$commit2" | while IFS=$'\t' read -r sim old new; do
|
||||
echo " $old → $new ($sim%)"
|
||||
done
|
||||
fi
|
||||
|
||||
# Generate filename
|
||||
filename=$(format_diff_filename "$short1" "$short2")
|
||||
echo "Output: $filename"
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
All scripts use `set -euo pipefail` for strict error handling:
|
||||
- `-e`: Exit on error
|
||||
- `-u`: Exit on undefined variable
|
||||
- `-o pipefail`: Fail on pipe errors
|
||||
|
||||
When sourcing, you may want to disable this:
|
||||
```bash
|
||||
set +euo pipefail
|
||||
source diff_stats.sh
|
||||
set -euo pipefail
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- bash 4.0+
|
||||
- git 2.0+
|
||||
- Standard Unix utilities (awk, sed, cut, wc)
|
||||
|
||||
## Testing
|
||||
|
||||
Each script includes a main section that runs when executed directly:
|
||||
|
||||
```bash
|
||||
# Test all scripts
|
||||
./validate.sh HEAD
|
||||
./commit_info.sh HEAD HEAD^
|
||||
./diff_stats.sh HEAD^ HEAD
|
||||
./file_operations.sh HEAD^ HEAD
|
||||
./utils.sh
|
||||
```
|
||||
|
||||
## Integration with Skill
|
||||
|
||||
These scripts are designed to be used by Claude Code when executing the get-git-diff skill. They provide reliable, composable functions for git diff analysis.
|
||||
|
||||
Example integration:
|
||||
```bash
|
||||
# In skill execution
|
||||
source scripts/validate.sh
|
||||
source scripts/diff_stats.sh
|
||||
|
||||
if validate_commit "$user_commit"; then
|
||||
stats=$(get_diff_stats "$user_commit" "HEAD")
|
||||
# Process stats...
|
||||
fi
|
||||
```
|
||||
157
skills/get-git-diff/scripts/commit_info.sh
Executable file
157
skills/get-git-diff/scripts/commit_info.sh
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Git Commit Information Functions
|
||||
#
|
||||
# Functions for retrieving commit metadata and relationships.
|
||||
#
|
||||
# Author: Claude Code get-git-diff skill
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#######################################
|
||||
# Get detailed information about a commit
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# Outputs:
|
||||
# Pipe-separated: full_hash|short_hash|author_name|author_email|date|message
|
||||
#######################################
|
||||
get_commit_info() {
|
||||
local commit_ref="$1"
|
||||
local format="%H|%h|%an|%ae|%ad|%s"
|
||||
|
||||
git log -1 --format="${format}" "${commit_ref}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get short hash from commit reference
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# $2 - length (optional, default: 7)
|
||||
# Outputs:
|
||||
# Short hash
|
||||
#######################################
|
||||
get_short_hash() {
|
||||
local commit_ref="$1"
|
||||
local length="${2:-7}"
|
||||
|
||||
git rev-parse --short="${length}" "${commit_ref}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check if a commit is a merge commit
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# Returns:
|
||||
# 0 if merge commit, 1 if not
|
||||
# Outputs:
|
||||
# Parent hashes separated by spaces
|
||||
#######################################
|
||||
is_merge_commit() {
|
||||
local commit_ref="$1"
|
||||
local parents
|
||||
|
||||
if parents=$(git log -1 --format="%P" "${commit_ref}" 2>/dev/null); then
|
||||
local parent_count=$(echo "${parents}" | wc -w)
|
||||
if [[ ${parent_count} -gt 1 ]]; then
|
||||
echo "${parents}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get number of commits between two refs
|
||||
# Arguments:
|
||||
# $1 - first commit (older)
|
||||
# $2 - second commit (newer)
|
||||
# Outputs:
|
||||
# Number of commits between them
|
||||
#######################################
|
||||
get_commits_between() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
|
||||
git rev-list --count "${commit1}..${commit2}" 2>/dev/null || echo "0"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get commit message (first line)
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# Outputs:
|
||||
# Commit message subject line
|
||||
#######################################
|
||||
get_commit_message() {
|
||||
local commit_ref="$1"
|
||||
|
||||
git log -1 --format="%s" "${commit_ref}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get commit author
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# Outputs:
|
||||
# Author name and email
|
||||
#######################################
|
||||
get_commit_author() {
|
||||
local commit_ref="$1"
|
||||
|
||||
git log -1 --format="%an <%ae>" "${commit_ref}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get commit date
|
||||
# Arguments:
|
||||
# $1 - commit reference
|
||||
# Outputs:
|
||||
# Commit date
|
||||
#######################################
|
||||
get_commit_date() {
|
||||
local commit_ref="$1"
|
||||
|
||||
git log -1 --format="%ad" "${commit_ref}" 2>/dev/null
|
||||
}
|
||||
|
||||
# Allow script to be sourced or run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
# Example usage
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 <commit-ref> [<commit-ref2>]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 HEAD"
|
||||
echo " $0 HEAD HEAD^"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
commit1="$1"
|
||||
commit2="${2:-}"
|
||||
|
||||
echo "=== Commit Info for ${commit1} ==="
|
||||
info=$(get_commit_info "${commit1}")
|
||||
IFS='|' read -r full short author email date message <<< "${info}"
|
||||
echo "Full hash: ${full}"
|
||||
echo "Short hash: ${short}"
|
||||
echo "Author: ${author} <${email}>"
|
||||
echo "Date: ${date}"
|
||||
echo "Message: ${message}"
|
||||
|
||||
if is_merge_commit "${commit1}"; then
|
||||
parents=$(is_merge_commit "${commit1}")
|
||||
echo "Merge commit: yes"
|
||||
echo "Parents: ${parents}"
|
||||
else
|
||||
echo "Merge commit: no"
|
||||
fi
|
||||
|
||||
if [[ -n "${commit2}" ]]; then
|
||||
echo ""
|
||||
echo "=== Comparison: ${commit1} to ${commit2} ==="
|
||||
count=$(get_commits_between "${commit1}" "${commit2}")
|
||||
echo "Commits between: ${count}"
|
||||
fi
|
||||
fi
|
||||
178
skills/get-git-diff/scripts/diff_stats.sh
Executable file
178
skills/get-git-diff/scripts/diff_stats.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Git Diff Statistics Functions
|
||||
#
|
||||
# Functions for calculating and analyzing diff statistics.
|
||||
#
|
||||
# Author: Claude Code get-git-diff skill
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#######################################
|
||||
# Get diff statistics between two commits
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Tab-separated: files_changed insertions deletions net_change
|
||||
#######################################
|
||||
get_diff_stats() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local shortstat
|
||||
|
||||
if shortstat=$(git diff --shortstat "${commit1}...${commit2}" 2>/dev/null); then
|
||||
local files=0 insertions=0 deletions=0
|
||||
|
||||
# Parse: "3 files changed, 165 insertions(+), 20 deletions(-)"
|
||||
if [[ ${shortstat} =~ ([0-9]+)\ files?\ changed ]]; then
|
||||
files="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
if [[ ${shortstat} =~ ([0-9]+)\ insertions? ]]; then
|
||||
insertions="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
if [[ ${shortstat} =~ ([0-9]+)\ deletions? ]]; then
|
||||
deletions="${BASH_REMATCH[1]}"
|
||||
fi
|
||||
|
||||
local net_change=$((insertions - deletions))
|
||||
echo "${files} ${insertions} ${deletions} ${net_change}"
|
||||
else
|
||||
echo "0 0 0 0"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get detailed per-file statistics
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Tab-separated: insertions deletions filename (one per line)
|
||||
#######################################
|
||||
get_file_stats() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
|
||||
git diff --numstat "${commit1}...${commit2}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check if diff is large (exceeds threshold)
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# $3 - threshold (optional, default: 1000)
|
||||
# Returns:
|
||||
# 0 if large, 1 if not
|
||||
# Outputs:
|
||||
# Total line count if large
|
||||
#######################################
|
||||
is_large_diff() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local threshold="${3:-1000}"
|
||||
local stats
|
||||
|
||||
if stats=$(get_diff_stats "${commit1}" "${commit2}"); then
|
||||
local insertions deletions total_lines
|
||||
insertions=$(echo "${stats}" | cut -f2)
|
||||
deletions=$(echo "${stats}" | cut -f3)
|
||||
total_lines=$((insertions + deletions))
|
||||
|
||||
if [[ ${total_lines} -gt ${threshold} ]]; then
|
||||
echo "${total_lines}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get total line count in diff
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Total lines changed (insertions + deletions)
|
||||
#######################################
|
||||
get_total_lines() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local stats
|
||||
|
||||
if stats=$(get_diff_stats "${commit1}" "${commit2}"); then
|
||||
local insertions deletions
|
||||
insertions=$(echo "${stats}" | cut -f2)
|
||||
deletions=$(echo "${stats}" | cut -f3)
|
||||
echo $((insertions + deletions))
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get files changed count
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Number of files changed
|
||||
#######################################
|
||||
get_files_changed() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local stats
|
||||
|
||||
if stats=$(get_diff_stats "${commit1}" "${commit2}"); then
|
||||
echo "${stats}" | cut -f1
|
||||
else
|
||||
echo "0"
|
||||
fi
|
||||
}
|
||||
|
||||
# Allow script to be sourced or run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
# Example usage
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: $0 <commit1> <commit2> [threshold]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 HEAD^ HEAD"
|
||||
echo " $0 main feature-branch 500"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
commit1="$1"
|
||||
commit2="$2"
|
||||
threshold="${3:-1000}"
|
||||
|
||||
echo "=== Diff Statistics: ${commit1} → ${commit2} ==="
|
||||
stats=$(get_diff_stats "${commit1}" "${commit2}")
|
||||
IFS=$'\t' read -r files ins del net <<< "${stats}"
|
||||
|
||||
echo "Files changed: ${files}"
|
||||
echo "Insertions: +${ins}"
|
||||
echo "Deletions: -${del}"
|
||||
echo "Net change: ${net}"
|
||||
echo "Total lines: $((ins + del))"
|
||||
|
||||
if is_large_diff "${commit1}" "${commit2}" "${threshold}"; then
|
||||
total=$(is_large_diff "${commit1}" "${commit2}" "${threshold}")
|
||||
echo "⚠ Large diff detected (${total} lines, threshold: ${threshold})"
|
||||
else
|
||||
echo "✓ Normal size diff (threshold: ${threshold})"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Top 10 Changed Files ==="
|
||||
get_file_stats "${commit1}" "${commit2}" | head -10 | while IFS=$'\t' read -r ins del file; do
|
||||
if [[ "${ins}" == "-" && "${del}" == "-" ]]; then
|
||||
echo " ${file} (binary)"
|
||||
else
|
||||
echo " ${file} (+${ins}, -${del})"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
250
skills/get-git-diff/scripts/file_operations.sh
Executable file
250
skills/get-git-diff/scripts/file_operations.sh
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Git File Operations Functions
|
||||
#
|
||||
# Functions for analyzing file operations in diffs (add, modify, delete, rename).
|
||||
#
|
||||
# Author: Claude Code get-git-diff skill
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#######################################
|
||||
# Get file operations (add, modify, delete, rename)
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# $3 - detect renames (optional, default: true)
|
||||
# Outputs:
|
||||
# File operations, one per line (status TAB path)
|
||||
#######################################
|
||||
get_file_operations() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local detect_renames="${3:-true}"
|
||||
local cmd="git diff --name-status"
|
||||
|
||||
if [[ "${detect_renames}" == "true" ]]; then
|
||||
cmd="${cmd} -M"
|
||||
fi
|
||||
|
||||
${cmd} "${commit1}...${commit2}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Count file operations by type
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Tab-separated: added modified deleted renamed copied
|
||||
#######################################
|
||||
count_file_operations() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local operations
|
||||
|
||||
operations=$(get_file_operations "${commit1}" "${commit2}")
|
||||
|
||||
local added=0 modified=0 deleted=0 renamed=0 copied=0
|
||||
|
||||
while IFS= read -r line; do
|
||||
[[ -z "${line}" ]] && continue
|
||||
|
||||
local status="${line:0:1}"
|
||||
case "${status}" in
|
||||
A) ((added++)) ;;
|
||||
M) ((modified++)) ;;
|
||||
D) ((deleted++)) ;;
|
||||
R) ((renamed++)) ;;
|
||||
C) ((copied++)) ;;
|
||||
esac
|
||||
done <<< "${operations}"
|
||||
|
||||
echo "${added} ${modified} ${deleted} ${renamed} ${copied}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get files by operation type
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# $3 - operation type (A, M, D, R, C)
|
||||
# Outputs:
|
||||
# List of files, one per line
|
||||
#######################################
|
||||
get_files_by_operation() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local operation_type="$3"
|
||||
local operations
|
||||
|
||||
operations=$(get_file_operations "${commit1}" "${commit2}")
|
||||
|
||||
echo "${operations}" | awk -v op="${operation_type}" '$1 ~ "^"op {print $2}'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get renamed files with old and new paths
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Tab-separated: similarity old_path new_path (one per line)
|
||||
#######################################
|
||||
get_renamed_files() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local operations
|
||||
|
||||
operations=$(get_file_operations "${commit1}" "${commit2}" true)
|
||||
|
||||
echo "${operations}" | awk '
|
||||
/^R[0-9]+/ {
|
||||
similarity = substr($1, 2)
|
||||
old_path = $2
|
||||
new_path = $3
|
||||
print similarity "\t" old_path "\t" new_path
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get binary files in diff
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# List of binary file paths, one per line
|
||||
#######################################
|
||||
get_binary_files() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local numstat
|
||||
|
||||
if numstat=$(git diff --numstat "${commit1}...${commit2}" 2>/dev/null); then
|
||||
echo "${numstat}" | awk '$1 == "-" && $2 == "-" {print $3}'
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Count binary files
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Number of binary files
|
||||
#######################################
|
||||
count_binary_files() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
|
||||
get_binary_files "${commit1}" "${commit2}" | wc -l | tr -d ' '
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Categorize files by type
|
||||
# Arguments:
|
||||
# Reads file paths from stdin, one per line
|
||||
# Outputs:
|
||||
# Tab-separated: source tests docs config database other
|
||||
#######################################
|
||||
categorize_files() {
|
||||
local source=0 tests=0 docs=0 config=0 database=0 other=0
|
||||
|
||||
while IFS= read -r path; do
|
||||
[[ -z "${path}" ]] && continue
|
||||
|
||||
# Categorize based on path and extension
|
||||
if [[ "${path}" =~ test|tests/ ]]; then
|
||||
((tests++))
|
||||
elif [[ "${path}" =~ \.md$|doc ]]; then
|
||||
((docs++))
|
||||
elif [[ "${path}" =~ config|settings|\.env|\.ya?ml$|\.json$|\.toml$|\.ini$ ]]; then
|
||||
((config++))
|
||||
elif [[ "${path}" =~ migration|schema|models ]]; then
|
||||
((database++))
|
||||
elif [[ "${path}" =~ \.(py|js|ts|java|go|rs|cpp|c|rb|php|swift|kt)$ ]]; then
|
||||
((source++))
|
||||
else
|
||||
((other++))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${source} ${tests} ${docs} ${config} ${database} ${other}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get categorized file counts from diff
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Tab-separated category counts
|
||||
#######################################
|
||||
get_categorized_counts() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
|
||||
get_file_operations "${commit1}" "${commit2}" | cut -f2 | categorize_files
|
||||
}
|
||||
|
||||
# Allow script to be sourced or run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
# Example usage
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "Usage: $0 <commit1> <commit2>"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 HEAD^ HEAD"
|
||||
echo " $0 main feature-branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
commit1="$1"
|
||||
commit2="$2"
|
||||
|
||||
echo "=== File Operations: ${commit1} → ${commit2} ==="
|
||||
counts=$(count_file_operations "${commit1}" "${commit2}")
|
||||
IFS=$'\t' read -r added modified deleted renamed copied <<< "${counts}"
|
||||
|
||||
echo "Added: ${added}"
|
||||
echo "Modified: ${modified}"
|
||||
echo "Deleted: ${deleted}"
|
||||
echo "Renamed: ${renamed}"
|
||||
echo "Copied: ${copied}"
|
||||
|
||||
binary_count=$(count_binary_files "${commit1}" "${commit2}")
|
||||
echo "Binary: ${binary_count}"
|
||||
|
||||
if [[ ${added} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "=== Added Files ==="
|
||||
get_files_by_operation "${commit1}" "${commit2}" "A" | head -10
|
||||
if [[ ${added} -gt 10 ]]; then
|
||||
echo " ... and $((added - 10)) more"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ${renamed} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo "=== Renamed Files ==="
|
||||
get_renamed_files "${commit1}" "${commit2}" | head -10 | while IFS=$'\t' read -r sim old new; do
|
||||
echo " ${old} → ${new} (${sim}%)"
|
||||
done
|
||||
if [[ ${renamed} -gt 10 ]]; then
|
||||
echo " ... and $((renamed - 10)) more"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== File Categorization ==="
|
||||
categories=$(get_categorized_counts "${commit1}" "${commit2}")
|
||||
IFS=$'\t' read -r source tests docs config database other <<< "${categories}"
|
||||
echo "Source code: ${source}"
|
||||
echo "Tests: ${tests}"
|
||||
echo "Documentation: ${docs}"
|
||||
echo "Configuration: ${config}"
|
||||
echo "Database: ${database}"
|
||||
echo "Other: ${other}"
|
||||
fi
|
||||
250
skills/get-git-diff/scripts/utils.sh
Executable file
250
skills/get-git-diff/scripts/utils.sh
Executable file
@@ -0,0 +1,250 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Git Utility Functions
|
||||
#
|
||||
# General utility functions for git operations and formatting.
|
||||
#
|
||||
# Author: Claude Code get-git-diff skill
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#######################################
|
||||
# Get current branch name
|
||||
# Outputs:
|
||||
# Branch name, or "HEAD" if detached
|
||||
#######################################
|
||||
get_current_branch() {
|
||||
local branch
|
||||
|
||||
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")
|
||||
echo "${branch}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get default branch (main or master)
|
||||
# Outputs:
|
||||
# Default branch name, or empty if not found
|
||||
#######################################
|
||||
get_default_branch() {
|
||||
if git rev-parse --verify main >/dev/null 2>&1; then
|
||||
echo "main"
|
||||
elif git rev-parse --verify master >/dev/null 2>&1; then
|
||||
echo "master"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Format diff filename
|
||||
# Arguments:
|
||||
# $1 - first commit short hash
|
||||
# $2 - second commit short hash
|
||||
# $3 - extension (optional, default: md)
|
||||
# Outputs:
|
||||
# Formatted filename like "diff_abc123_def456.md"
|
||||
#######################################
|
||||
format_diff_filename() {
|
||||
local hash1="$1"
|
||||
local hash2="$2"
|
||||
local ext="${3:-md}"
|
||||
|
||||
echo "diff_${hash1}_${hash2}.${ext}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get repository root directory
|
||||
# Outputs:
|
||||
# Absolute path to repository root
|
||||
#######################################
|
||||
get_repo_root() {
|
||||
git rev-parse --show-toplevel 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get repository name
|
||||
# Outputs:
|
||||
# Name of the repository (directory name)
|
||||
#######################################
|
||||
get_repo_name() {
|
||||
local root
|
||||
|
||||
root=$(get_repo_root)
|
||||
if [[ -n "${root}" ]]; then
|
||||
basename "${root}"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get remote URL (if available)
|
||||
# Arguments:
|
||||
# $1 - remote name (optional, default: origin)
|
||||
# Outputs:
|
||||
# Remote URL or empty if not found
|
||||
#######################################
|
||||
get_remote_url() {
|
||||
local remote="${1:-origin}"
|
||||
|
||||
git remote get-url "${remote}" 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check if commit is reachable from another
|
||||
# Arguments:
|
||||
# $1 - ancestor commit
|
||||
# $2 - descendant commit
|
||||
# Returns:
|
||||
# 0 if reachable, 1 if not
|
||||
#######################################
|
||||
is_ancestor() {
|
||||
local ancestor="$1"
|
||||
local descendant="$2"
|
||||
|
||||
git merge-base --is-ancestor "${ancestor}" "${descendant}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get common ancestor of two commits
|
||||
# Arguments:
|
||||
# $1 - first commit
|
||||
# $2 - second commit
|
||||
# Outputs:
|
||||
# Common ancestor commit hash
|
||||
#######################################
|
||||
get_common_ancestor() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
|
||||
git merge-base "${commit1}" "${commit2}" 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Format timestamp
|
||||
# Arguments:
|
||||
# $1 - format (optional, default: "%Y-%m-%d %H:%M:%S")
|
||||
# Outputs:
|
||||
# Formatted current timestamp
|
||||
#######################################
|
||||
format_timestamp() {
|
||||
local format="${1:-%Y-%m-%d %H:%M:%S}"
|
||||
|
||||
date "+${format}"
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Create directory if it doesn't exist
|
||||
# Arguments:
|
||||
# $1 - directory path
|
||||
# Returns:
|
||||
# 0 if successful or already exists
|
||||
#######################################
|
||||
ensure_directory() {
|
||||
local dir="$1"
|
||||
|
||||
if [[ ! -d "${dir}" ]]; then
|
||||
mkdir -p "${dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get git config value
|
||||
# Arguments:
|
||||
# $1 - config key (e.g., user.name)
|
||||
# Outputs:
|
||||
# Config value or empty if not set
|
||||
#######################################
|
||||
get_git_config() {
|
||||
local key="$1"
|
||||
|
||||
git config --get "${key}" 2>/dev/null || echo ""
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check if working directory is clean
|
||||
# Returns:
|
||||
# 0 if clean, 1 if dirty
|
||||
#######################################
|
||||
is_working_tree_clean() {
|
||||
git diff-index --quiet HEAD -- 2>/dev/null
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Get list of all branches
|
||||
# Arguments:
|
||||
# $1 - type: "local", "remote", or "all" (default: local)
|
||||
# Outputs:
|
||||
# List of branches, one per line
|
||||
#######################################
|
||||
get_branches() {
|
||||
local type="${1:-local}"
|
||||
|
||||
case "${type}" in
|
||||
local)
|
||||
git branch --format='%(refname:short)' 2>/dev/null
|
||||
;;
|
||||
remote)
|
||||
git branch -r --format='%(refname:short)' 2>/dev/null
|
||||
;;
|
||||
all)
|
||||
git branch -a --format='%(refname:short)' 2>/dev/null
|
||||
;;
|
||||
*)
|
||||
echo "Error: Invalid branch type: ${type}" >&2
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Check if ref exists
|
||||
# Arguments:
|
||||
# $1 - ref name (branch, tag, commit)
|
||||
# Returns:
|
||||
# 0 if exists, 1 if not
|
||||
#######################################
|
||||
ref_exists() {
|
||||
local ref="$1"
|
||||
|
||||
git rev-parse --verify "${ref}" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Allow script to be sourced or run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
# Example usage
|
||||
echo "=== Git Repository Utilities ==="
|
||||
echo ""
|
||||
|
||||
echo "Current branch: $(get_current_branch)"
|
||||
echo "Default branch: $(get_default_branch)"
|
||||
echo "Repository root: $(get_repo_root)"
|
||||
echo "Repository name: $(get_repo_name)"
|
||||
|
||||
remote_url=$(get_remote_url)
|
||||
if [[ -n "${remote_url}" ]]; then
|
||||
echo "Remote URL (origin): ${remote_url}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Git user name: $(get_git_config user.name)"
|
||||
echo "Git user email: $(get_git_config user.email)"
|
||||
|
||||
echo ""
|
||||
if is_working_tree_clean; then
|
||||
echo "Working tree: clean"
|
||||
else
|
||||
echo "Working tree: dirty (uncommitted changes)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Example filename: $(format_diff_filename "abc123" "def456")"
|
||||
echo "Current timestamp: $(format_timestamp)"
|
||||
|
||||
echo ""
|
||||
echo "=== Local Branches ==="
|
||||
get_branches local | head -5
|
||||
local_count=$(get_branches local | wc -l)
|
||||
if [[ ${local_count} -gt 5 ]]; then
|
||||
echo " ... and $((local_count - 5)) more"
|
||||
fi
|
||||
fi
|
||||
88
skills/get-git-diff/scripts/validate.sh
Executable file
88
skills/get-git-diff/scripts/validate.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Git Validation Functions
|
||||
#
|
||||
# Functions for validating git repositories and commit references.
|
||||
#
|
||||
# Author: Claude Code get-git-diff skill
|
||||
# Version: 1.0.0
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
#######################################
|
||||
# Check if in a git repository
|
||||
# Returns:
|
||||
# 0 if in git repo, 1 if not
|
||||
#######################################
|
||||
is_git_repo() {
|
||||
git rev-parse --is-inside-work-tree >/dev/null 2>&1
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Validate that a commit reference exists
|
||||
# Arguments:
|
||||
# $1 - commit reference (hash, branch, tag)
|
||||
# Returns:
|
||||
# 0 if valid, 1 if invalid
|
||||
# Outputs:
|
||||
# Full commit hash if valid, error message to stderr if invalid
|
||||
#######################################
|
||||
validate_commit() {
|
||||
local commit_ref="$1"
|
||||
local full_hash
|
||||
|
||||
if full_hash=$(git rev-parse --verify "${commit_ref}" 2>/dev/null); then
|
||||
echo "${full_hash}"
|
||||
return 0
|
||||
else
|
||||
echo "Error: Invalid commit reference: ${commit_ref}" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
#######################################
|
||||
# Validate two commit references
|
||||
# Arguments:
|
||||
# $1 - first commit reference
|
||||
# $2 - second commit reference
|
||||
# Returns:
|
||||
# 0 if both valid, 1 if either invalid
|
||||
# Outputs:
|
||||
# Tab-separated full hashes if valid
|
||||
#######################################
|
||||
validate_commit_pair() {
|
||||
local commit1="$1"
|
||||
local commit2="$2"
|
||||
local hash1 hash2
|
||||
|
||||
if hash1=$(validate_commit "${commit1}") && hash2=$(validate_commit "${commit2}"); then
|
||||
echo "${hash1} ${hash2}"
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Allow script to be sourced or run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
# Example usage
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Usage: $0 <commit-ref> [<commit-ref2>]"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 HEAD"
|
||||
echo " $0 abc123 def456"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if is_git_repo; then
|
||||
if [[ $# -eq 1 ]]; then
|
||||
validate_commit "$1"
|
||||
else
|
||||
validate_commit_pair "$1" "$2"
|
||||
fi
|
||||
else
|
||||
echo "Error: Not in a git repository" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
241
skills/get-git-diff/templates/output_template.md
Normal file
241
skills/get-git-diff/templates/output_template.md
Normal file
@@ -0,0 +1,241 @@
|
||||
# Git Diff: [SHORT_HASH_1] → [SHORT_HASH_2]
|
||||
|
||||
**Generated**: [YYYY-MM-DD HH:MM:SS]
|
||||
**Skill**: get-git-diff v1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Commit Information
|
||||
|
||||
### From Commit
|
||||
- **Hash**: `[FULL_HASH_1]` ([SHORT_HASH_1])
|
||||
- **Author**: [AUTHOR_NAME] <[AUTHOR_EMAIL]>
|
||||
- **Date**: [COMMIT_DATE_1]
|
||||
- **Message**: [COMMIT_MESSAGE_1]
|
||||
|
||||
### To Commit
|
||||
- **Hash**: `[FULL_HASH_2]` ([SHORT_HASH_2])
|
||||
- **Author**: [AUTHOR_NAME] <[AUTHOR_EMAIL]>
|
||||
- **Date**: [COMMIT_DATE_2]
|
||||
- **Message**: [COMMIT_MESSAGE_2]
|
||||
|
||||
### Comparison Details
|
||||
- **Commits Between**: [N] commits
|
||||
- **Branch Context**: [BRANCH_INFO] (if applicable)
|
||||
- **Merge Commit**: [Yes/No] (if applicable)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
### Change Statistics
|
||||
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| Files Changed | [N] |
|
||||
| Insertions | [N] (+) |
|
||||
| Deletions | [N] (-) |
|
||||
| Net Change | [±N] |
|
||||
|
||||
### File Operations
|
||||
|
||||
#### Added Files ([N])
|
||||
[If any files were added, list them here]
|
||||
- `path/to/new_file.py`
|
||||
- `path/to/another_file.js`
|
||||
|
||||
#### Modified Files ([N])
|
||||
[If any files were modified, list them here]
|
||||
- `src/module.py` (+[N], -[N])
|
||||
- `tests/test_module.py` (+[N], -[N])
|
||||
|
||||
#### Deleted Files ([N])
|
||||
[If any files were deleted, list them here]
|
||||
- `old/deprecated_file.py`
|
||||
|
||||
#### Renamed Files ([N])
|
||||
[If any files were renamed, list them here]
|
||||
- `old/path/file.py` → `new/path/file.py` ([SIMILARITY]%)
|
||||
|
||||
#### Binary Files ([N])
|
||||
[If any binary files changed, list them here]
|
||||
- `static/images/logo.png` (binary)
|
||||
- `docs/manual.pdf` (binary)
|
||||
|
||||
---
|
||||
|
||||
## Change Categorization
|
||||
|
||||
### By File Type
|
||||
|
||||
| Category | Files | Insertions | Deletions |
|
||||
|----------|-------|------------|-----------|
|
||||
| Source Code | [N] | [N] | [N] |
|
||||
| Tests | [N] | [N] | [N] |
|
||||
| Documentation | [N] | [N] | [N] |
|
||||
| Configuration | [N] | [N] | [N] |
|
||||
| Database | [N] | [N] | [N] |
|
||||
| Other | [N] | [N] | [N] |
|
||||
|
||||
### Change Type Assessment
|
||||
[Based on patterns identified]
|
||||
- **Primary Type**: [Feature Addition / Bug Fix / Refactoring / etc.]
|
||||
- **Impact Areas**:
|
||||
- [Area 1 - e.g., Authentication system]
|
||||
- [Area 2 - e.g., User API endpoints]
|
||||
- [Area 3 - e.g., Test coverage]
|
||||
|
||||
### Risk Assessment
|
||||
[Based on change patterns and areas affected]
|
||||
- **Risk Level**: [Low / Medium / High / Critical]
|
||||
- **Risk Factors**:
|
||||
- [Factor 1 - e.g., Database schema changes]
|
||||
- [Factor 2 - e.g., Breaking API changes]
|
||||
- [Factor 3 - e.g., Security-sensitive code modified]
|
||||
|
||||
---
|
||||
|
||||
## Special Notes
|
||||
|
||||
[Include any special considerations or warnings]
|
||||
|
||||
### Large Diff Warning
|
||||
[If applicable]
|
||||
⚠️ **This diff contains [N] lines across [M] files.** This is a substantial change set.
|
||||
[If summary only: "Full unified diff omitted due to size. Use `git diff [hash1]...[hash2]` to view complete diff."]
|
||||
|
||||
### Merge Commit
|
||||
[If applicable]
|
||||
⚠️ **This is a merge commit** merging [BRANCH_NAME] into [BASE_BRANCH].
|
||||
- Parent 1: [HASH] ([BRANCH])
|
||||
- Parent 2: [HASH] ([BRANCH])
|
||||
|
||||
### Binary Changes
|
||||
[If applicable]
|
||||
⚠️ **This diff includes [N] binary files.** Binary content is not displayed in the diff below.
|
||||
|
||||
### Renamed Files
|
||||
[If applicable]
|
||||
ℹ️ **[N] files were renamed or moved.** Old and new paths are shown above with similarity percentages.
|
||||
|
||||
---
|
||||
|
||||
## Detailed Diff
|
||||
|
||||
[If full diff is included, otherwise note that it was omitted]
|
||||
|
||||
```diff
|
||||
[FULL_UNIFIED_DIFF_OUTPUT]
|
||||
|
||||
Example format:
|
||||
diff --git a/src/module.py b/src/module.py
|
||||
index abc123..def456 100644
|
||||
--- a/src/module.py
|
||||
+++ b/src/module.py
|
||||
@@ -10,7 +10,8 @@ def my_function():
|
||||
# Unchanged line
|
||||
- old_line = "removed"
|
||||
+ new_line = "added"
|
||||
+ another_line = "also added"
|
||||
# More context
|
||||
|
||||
diff --git a/tests/test_module.py b/tests/test_module.py
|
||||
index 111222..333444 100644
|
||||
--- a/tests/test_module.py
|
||||
+++ b/tests/test_module.py
|
||||
@@ -5,3 +5,6 @@ def test_my_function():
|
||||
assert my_function() is not None
|
||||
+
|
||||
+def test_new_behavior():
|
||||
+ assert new_line == "added"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
[Optional section with actionable recommendations based on diff analysis]
|
||||
|
||||
### Testing
|
||||
- [ ] Verify all tests pass with these changes
|
||||
- [ ] Add tests for any new functionality
|
||||
- [ ] Check test coverage for modified areas
|
||||
|
||||
### Code Review Focus
|
||||
- [ ] Review [specific area] for correctness
|
||||
- [ ] Validate [specific concern] is addressed
|
||||
- [ ] Ensure backward compatibility for [specific component]
|
||||
|
||||
### Deployment Considerations
|
||||
- [ ] Update environment variables if config changed
|
||||
- [ ] Run database migrations if schema changed
|
||||
- [ ] Update documentation if API changed
|
||||
- [ ] Coordinate with [team] if [specific area] changed
|
||||
|
||||
---
|
||||
|
||||
## Related Commands
|
||||
|
||||
```bash
|
||||
# View this diff locally
|
||||
git diff [SHORT_HASH_1]...[SHORT_HASH_2]
|
||||
|
||||
# View with file names only
|
||||
git diff --name-status [SHORT_HASH_1]...[SHORT_HASH_2]
|
||||
|
||||
# View with statistics
|
||||
git diff --stat [SHORT_HASH_1]...[SHORT_HASH_2]
|
||||
|
||||
# View specific file from this diff
|
||||
git diff [SHORT_HASH_1]...[SHORT_HASH_2] -- path/to/file
|
||||
|
||||
# View commit messages in range
|
||||
git log --oneline [SHORT_HASH_1]..[SHORT_HASH_2]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
- **Analyzed by**: Claude Code get-git-diff skill v1.0.0
|
||||
- **Analysis date**: [YYYY-MM-DD HH:MM:SS]
|
||||
- **Repository**: [REPO_PATH or REPO_URL if available]
|
||||
- **Output format**: Standard Unified Diff with Summary
|
||||
|
||||
---
|
||||
|
||||
<!--
|
||||
Template Usage Instructions:
|
||||
|
||||
Replace all placeholders in [BRACKETS] with actual values:
|
||||
- [SHORT_HASH_1/2] - 7-character commit hash
|
||||
- [FULL_HASH_1/2] - Full 40-character commit hash
|
||||
- [AUTHOR_NAME] - Commit author name
|
||||
- [AUTHOR_EMAIL] - Commit author email
|
||||
- [COMMIT_DATE_1/2] - Commit date
|
||||
- [COMMIT_MESSAGE_1/2] - Commit message (first line)
|
||||
- [N] - Numeric values (file counts, line counts, etc.)
|
||||
- [BRANCH_INFO] - Branch context information
|
||||
- [SIMILARITY] - Similarity percentage for renames
|
||||
- [FULL_UNIFIED_DIFF_OUTPUT] - Complete git diff output
|
||||
|
||||
Conditional Sections:
|
||||
- Include "Large Diff Warning" only if diff > 1000 lines
|
||||
- Include "Merge Commit" only if commit is a merge
|
||||
- Include "Binary Changes" only if binary files present
|
||||
- Include "Renamed Files" only if renames detected
|
||||
- Omit empty categories (e.g., if no files deleted, omit that section)
|
||||
|
||||
Formatting:
|
||||
- Use proper markdown syntax
|
||||
- Wrap code/paths in backticks: `path/to/file`
|
||||
- Use tables for structured data
|
||||
- Use bullet lists for items
|
||||
- Use checkboxes [ ] for action items
|
||||
- Use emoji/symbols sparingly: ⚠️ for warnings, ℹ️ for info
|
||||
|
||||
Output Location:
|
||||
- Save to: /claudedocs/diff_{short_hash1}_{short_hash2}.md
|
||||
- Ensure /claudedocs directory exists
|
||||
- Use filesystem-safe characters only
|
||||
-->
|
||||
Reference in New Issue
Block a user