mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-23 11:11:11 +01:00
feat: enhance merge conflict handling with improved status messages and conditional rendering feat: improved modify / delete conflict handling - fix ours/theirs messages for deleted files - fix handling of choice moniker - fix parsing of local/incoming name when deleted
142 lines
5.3 KiB
Python
142 lines
5.3 KiB
Python
import os
|
|
import yaml
|
|
import logging
|
|
from git import GitCommandError
|
|
from .conflict_comparison import create_conflict_summary, UNRESOLVED, RESOLVED, MODIFY_DELETE
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_version_data(repo, ref, file_path):
|
|
"""Get YAML data from a specific version of a file"""
|
|
try:
|
|
content = repo.git.show(f'{ref}:{file_path}')
|
|
return yaml.safe_load(content) if content else None
|
|
except GitCommandError:
|
|
return None
|
|
|
|
|
|
def process_modify_delete_conflict(repo, file_path, deleted_in_head):
|
|
"""Handle case where one side modified while other deleted"""
|
|
try:
|
|
# Check if conflict is resolved
|
|
status_output = repo.git.status('--porcelain', file_path)
|
|
file_exists = os.path.exists(os.path.join(repo.working_dir, file_path))
|
|
is_staged = status_output and status_output[0] in ['M', 'A']
|
|
|
|
# Determine status
|
|
if (file_exists and is_staged) or (not file_exists
|
|
and status_output.startswith('D ')):
|
|
status = RESOLVED
|
|
else:
|
|
status = MODIFY_DELETE
|
|
|
|
# For delete conflicts, we need to extract the name for display purposes
|
|
# This will be the name of the actual file before it was deleted
|
|
basename = os.path.basename(file_path)
|
|
filename = os.path.splitext(basename)[0] # Strip extension
|
|
|
|
# Get metadata from existing version to extract name if possible
|
|
if file_exists:
|
|
# File exists locally, read it
|
|
try:
|
|
with open(os.path.join(repo.working_dir, file_path), 'r') as f:
|
|
existing_data = yaml.safe_load(f.read())
|
|
except Exception as read_error:
|
|
logger.warning(f"Could not read existing file {file_path}: {str(read_error)}")
|
|
existing_data = {'name': filename}
|
|
else:
|
|
# File was deleted locally, try to get from merge head
|
|
try:
|
|
existing_data = get_version_data(repo, 'MERGE_HEAD', file_path)
|
|
except Exception as merge_error:
|
|
logger.warning(f"Could not get merge head for {file_path}: {str(merge_error)}")
|
|
existing_data = {'name': filename}
|
|
|
|
# Simplified placeholder data for deleted version
|
|
if deleted_in_head:
|
|
# File was deleted in HEAD (local) but exists in MERGE_HEAD (incoming)
|
|
local_data = None # This indicates deleted
|
|
try:
|
|
# Try to get name from incoming
|
|
incoming_data = existing_data if existing_data else {'name': filename}
|
|
except Exception:
|
|
incoming_data = {'name': filename}
|
|
else:
|
|
# File exists in HEAD (local) but deleted in MERGE_HEAD (incoming)
|
|
try:
|
|
local_data = existing_data if existing_data else {'name': filename}
|
|
except Exception:
|
|
local_data = {'name': filename}
|
|
incoming_data = None # This indicates deleted
|
|
|
|
return create_conflict_summary(file_path, local_data, incoming_data, status)
|
|
|
|
except Exception as e:
|
|
logger.error(
|
|
f"Failed to process modify/delete conflict for {file_path}: {str(e)}"
|
|
)
|
|
return None
|
|
|
|
|
|
def process_regular_conflict(repo, file_path):
|
|
"""Handle standard merge conflict between two versions"""
|
|
try:
|
|
# Get both versions
|
|
ours_data = get_version_data(repo, 'HEAD', file_path)
|
|
theirs_data = get_version_data(repo, 'MERGE_HEAD', file_path)
|
|
|
|
if not ours_data and not theirs_data:
|
|
return None
|
|
|
|
# Check if conflict is resolved
|
|
status_output = repo.git.status('--porcelain', file_path)
|
|
status = UNRESOLVED if status_output.startswith('UU') else RESOLVED
|
|
|
|
return create_conflict_summary(file_path, ours_data, theirs_data,
|
|
status)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to process conflict for {file_path}: {str(e)}")
|
|
return None
|
|
|
|
|
|
def get_merge_conflicts(repo):
|
|
"""Get all merge conflicts in the repository"""
|
|
try:
|
|
# Check if we're in a merge state
|
|
if not os.path.exists(os.path.join(repo.git_dir, 'MERGE_HEAD')):
|
|
return []
|
|
|
|
conflicts = []
|
|
status = repo.git.status('--porcelain', '-z').split('\0')
|
|
|
|
# Process each status entry
|
|
for item in status:
|
|
if not item or len(item) < 4:
|
|
continue
|
|
|
|
x, y = item[0], item[1]
|
|
file_path = item[3:]
|
|
|
|
# Handle modify/delete conflicts
|
|
if (x == 'D' and y == 'U') or (x == 'U'
|
|
and y == 'D') or (x == 'A'
|
|
and y == 'U'):
|
|
conflict = process_modify_delete_conflict(
|
|
repo, file_path, x == 'D')
|
|
if conflict:
|
|
conflicts.append(conflict)
|
|
|
|
# Handle regular conflicts
|
|
elif 'U' in (x, y) or (x == 'D' and y == 'D'):
|
|
conflict = process_regular_conflict(repo, file_path)
|
|
if conflict:
|
|
conflicts.append(conflict)
|
|
|
|
return conflicts
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to get merge conflicts: {str(e)}")
|
|
return []
|