import os
import re
import subprocess
import tempfile
from types import SimpleNamespace
from typing import List, Tuple
import Levenshtein
from git import Repo
from langchain_core.tools import tool
import tools.utils as utils
from tools.logger import logger
class Project:
def __init__(self, data: SimpleNamespace):
self.project_url = data.project_url
self.dir = data.project_dir
self.repo = Repo(data.project_dir)
if not data.error_message:
self.err_msg = "no err_msg"
else:
self.err_msg = data.error_message
self.new_patch_parent = data.new_patch_parent
self.target_release = data.target_release
self.succeeded_patches = []
self.context_mismatch_times = 0
self.round_succeeded = False
self.all_hunks_applied_succeeded = False
self.compile_succeeded = False
self.testcase_succeeded = False
self.poc_succeeded = False
self.symbol_map = {}
self.now_hunk = ""
self.now_hunk_num = 0
self.hunk_log_info = {}
self.add_percent = 0
self.last_context = []
def _checkout(self, ref: str) -> None:
self.repo.git.reset("--hard")
self.repo.git.checkout(ref)
def _get_patch(self, ref: str) -> str:
try:
return self.repo.git.show(f"{ref}^..{ref}")
except:
return "Error commit id, please check if the commit id is correct."
def _prepare(self, ref: str) -> None:
"""
Prepares the project by generating a symbol map using ctags.
Raises:
subprocess.CalledProcessError: If the ctags command fails.
"""
ctags = subprocess.run(
["ctags", "--excmd=number", "-R", "."],
stdout=subprocess.PIPE,
cwd=self.dir,
stdin=subprocess.PIPE,
stderr=subprocess.DEVNULL,
)
ctags.check_returncode()
self.symbol_map[ref] = {}
with open(os.path.join(self.dir, "tags"), "rb") as f:
for line in f.readlines():
if text := line.decode("utf-8", errors="ignore"):
if text.startswith("!_TAG_"):
continue
try:
symbol, file, lineno = text.strip().split(';"')[0].split("\t")
lineno = int(lineno)
if symbol not in self.symbol_map[ref]:
self.symbol_map[ref][symbol] = []
self.symbol_map[ref][symbol].append((file, lineno))
except:
continue
def _viewcode(self, ref: str, path: str, startline: int, endline: int) -> str:
"""
View a file from a specific ref of the target repository. Lines between startline and endline are shown.
Args:
ref (str): The specific ref of the target repository.
path (str): The path of the file to view.
startline (int): The starting line number to display.
endline (int): The ending line number to display.
Returns:
str: The content of the file between the specified startline and endline.
If the file doesn't exist in the commit, a message indicating that is returned.
"""
try:
file = self.repo.tree(ref) / path
except:
return "This file doesn't exist in this commit."
content = file.data_stream.read().decode("utf-8", errors="ignore")
lines = content.split("\n")
ret = []
if not lines:
return "This file is empty.\n"
if startline > endline:
startline, endline = endline, startline
startline = max(1, startline)
if startline > len(lines):
ret.append(
f"This file only has {len(lines)} lines. Showing full file.\n"
)
startline = 1
endline = len(lines)
elif endline > len(lines):
endline = len(lines)
ret.append(
f"This file only has {len(lines)} lines. Here are lines {startline} through {endline}.\n"
)
else:
ret.append(f"Here are lines {startline} through {endline}.\n")
for i in range(startline - 1, endline):
ret.append(lines[i])
return (
"\n".join(ret)
+ "\nBased on the previous information, think carefully do you see the target code? You may want to keep checking if you don't.\n"
)
def _locate_symbol(self, ref: str, symbol: str) -> List[Tuple[str, int]] | None:
"""
Locate a symbol in a specific ref of the target repository.
Args:
ref (str): The reference of the target repository.
symbol (str): The symbol to locate.
Returns:
List[Tuple[str, int]] | None: File path and code lines.
"""
if ref not in self.symbol_map:
self._checkout(ref)
self._prepare(ref)
if symbol in self.symbol_map[ref]:
return self.symbol_map[ref][symbol]
else:
return None
def _locate_similar_symbol(
self, ref: str, symbol: str
) -> List[Tuple[str, int]] | None:
"""
Locate the most similar symbol with llm need in a specific ref of the target repository.
Args:
ref (str): The reference of the target repository.
symbol (str): The symbol to locates.
Returns:
List[Tuple[str, int]] : File path and code lines for the most similar symbol.
"""
symbols = self.symbol_map.get(ref, {})
most_similar = None
smallest_distance = float("inf")
for symbol_i in symbols.keys():
distance = Levenshtein.distance(symbol, symbol_i)
if distance < smallest_distance:
smallest_distance = distance
most_similar = symbol_i
return symbols.get(most_similar), most_similar
def _git_history(self) -> str:
"""
XXX: TBD
Args:
XXX
Returns:
XXX(str):
"""
if self.now_hunk != "completed":
merge_base = self.repo.merge_base(
self.target_release, self.new_patch_parent
)
start_commit = merge_base[0].hexsha if merge_base else None
hunk = self.now_hunk
filepath = re.findall(r"--- a/(.*)", hunk)[0]
chunks = re.findall(r"@@ -(\d+),(\d+) \+(\d+),(\d+) @@(.*)", hunk)[0]
start_line = chunks[0]
end_line = int(chunks[0]) + int(chunks[1]) - 1
log_message = self.repo.git.log(
"--oneline",
f"-L {start_line},{end_line}:{filepath}",
f"{start_commit}..{self.new_patch_parent}",
)
if self.now_hunk_num not in self.hunk_log_info and log_message:
last_context = list(utils.split_patch(log_message, False))[-1]
(
_,
context_line_num,
self.last_context,
add_line_num,
) = utils.extract_context(last_context.split("\n")[3:])
self.add_percent = add_line_num / (add_line_num + context_line_num)
self.hunk_log_info[self.now_hunk_num] = []
patch_list = log_message.split("\n")
for idx, line in enumerate(patch_list):
if line.startswith("diff --git"):
sha_num = patch_list[idx - 2].split(" ")[0]
self.hunk_log_info[self.now_hunk_num].append(sha_num)
ret = log_message[len(log_message) - 5001 : -1]
ret += "\nYou need to do the following analysis based on the information in the last commit:\n"
ret += "Analyze the code logic of the context of the patch to be ported in this commit step by step.\n"
ret += "If code logic already existed before this commit, the patch context can be assumed to remain in a similar location. Use `locate` and `viewcode` to check your results.\n"
ret += "If code logic were added in this commit, then you need to `git_show` for further details.\n"
return ret
else:
pass
def _git_show(self) -> str:
"""
Show commit message for a specific ref when LLM need.
Args:
ref (str): The reference of the target repository.
Returns:
message(str): The commit message of ref
"""
try:
ref_line = self.hunk_log_info[self.now_hunk_num][-1]
ref = ref_line.split(" ")[0].strip()
log = self.repo.git.show(f"{ref}")
pps = utils.split_patch(log, False)
dist = float("inf")
last_context_len = len(self.last_context)
best_context = []
file_path = ""
file_no = 0
for idx, pp in enumerate(pps):
try:
file_path_i = re.findall(r"--- a/(.*)", pp)[0]
chunks = re.findall(r"@@ -(\d+),(\d+) \+(\d+),(\d+) @@(.*)", pp)[0]
contexts, _, _, _ = utils.extract_context(pp.split("\n")[3:])
if (int(chunks[1]) - int(chunks[3])) < last_context_len:
continue
lineno, dist_i = utils.find_most_similar_block(
self.last_context, contexts, last_context_len, False
)
if dist_i < dist:
best_context = contexts[
lineno - 1 : lineno - 1 + last_context_len
]
dist = dist_i
file_path = file_path_i
file_no = int(chunks[0]) + lineno - 1
except:
continue
ret = ""
stat = self.repo.git.show("--stat", f"{ref}")
ret += stat[0 : min(len(stat), 3000)]
ret += "\n"
if self.add_percent < 0.6:
ret += f"[IMPORTANT] The relevant code shown by `git_history` is not fully `+` lines.\n"
ret += f"[IMPORTANT] This means that the code in question was not added or migrated in this commit.\n"
ret += f"[IMPORTANT] Please think step by step and check the abstract below carefully. If error exists in abstract, please ignore the info below.\n"
elif best_context:
ret += f"Because the commit's code change maybe too long, so I generate the abstract of the code change to show you how code changed in this commit.\n"
ret += f"Commit shows that the patch code in old version maybe in the file {file_path} around line number {file_no} to {file_no + last_context_len}. The code is below\n"
code_snippets = "\n".join(best_context)
ret += f"{code_snippets}"
ret += f"\nYou can call `viewcode` and `locate_symbol` to find the relevant code based on this information step by step."
else:
ret += f"This commit shows that there is a high probability that this code is new, so the corresponding code segment cannot be found in the old version.\n"
ret += f"You can call `viewcode` and `locate_symbol` to further check the results step by step. For newly introduced code, we consider that this hunk `need not ported`.\n"
return ret
except:
return "Something error, maybe you don't use git_history before or git_history is empty."
def _apply_error_handling(self, ref: str, revised_patch: str) -> Tuple[str, str]:
"""
Generate feedback to llm when an error patch is applied.
When a file is not found, it is looked for in the five most similar files.
Args:
ref (str): The reference of the target repository.
revised_patch (str): The patch to be applied.
Returns:
Tuple[str, str]: Bug patch similar code block information and difference between patch context and original code context.
"""
path = re.findall(r"--- a/(.*)", revised_patch)[0]
revised_patch_line = revised_patch.split("\n")[3:]
contexts, num_context, _, _ = utils.extract_context(revised_patch_line)
lineno = -1
lines = []
min_distance = float("inf")
try:
file = self.repo.tree(ref) / path
content = file.data_stream.read().decode("utf-8", errors="ignore")
lines = content.split("\n")
lineno, dist = utils.find_most_similar_block(
contexts, lines, num_context, False
)
except:
similar_files = utils.find_most_similar_files(path.split("/")[-1], self.dir)
for similar_file in similar_files:
file = self.repo.tree(ref) / similar_file
content = file.data_stream.read().decode("utf-8", errors="ignore")
similar_lines = content.split("\n")
current_line, current_dist = utils.find_most_similar_block(
"\n".join(contexts), similar_lines, num_context, False
)
if current_dist < min_distance:
min_distance = current_dist
lineno = current_line
path = similar_file
lines = similar_lines
startline = max(lineno - 1, 0)
endline = min(lineno + num_context, len(lines))
block = "Here are lines {} through {} of file {} for commit {}.\n".format(
startline, endline, path, ref
)
block += "```code snippet\n"
for i in range(startline, endline):
block = block + lines[i] + "\n"
block += "```\n"
differ = "```context diff\n"
contexts = contexts[: min(len(lines), len(contexts))]
j = 0
for i, context in enumerate(revised_patch_line):
if context.startswith(" ") or context.startswith("-"):
if context[1:] != lines[lineno - 1 + j]:
differ += f"On the line {i + 4} of your patch.\n"
differ += f" Your patch:{context[1:]}\n"
differ += f"Original source code:{lines[lineno - 1 + j]}\n"
j += 1
if differ == "```context diff\n":
differ = "Here it shows that there is no difference between your context and the original code, the reason for the failure is that you didn't keep at least three lines of source code at the beginning and end of the patch, please follow this to fix it.\n"
else:
differ += "```\nPlease eliminate these diffs step by step. Be sure to eliminate these diffs the next time you generate a patch!\n"
return block, differ
def _apply_file_move_handling(self, ref: str, old_patch: str) -> str:
"""
If a patch cannot apply for "No such file", try to find the symbol and apply the patch to the correct file.
Args:
ref (str): The reference string.
old_patch (str): The patch that raises "No such file" when apply.
Returns:
str: If the file is found, return the current file path. Else, return all possible file paths.
"""
ret = ""
file_paths = []
missing_file_path = re.findall(r"--- a/(.*)", old_patch)[0]
diff_args = [
"--diff-filter=R",
"--name-status",
"--follow",
self.target_release,
self.new_patch_parent,
"--",
missing_file_path,
]
file_diff = self.repo.git.diff(diff_args)
if file_diff:
file_path = file_diff.split("\t")[1]
logger.debug(
f"We have found the patch's file path is {file_path} at target release by git diff."
)
file_paths.append(file_path)
if not file_paths:
try:
at_line = old_patch.split("\n")[2]
symbol_name = re.findall(r"\b\w+(?=\s*[{\(])", at_line)[0]
symbol_locations = self._locate_symbol(ref, symbol_name)
if not symbol_locations:
logger.debug(
f"No {missing_file_path} and no {symbol_name} in the repo."
)
file_paths = utils.find_most_similar_files(
missing_file_path.split("/")[-1], self.dir
)
else:
logger.debug(f"Find {symbol_name} in {symbol_locations}.")
file_paths = [item[0] for item in symbol_locations]
except:
logger.debug("Can not find a symbol in given patch.")
file_paths = utils.find_most_similar_files(
missing_file_path.split("/")[-1], self.dir
)
for file_path in file_paths:
new_patch = old_patch.replace(missing_file_path, file_path)
logger.debug(f"Try to apply patch to {file_path}.")
apply_ret = self._apply_hunk(ref, new_patch, False)
if "successfully" in apply_ret:
logger.debug(f"{missing_file_path} has been moved to {file_path}.")
return f"{missing_file_path} has been moved to {file_path}. Please use --- a/{file_path} in your patch.\n"
else:
ret += apply_ret
logger.debug(f"Patch can not be applied to {file_paths}.")
return f"The target file has been moved, here is possible file paths:{file_paths}\n{ret}"
def _apply_hunk(self, ref: str, patch: str, revise_context: bool = False) -> str:
"""
Apply a hunk to a specific ref of the target repository.
Args:
ref (str): The reference of the target repository.
patch (str): The patch to be applied.
Returns:
str: A string indicating the result of the patch application.
Raises:
Exception: If the patch fails to apply.
"""
ret = ""
self._checkout(ref)
self.repo.git.reset("--hard")
if revise_context:
logger.debug("original patch:\n" + patch)
revised_patch, fixed = utils.revise_patch(patch, self.dir, revise_context)
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(revised_patch)
logger.debug("revised patch:\n" + revised_patch)
logger.debug(f"Applying patch {f.name}")
try:
self.repo.git.apply([f.name], v=True)
ret += "Patch applied successfully\n"
self.succeeded_patches.append(revised_patch)
self.round_succeeded = True
except Exception as e:
if "No such file" in e.stderr:
logger.debug(f"File not found")
find_ret = self._apply_file_move_handling(ref, revised_patch)
ret += find_ret
elif "corrupt patch" in e.stderr:
ret = "Unexpected corrupt patch, Please carefully check your answer, especially in your call tools arguments.\n"
else:
logger.debug(f"Context mismatch")
ret += "This patch does not apply because of CONTEXT MISMATCH. Context are patch lines that already exist in the file, that is, lines starting with ` ` and `-`. You should modify the error patch according to the context of older version.\n"
block, differ = self._apply_error_handling(ref, revised_patch)
ret += block
ret += "Besides, here is detailed info about how the context differs between the patch and the old version.\n"
ret += differ
self.repo.git.reset("--hard")
return ret
def _compile_patch(
self, ref: str, complete_patch: str, revise_context: bool = False
) -> str:
"""
If all hunks could be applied successfully, compiles the patched source code after applying the joined patch.
Args:
ref (str): The reference to checkout before applying the patch.
complete_patch (str): The complete patch to be applied.
Returns:
str: A message indicating the result of the compilation process.
Raises:
subprocess.TimeoutExpired: If the compilation process times out.
"""
self._checkout(ref)
ret = ""
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(complete_patch)
logger.debug(f"The completed patch file {f.name}")
pps = utils.split_patch(complete_patch, False)
for idx, pp in enumerate(pps):
revised_patch, fixed = utils.revise_patch(pp, self.dir, revise_context)
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
f.write(revised_patch)
try:
self.repo.git.apply([f.name], v=True)
logger.debug(
f"The joined patch hunk {idx} could be applied successfully, file {f.name}"
)
except Exception as e:
logger.debug(
f"Failed to apply Complete patch hunk {idx}, file {f.name}"
)
ret = f"For the patch you just generated, there was an APPLY failure during testing. Specifically there was a context mismatch in hunk {idx} across the patch, below is part of the feedback I found for you.\n"
block, differ = self._apply_error_handling(ref, revised_patch)
ret += block
ret += f"Here is the source code near the hunk context for your reference, a good patch context should look exactly like the source code.\n"
ret += f"In addition to that, I've got more detailed error messages for you below where the context of your generated patch differs specifically from the source code context.(The line numbers below are all line numbers in the hunk, not the entire patch.)\n"
ret += differ
ret += f"Based on the above feedback, MUST you please modify only hunk {idx} in the patch and leave the other hunks untouched so that the context present in hunk {idx} is exactly the same as the source code to guarantee that git apply can be executed normally.\n"
self.repo.git.reset("--hard")
return ret
logger.debug("Start compile the patched source code")
if not os.path.exists(os.path.join(self.dir, "build.sh")):
logger.debug("No build.sh file found.")
ret += "The patched source code could be COMPILED successfully! I really thank you for your great efforts.\n"
self.compile_succeeded = True
return ret
docker_command = [
"docker",
"run",
"-v",
f"{self.dir}:{self.dir}",
"--rm",
"build-kernel-ubuntu-16.04",
"/bin/bash",
"-c",
f"cd {self.dir}; bash build.sh",
]
build_process = subprocess.Popen(
docker_command,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.dir,
text=True,
)
try:
_, compile_result = build_process.communicate(timeout=60 * 60)
except subprocess.TimeoutExpired:
build_process.kill()
ret += f"The compilation process of the patched source code is timeout. "
self.repo.git.reset("--hard")
logger.warning(
"Timeout in project compilation. Please check patch manually!"
)
for patch in self.succeeded_patches:
logger.info(patch)
exit(0)
return ret
if build_process.returncode != 0:
logger.info(f"Compilation FAILED")
error_lines = "\n".join(
[
line
for line in compile_result.splitlines()
if "error:" in line.lower()
]
)
logger.debug(error_lines)
ret += "The source code could not be COMPILED successfully after applying the patch. "
ret += "Next I'll give you the error message during compiling, and you should modify the error patch. "
ret += f"Here is the error message:\n{error_lines}\n"
ret += "Please revise the patch with above error message. "
ret += "Or use tools `locate_symbol` and `viewcode` to re-check patch-related code snippet. "
ret += "Please DO NOT send the same patch to me, repeated patches will harm the lives of others.\n"
self.repo.git.reset("--hard")
else:
logger.info(f"Compilation PASS")
ret += "The patched source code could be COMPILED successfully! I really thank you for your great efforts.\n"
self.compile_succeeded = True
return ret
def _run_testcase(self) -> str:
"""
Runs the testcase after compiling a patch.
Returns:
str: A message indicating the result of the testcase process.
"""
ret = ""
logger.debug("Run testcase after compile")
if not os.path.exists(os.path.join(self.dir, "test.sh")):
logger.debug("No test.sh file found, considered as test passed.")
self.testcase_succeeded = True
ret += "The patched source code could pass TESTCASE! I really thank you for your great efforts.\n"
return ret
testcase_process = subprocess.Popen(
["/bin/bash", "test.sh"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.dir,
text=True,
)
try:
_, testcase_result = testcase_process.communicate(timeout=60 * 30)
except subprocess.TimeoutExpired:
testcase_process.kill()
ret += "The TESTCASE process of the patched source code is timeout. "
return ret
if testcase_process.returncode != 0:
logger.info(f"Testsuite FAILED")
logger.debug(f"{testcase_result}")
ret = "The patched program could not pass the testcase. "
ret += "Next I'll give you the error message during running the testcase, and you should modify the previous error patch according to this section. "
ret += f"Here is the error message:\n{testcase_result}\n"
ret += "Please revise the patch with above error message. "
ret += "Or use tools `locate_symbol` and `viewcode` to re-check patch-related code snippet. "
ret += "Please DO NOT send the same patch to me, repeated patches will harm the lives of others.\n"
self.compile_succeeded = False
else:
logger.info(f"Testsuite PASS")
ret += "The patched source code could pass TESTCASE! I really thank you for your great efforts.\n"
self.testcase_succeeded = True
return ret
def _run_poc(self, complete_patch) -> str:
"""
Runs the Proof of Concept (PoC) after running the testcase.
Returns:
str: A message indicating the result of the PoC process.
"""
ret = ""
logger.debug("Run PoC after compile and run testcase")
if not os.path.exists(os.path.join(self.dir, "poc.sh")):
logger.debug("No poc.sh file found, considered as PoC passed.")
self.poc_succeeded = True
self.succeeded_patches.clear()
self.succeeded_patches.append(complete_patch)
ret += "Existing PoC could NOT TRIGGER the bug, which means your patch successfully fix the bug! I really thank you for your great efforts.\n"
return ret
poc_process = subprocess.Popen(
["/bin/bash", "poc.sh"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.dir,
text=True,
)
try:
_, poc_result = poc_process.communicate(timeout=60 * 10)
except subprocess.TimeoutExpired:
poc_process.kill()
ret += "The TESTCASE process of the patched source code is timeout. "
return ret
if self.err_msg in poc_result:
logger.info(f"PoC test FAILED")
logger.debug(f"returncode = {poc_process.returncode}")
logger.debug(f"stderr: {poc_result}")
ret += "Existing PoC could still trigger the bug, which means your patch fail to fix the bug. "
ret += "Next I'll give you the error message during running the PoC, and you should modify the previous error patch according to this section. "
ret += f"Here is the error message:\n{poc_result}\n"
ret += "Please revise the patch with above error message. "
ret += "Or use tools `locate_symbol` and `viewcode` to re-check patch-related code snippet. "
ret += "Please DO NOT send the same patch to me, repeated patches will harm the lives of others.\n"
self.compile_succeeded = False
self.testcase_succeeded = False
else:
logger.info(f"PoC test PASS")
ret += "Existing PoC could NOT TRIGGER the bug, which means your patch successfully fix the bug! I really thank you for your great efforts.\n"
self.succeeded_patches.clear()
self.succeeded_patches.append(complete_patch)
self.poc_succeeded = True
return ret
def _validate(self, ref: str, patch: str) -> str:
"""
Validates a patch by using the `_compile_patch`, `_run_testcase`, and `_run_poc` methods.
Args:
ref (str): The reference string.
patch (str): The patch string.
Returns:
str: The validation result.
"""
if self.all_hunks_applied_succeeded:
ret = ""
if not self.compile_succeeded:
ret += self._compile_patch(
ref, patch, True if self.context_mismatch_times >= 1 else False
)
self.context_mismatch_times += 1
if self.compile_succeeded and not self.testcase_succeeded:
ret += self._run_testcase()
if (
self.compile_succeeded
and self.testcase_succeeded
and not self.poc_succeeded
):
ret += self._run_poc(patch)
return ret
else:
if "need not ported" in patch:
self.round_succeeded = True
return "Patch applied successfully\n"
ret = self._apply_hunk(
ref, patch, True if self.context_mismatch_times >= 2 else False
)
if "CONTEXT MISMATCH" in ret:
self.context_mismatch_times += 1
return ret
def get_tools(self):
return (
creat_viewcode_tool(self),
creat_locate_symbol_tool(self),
create_validate_tool(self),
create_git_history_tool(self),
create_git_show_tool(self),
)
def creat_locate_symbol_tool(project: Project):
@tool
def locate_symbol(ref: str, symbol: str) -> str:
"""
Locate a symbol in a specific ref of the target repository.
"""
res = project._locate_symbol(ref, symbol)
if res is not None:
return "\n".join([f"{file}:{line}" for file, line in res])
else:
res, most_similar = project._locate_similar_symbol(ref, symbol)
ret = f"The symbol {symbol} you are looking for does not exist in the current ref.\n"
ret += f"But here is a symbol similar to it. It's `{most_similar}`.\n"
ret += f"The file where this symbol is located is: \n"
ret += "\n".join([f"{file}:{line}" for file, line in res])
ret += f"\nPlease be careful to check that this symbol indicates the same thing as the previous symbol.\n"
return ret
return locate_symbol
def creat_viewcode_tool(project: Project):
@tool
def viewcode(ref: str, path: str, startline: int, endline: int) -> str:
"""
View a file from a specific ref of the target repository. Lines between startline and endline are shown.
"""
return project._viewcode(ref, path, startline, endline)
return viewcode
def create_validate_tool(project: Project):
@tool
def validate(ref: str, patch: str) -> str:
"""
validate a patch on a specific ref of the target repository.
"""
return project._validate(ref, patch)
return validate
def create_git_history_tool(project: Project):
@tool
def git_history() -> str:
"""
get history for lines which relate to patch hunk.
"""
return project._git_history()
return git_history
def create_git_show_tool(project: Project):
@tool
def git_show() -> str:
"""
show change log for a specific ref
"""
return project._git_show()
return git_show