Files
gh-doarakko-dotfiles-claude/commands/commit-split.md
2025-11-29 18:23:35 +08:00

13 KiB
Raw Permalink Blame History

コミット分割コマンド

Gitの差分をもとに適切にコミットを分割します。ファイル単位や機能単位で論理的にコミットを分けることで、レビューしやすく意味のあるコミット履歴を作成します。

使用方法

/commit-split [ベースブランチ] [分割方式]

パラメータ

  • ベースブランチ: 比較対象のブランチ(省略時はmasterまたはmainを自動検出)
  • 分割方式: file(ファイル単位)、feature(機能単位)、interactive(インタラクティブ)

処理手順

  1. 現在の変更状況を確認
  2. 差分の分析と分割方針の決定
  3. インタラクティブな分割実行
  4. 各コミットの作成と確認

実装

ステップ1: 初期状態の確認

# 作業ディレクトリの状態を確認
echo "📊 現在の作業状況を確認中..."
git status --porcelain

# ステージされていない変更があるかチェック
UNSTAGED_CHANGES=$(git diff --name-only)
STAGED_CHANGES=$(git diff --cached --name-only)

if [ -n "$UNSTAGED_CHANGES" ] || [ -n "$STAGED_CHANGES" ]; then
    echo "⚠️  未コミットの変更があります"
    echo "📝 ステージされていない変更: $(echo "$UNSTAGED_CHANGES" | wc -l) ファイル"
    echo "📝 ステージされた変更: $(echo "$STAGED_CHANGES" | wc -l) ファイル"
else
    echo "✅ 作業ディレクトリはクリーンです"
    exit 0
fi

ステップ2: ベースブランチの決定

# ベースブランチの自動検出または指定
BASE_BRANCH="${1:-}"
if [ -z "$BASE_BRANCH" ]; then
    if git show-ref --verify --quiet refs/heads/master; then
        BASE_BRANCH="master"
    elif git show-ref --verify --quiet refs/heads/main; then
        BASE_BRANCH="main"
    else
        echo "❌ ベースブランチを特定できません。明示的に指定してください。"
        exit 1
    fi
fi

echo "🎯 ベースブランチ: $BASE_BRANCH"

# ベースブランチとの差分を確認
CURRENT_BRANCH=$(git branch --show-current)
echo "🌿 現在のブランチ: $CURRENT_BRANCH"

ステップ3: 差分の分析

# 変更されたファイルの一覧と統計
echo "📈 差分の分析中..."
CHANGED_FILES=$(git diff --name-only $BASE_BRANCH..HEAD 2>/dev/null || git diff --name-only)

if [ -z "$CHANGED_FILES" ]; then
    echo "📝 ベースブランチとの差分がありません"
    # 作業ディレクトリの変更のみ処理
    CHANGED_FILES=$(git diff --name-only)
    if [ -z "$CHANGED_FILES" ]; then
        CHANGED_FILES=$(git diff --cached --name-only)
    fi
fi

echo "📁 変更されたファイル一覧:"
echo "$CHANGED_FILES" | nl

# ファイルタイプ別の分類
DOCS_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(md|txt|rst)$' || true)
CONFIG_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(json|yaml|yml|toml|ini|conf)$' || true)
CODE_FILES=$(echo "$CHANGED_FILES" | grep -E '\.(js|ts|py|go|rs|java|c|cpp|php|rb)$' || true)
TEST_FILES=$(echo "$CHANGED_FILES" | grep -E '(test|spec)\.' || true)

echo ""
echo "📊 ファイルタイプ別統計:"
[ -n "$DOCS_FILES" ] && echo "📖 ドキュメント: $(echo "$DOCS_FILES" | wc -l) ファイル"
[ -n "$CONFIG_FILES" ] && echo "⚙️  設定ファイル: $(echo "$CONFIG_FILES" | wc -l) ファイル"
[ -n "$CODE_FILES" ] && echo "💻 コードファイル: $(echo "$CODE_FILES" | wc -l) ファイル"
[ -n "$TEST_FILES" ] && echo "🧪 テストファイル: $(echo "$TEST_FILES" | wc -l) ファイル"

ステップ4: 分割方式の決定

SPLIT_MODE="${2:-file}"

case "$SPLIT_MODE" in
    "file")
        echo "📂 ファイル単位での分割を実行します"
        commit_by_file
        ;;
    "feature")
        echo "🎯 機能単位での分割を実行します"
        commit_by_feature
        ;;
    "interactive"|*)
        echo "🤝 インタラクティブ分割を実行します"
        commit_interactive
        ;;
