Initial commit
This commit is contained in:
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
|
||||
Reference in New Issue
Block a user