fix: enhance conflict resolution by improving hunk parsing and context extraction in OpenAI API integration
This commit is contained in:
parent
3c378db428
commit
5b53026395
170
.github/scripts/openai_resolver.py
vendored
170
.github/scripts/openai_resolver.py
vendored
@ -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
|
||||
|
||||
|
||||
|
||||
36
.github/workflows/patch-node.yml
vendored
36
.github/workflows/patch-node.yml
vendored
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user