esac

ステップ5: ファイル単位分割の実装

commit_by_file() {
    echo "$CHANGED_FILES" | while read -r file; do
        if [ -n "$file" ]; then
            echo ""
            echo "📝 処理中: $file"
            
            # ファイルの変更内容を表示
            git diff --stat "$file" 2>/dev/null || git diff --cached --stat "$file" 2>/dev/null || true
            
            # コミットメッセージの自動生成
            FILE_EXT="${file##*.}"
            DIR_NAME=$(dirname "$file")
            FILE_NAME=$(basename "$file")
            
            # ファイルタイプに基づいたプレフィックス
            if echo "$file" | grep -q -E '\.(md|txt|rst)$'; then
                PREFIX="docs"
            elif echo "$file" | grep -q -E '\.(json|yaml|yml|toml|ini|conf)$'; then
                PREFIX="config"
            elif echo "$file" | grep -q -E '(test|spec)\.'; then
                PREFIX="test"
            else
                PREFIX="feat"
            fi
            
            COMMIT_MSG="$PREFIX: update $FILE_NAME"
            
            # 自動でコミット(確認なし)
            echo "💬 提案されたコミットメッセージ: $COMMIT_MSG"
            git add "$file"
            git commit -m "$COMMIT_MSG"
            echo "✅ コミット完了: $file"
        fi
    done
}

ステップ6: 機能単位分割の実装

commit_by_feature() {
    # ディレクトリ構造に基づいた機能別グループ化
    echo "🔍 機能別にファイルをグループ化中..."
    
    # ディレクトリごとにファイルをグループ化
    DIRECTORIES=$(echo "$CHANGED_FILES" | xargs dirname | sort | uniq)
    
    echo "$DIRECTORIES" | while read -r dir; do
        if [ -n "$dir" ]; then
            DIR_FILES=$(echo "$CHANGED_FILES" | grep "^$dir/")
            
            if [ -n "$DIR_FILES" ]; then
                echo ""
                echo "📁 ディレクトリ: $dir"
                echo "$DIR_FILES" | sed 's/^/  - /'
                
                # ディレクトリ名に基づいたコミットメッセージ
                case "$dir" in
                    "src"|"lib"|"app")
                        PREFIX="feat"
                        ;;
                    "test"|"tests"|"__tests__")
                        PREFIX="test"
                        ;;
                    "docs"|"doc")
                        PREFIX="docs"
                        ;;
                    "config"|".github")
                        PREFIX="config"
                        ;;
                    *)
                        PREFIX="update"
                        ;;
                esac
                
                COMMIT_MSG="$PREFIX: update $dir module"
                
                echo "💬 提案されたコミットメッセージ: $COMMIT_MSG"
                echo "$DIR_FILES" | xargs git add
                git commit -m "$COMMIT_MSG"
                echo "✅ コミット完了: $dir"
            fi
        fi
    done
}

ステップ7: インタラクティブ分割の実装

commit_interactive() {
    echo "🎮 自動モードでコミットを分割します"
    
    # 現在の状態を確認
    REMAINING_UNSTAGED=$(git diff --name-only)
    REMAINING_STAGED=$(git diff --cached --name-only)
    
    if [ -z "$REMAINING_UNSTAGED" ] && [ -z "$REMAINING_STAGED" ]; then
        echo "📝 変更がありません"
        return
    fi
    
    # 全ファイルをステージして一度にコミット
    git add .
    commit_staged_changes
    
    echo "🎉 すべての変更が処理されました!"
}

select_files_for_staging() {
    ALL_CHANGED=$(git status --porcelain | awk '{print $2}')
    if [ -z "$ALL_CHANGED" ]; then
        echo "📝 変更されたファイルがありません"
        return
    fi
    
    echo "📁 変更されたファイル一覧:"
    echo "$ALL_CHANGED" | nl
    echo ""
    
    # 全ファイルを自動でステージ
    echo "$ALL_CHANGED" | while read -r file; do
        if [ -n "$file" ]; then
            git add "$file"
            echo "✅ ステージ完了: $file"
        fi
    done
}

