Files
gh-dhofheinz-open-plugins-p…/commands/commit-error-handling/.scripts/conflict-detector.py
2025-11-29 18:20:25 +08:00

174 lines
4.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
================================================================
Script: conflict-detector.py
Purpose: Detect and report merge conflicts
Version: 1.0.0
Usage: ./conflict-detector.py
Returns: JSON with conflict information
Exit Codes:
0 = Success (conflicts may or may not exist)
1 = Not a git repository
2 = Script error
================================================================
"""
import json
import subprocess
import sys
from datetime import datetime
from pathlib import Path
def run_git_command(command):
"""Run a git command and return output."""
try:
result = subprocess.run(
command,
shell=True,
capture_output=True,
text=True,
check=False
)
return result.returncode, result.stdout.strip(), result.stderr.strip()
except Exception as e:
return -1, "", str(e)
def check_repo_validity():
"""Check if current directory is a git repository."""
returncode, _, _ = run_git_command("git rev-parse --git-dir")
return returncode == 0
def get_conflicted_files():
"""Get list of files with merge conflicts."""
# Files with conflicts show up with 'U' status (unmerged)
returncode, stdout, _ = run_git_command("git ls-files -u")
if returncode != 0 or not stdout:
return []
# Extract unique filenames (git ls-files -u shows each stage)
conflicted_files = set()
for line in stdout.split('\n'):
if line.strip():
# Format: <mode> <object> <stage> <filename>
parts = line.split('\t')
if len(parts) > 1:
filename = parts[1]
conflicted_files.add(filename)
return sorted(conflicted_files)
def check_merge_in_progress():
"""Check if a merge operation is in progress."""
git_dir_code, git_dir, _ = run_git_command("git rev-parse --git-dir")
if git_dir_code != 0:
return False, None
git_dir_path = Path(git_dir)
# Check for various merge/rebase states
if (git_dir_path / "MERGE_HEAD").exists():
return True, "merge"
elif (git_dir_path / "REBASE_HEAD").exists():
return True, "rebase"
elif (git_dir_path / "CHERRY_PICK_HEAD").exists():
return True, "cherry-pick"
elif (git_dir_path / "REVERT_HEAD").exists():
return True, "revert"
return False, None
def get_conflict_details(files):
"""Get detailed information about conflicts in each file."""
details = []
for filepath in files:
try:
# Count conflict markers in file
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
conflict_count = content.count('<<<<<<<')
details.append({
"file": filepath,
"conflict_regions": conflict_count
})
except Exception:
# If can't read file, just include filename
details.append({
"file": filepath,
"conflict_regions": 0
})
return details
def main():
"""Main execution function."""
# Check if in git repository
if not check_repo_validity():
result = {
"has_conflicts": False,
"conflict_count": 0,
"conflicted_files": [],
"merge_in_progress": False,
"operation_type": None,
"error": "not a git repository",
"checked_at": datetime.now().isoformat()
}
print(json.dumps(result, indent=2))
sys.exit(1)
# Get conflicted files
conflicted_files = get_conflicted_files()
conflict_count = len(conflicted_files)
has_conflicts = conflict_count > 0
# Check merge status
merge_in_progress, operation_type = check_merge_in_progress()
# Get detailed conflict information
conflict_details = []
if has_conflicts:
conflict_details = get_conflict_details(conflicted_files)
# Build result
result = {
"has_conflicts": has_conflicts,
"conflict_count": conflict_count,
"conflicted_files": conflicted_files,
"conflict_details": conflict_details,
"merge_in_progress": merge_in_progress,
"operation_type": operation_type,
"error": "",
"checked_at": datetime.now().isoformat()
}
# Output JSON
print(json.dumps(result, indent=2))
sys.exit(0)
if __name__ == "__main__":
try:
main()
except Exception as e:
# Handle unexpected errors
result = {
"has_conflicts": False,
"conflict_count": 0,
"conflicted_files": [],
"merge_in_progress": False,
"operation_type": None,
"error": f"script error: {str(e)}",
"checked_at": datetime.now().isoformat()
}
print(json.dumps(result, indent=2))
sys.exit(2)