fix: enhance conflict resolution by improving hunk parsing and context extraction in OpenAI API integration

This commit is contained in:
robertsLando 2025-08-28 14:54:11 +02:00
parent 3c378db428
commit 5b53026395
No known key found for this signature in database
2 changed files with 138 additions and 68 deletions

View File

@ -2,8 +2,92 @@
import sys
import json
import os
import re
from urllib.request import Request, urlopen
from urllib.parse import urlencode
def parse_reject_file(reject_content):
"""Parse reject file to extract hunk information"""
hunks = []
lines = reject_content.split("\n")
current_hunk = None
for line in lines:
# Match hunk header: @@ -start,count +start,count @@
hunk_match = re.match(r"@@\s*-(\d+)(?:,(\d+))?\s*\+(\d+)(?:,(\d+))?\s*@@", line)
if hunk_match:
old_start = int(hunk_match.group(1))
old_count = int(hunk_match.group(2)) if hunk_match.group(2) else 1
new_start = int(hunk_match.group(3))
new_count = int(hunk_match.group(4)) if hunk_match.group(4) else 1
current_hunk = {
"old_start": old_start,
"old_count": old_count,
"new_start": new_start,
"new_count": new_count,
"lines": [],
}
hunks.append(current_hunk)
elif current_hunk is not None and (
line.startswith(" ") or line.startswith("-") or line.startswith("+")
):
current_hunk["lines"].append(line)
return hunks
def extract_file_context(file_content, hunks, context_lines=5):
"""Extract relevant sections from file based on hunks"""
if not file_content:
return ""
file_lines = file_content.split("\n")
extracted_sections = []
for hunk in hunks:
# Calculate the range of lines to extract with extra context
start_line = max(
0, hunk["old_start"] - context_lines - 1
) # -1 for 0-based indexing
end_line = min(
len(file_lines), hunk["old_start"] + hunk["old_count"] + context_lines - 1
)
# Extract the section
section_lines = file_lines[start_line:end_line]
section = {
"start_line_num": start_line + 1, # Convert back to 1-based for display
"end_line_num": end_line,
"content": "\n".join(section_lines),
"hunk": hunk,
}
extracted_sections.append(section)
return extracted_sections
def apply_fixed_sections(original_content, fixed_sections):
"""Apply fixed sections back to the original file"""
if not original_content:
return fixed_sections[0]["content"] if fixed_sections else ""
file_lines = original_content.split("\n")
# Sort sections by start line (descending) to apply from bottom to top
sorted_sections = sorted(
fixed_sections, key=lambda x: x["start_line_num"], reverse=True
)
for section in sorted_sections:
start_idx = section["start_line_num"] - 1 # Convert to 0-based indexing
end_idx = section["end_line_num"]
# Replace the section
fixed_lines = section["content"].split("\n")
file_lines[start_idx:end_idx] = fixed_lines
return "\n".join(file_lines)
def call_openai_api(prompt, api_key, model="gpt-3.5-turbo"):
@ -17,7 +101,7 @@ def call_openai_api(prompt, api_key, model="gpt-3.5-turbo"):
"messages": [
{
"role": "system",
"content": "You are an expert C++ developer helping to resolve Git patch conflicts. Return only the corrected file content without explanations or markdown formatting.",
"content": "You are an expert C++ developer helping to resolve Git patch conflicts. Return only the corrected code section without explanations or markdown formatting. Preserve the exact number of lines and structure.",
},
{"role": "user", "content": prompt},
],
@ -39,7 +123,7 @@ def call_openai_api(prompt, api_key, model="gpt-3.5-turbo"):
def resolve_conflict(reject_file, original_file, api_key):
"""Resolve a single conflict using OpenAI"""
"""Resolve a single conflict using OpenAI with context extraction"""
# Read reject file content
try:
@ -58,49 +142,69 @@ def resolve_conflict(reject_file, original_file, api_key):
except Exception as e:
print(f"Error reading original file {original_file}: {e}", file=sys.stderr)
# Create prompt for OpenAI
prompt = f"""I have a Git patch that failed to apply to a Node.js C++ source file.
# Parse reject file to extract hunks
hunks = parse_reject_file(reject_content)
if not hunks:
print(f"No valid hunks found in {reject_file}", file=sys.stderr)
return False
REJECTED PATCH HUNKS:
# Extract relevant file sections
file_sections = extract_file_context(current_content, hunks)
fixed_sections = []
# Process each section
for i, section in enumerate(file_sections):
hunk = section["hunk"]
# Create prompt for this specific section
prompt = f"""I have a Git patch that failed to apply. Here's the specific section that needs to be fixed:
REJECTED PATCH HUNK:
```
{reject_content}
@@ -{hunk['old_start']},{hunk['old_count']} +{hunk['new_start']},{hunk['new_count']} @@
{chr(10).join(hunk['lines'])}
```
CURRENT FILE CONTENT:
CURRENT FILE SECTION (lines {section['start_line_num']}-{section['end_line_num']}):
```
{current_content}
{section['content']}
```
Please analyze the rejected patch hunks and the current file content, then provide the complete corrected file content that successfully applies the intended changes from the patch. The changes should:
Please apply the intended changes from the rejected hunk to this file section. Return ONLY the corrected file section content, preserving the exact line structure and formatting. Do not add explanations or markdown formatting."""
1. Apply the intended modifications from the rejected hunks
2. Handle any line number shifts or context changes
3. Maintain proper C++ syntax and formatting
4. Preserve existing functionality while adding the patch changes
print(
f"Processing section {i+1}/{len(file_sections)} for {original_file}...",
file=sys.stderr,
)
Return ONLY the complete corrected file content, no explanations."""
print(f"Prompt for OpenAI API:\n{prompt}")
print(f"Calling OpenAI API to resolve {original_file}...", file=sys.stderr)
resolved_content = call_openai_api(prompt, api_key)
print(f"Prompt for OpenAI API:\n{prompt}")
resolved_content = call_openai_api(prompt, api_key)
if resolved_content:
try:
# Write resolved content
with open(original_file, "w", encoding="utf-8") as f:
f.write(resolved_content)
print(f"✅ Successfully resolved {original_file}")
return True
except Exception as e:
print(
f"Error writing resolved content to {original_file}: {e}",
file=sys.stderr,
if resolved_content:
fixed_sections.append(
{
"start_line_num": section["start_line_num"],
"end_line_num": section["end_line_num"],
"content": resolved_content,
}
)
else:
print(f"❌ Failed to get resolution from OpenAI for section {i+1}")
return False
else:
print(f"❌ Failed to get resolution from OpenAI for {original_file}")
# Apply all fixed sections back to the original file
try:
final_content = apply_fixed_sections(current_content, fixed_sections)
with open(original_file, "w", encoding="utf-8") as f:
f.write(final_content)
print(f"✅ Successfully resolved {original_file}")
return True
except Exception as e:
print(
f"Error writing resolved content to {original_file}: {e}", file=sys.stderr
)
return False

View File

@ -102,7 +102,7 @@ jobs:
cat resolution_output.txt
# Check if we have unresolved conflicts - if so, exit without creating PR
if [ "${HAS_UNRESOLVED:-false}" = "true" ]; then
if [ "${HAS_UNRESOLVED:-false}" = "True" ]; then
echo "❌ There are unresolved conflicts. Exiting without creating PR."
echo "Please resolve the conflicts manually and re-run the workflow."
exit 1
@ -130,40 +130,6 @@ jobs:
echo "HAS_UNRESOLVED=${HAS_UNRESOLVED:-false}" >> $GITHUB_ENV
echo "CREATE_PR=true" >> $GITHUB_ENV
- name: Summary
run: |
echo "=================================================="
echo "📋 WORKFLOW EXECUTION SUMMARY"
echo "=================================================="
echo "Node.js version: ${{ inputs.nodeVersion }}"
echo "Patch status: ${PATCH_STATUS:-unknown}"
case "${PATCH_STATUS:-unknown}" in
"clean")
echo "✅ Patch applied cleanly without conflicts"
;;
"partial")
echo "⚠️ Patch applied partially"
;;
"conflicts")
echo "🤖 AI-assisted conflict resolution used"
echo "Total conflicts: ${TOTAL_CONFLICTS:-0}"
echo "Resolved conflicts: ${CONFLICTS_RESOLVED:-0}"
if [ "${HAS_UNRESOLVED:-false}" = "true" ]; then
echo "❌ Some conflicts remain unresolved"
else
echo "✅ All conflicts successfully resolved"
fi
;;
esac
if [ "${CREATE_PR:-false}" = "true" ]; then
echo "🚀 Pull request will be created"
else
echo "⏹️ Pull request creation skipped due to unresolved issues"
fi
echo "=================================================="
- name: Create Pull Request
if: env.CREATE_PR == 'true'
uses: peter-evans/create-pull-request@v4