commit_staged_changes() {
    STAGED=$(git diff --cached --name-only)
    if [ -z "$STAGED" ]; then
        echo "📦 ステージされた変更がありません"
        return
    fi
    
    echo "📦 ステージされたファイル:"
    echo "$STAGED" | sed 's/^/  - /'
    echo ""
    
    # 自動でコミット
    commit_msg="update: staged changes"
    git commit -m "$commit_msg"
    echo "✅ コミット完了!"
}

show_diff_summary() {
    echo "📊 変更サマリー:"
    git diff --stat
    echo ""
    git diff --cached --stat
    echo ""
    # 詳細な差分を自動的に表示
    echo "📖 詳細な差分:"
    git diff
    git diff --cached
}

auto_push_changes() {
    # 現在のブランチ名を取得
    CURRENT_BRANCH=$(git branch --show-current)
    
    echo "📤 リモートブランチにプッシュ中..."
    
    # リモートブランチが存在するかチェック
    if git ls-remote --heads origin "$CURRENT_BRANCH" | grep -q "$CURRENT_BRANCH"; then
        # リモートブランチが存在する場合は通常のpush
        git push
        if [ $? -eq 0 ]; then
            echo "✅ プッシュ完了: $CURRENT_BRANCH"
        else
            echo "⚠️  プッシュでエラーが発生しました"
        fi
    else
        # リモートブランチが存在しない場合は -u オプション付きでpush
        git push -u origin "$CURRENT_BRANCH"
        if [ $? -eq 0 ]; then
            echo "✅ 新しいブランチを作成してプッシュ完了: $CURRENT_BRANCH"
        else
            echo "⚠️  プッシュでエラーが発生しました"
        fi
    fi
}

show_help() {
    echo "📚 コミット分割ヘルプ"
    echo ""
    echo "🎯 目的: 大きな変更を論理的に分割して複数のコミットに分ける"
    echo ""
    echo "💡 推奨される分割方針:"
    echo "  - 📁 ファイルタイプ別(設定、ドキュメント、コード、テスト)"
    echo "  - 🎯 機能別(新機能、バグ修正、リファクタリング)"
    echo "  - 📦 影響範囲別フロントエンド、バックエンド、DB"
    echo ""
    echo "🔧 便利なGitコマンド:"
    echo "  - git add -p : パッチ単位での選択的ステージング"
    echo "  - git diff --cached : ステージされた変更の確認"
    echo "  - git reset <file> : ファイルのアンステージ"
    echo "  - git status : 現在の状態確認"
    echo ""
    echo "📤 プッシュ機能:"
    echo "  - 各コミット後に個別でプッシュするか選択可能"
    echo "  - 最終処理で全てのコミットを一括プッシュ"
}

ステップ8: 完了処理とプッシュ

# 最終確認と統計
echo ""
echo "🎉 コミット分割が完了しました!"
echo ""
echo "📈 作成されたコミット:"
git log --oneline -10

echo ""
echo "📤 自動プッシュを実行します..."

# 現在のブランチ名を取得
CURRENT_BRANCH=$(git branch --show-current)

# リモートブランチが存在するかチェック
if git ls-remote --heads origin "$CURRENT_BRANCH" | grep -q "$CURRENT_BRANCH"; then
    # リモートブランチが存在する場合は通常のpush
    git push
    echo "✅ プッシュ完了: $CURRENT_BRANCH"
else
    # リモートブランチが存在しない場合は -u オプション付きでpush
    git push -u origin "$CURRENT_BRANCH"
    echo "✅ 新しいブランチを作成してプッシュ完了: $CURRENT_BRANCH"
fi

echo ""
echo "🚀 すべてのコミットがリモートに反映されました!"

重要なルール

  • 段階的なコミット: 関連する変更をまとめて、レビューしやすい単位でコミット
  • 意味のあるメッセージ: 各コミットが何を変更したかを明確に記述
  • ファイルタイプの考慮: 設定ファイル、ドキュメント、コード、テストを適切に分離
  • レビュー性の向上: 1つのコミットで1つの論理的変更を実現
  • 自動プッシュ: 各コミット後にプッシュ選択可能、最終的に全コミットをリモートに反映

使用例

ファイル単位での分割

/commit-split master file

機能単位での分割

/commit-split main feature

インタラクティブ分割

/commit-split

注意事項

  • 実行前に重要な変更はバックアップを取ってください
  • git add -p を使用する際は、パッチの内容をよく確認してください
  • 各コミット後にプッシュするかどうかを選択できます
  • 最終的にすべてのコミットがリモートに反映されます
  • リモートにプッシュ済みのコミットは分割しないでください