#!/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: 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)