Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:45:11 +08:00
commit 42c0b6ee81
16 changed files with 3608 additions and 0 deletions

View 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
```

View 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

View 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

View 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

View 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

View 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