Compare commits
No commits in common. "scripts" and "remove_addition_context_for_fix_workflow" have entirely different histories.
scripts
...
remove_add
3
.gitignore
vendored
3
.gitignore
vendored
@ -3,10 +3,7 @@ __pycache__/
|
||||
.DS_Store
|
||||
tmp/
|
||||
|
||||
cache/
|
||||
|
||||
custom/*
|
||||
!custom/config.yml.example
|
||||
|
||||
user_settings.yml
|
||||
.aider*
|
||||
|
9
Makefile
9
Makefile
@ -8,16 +8,15 @@ setup-dev:
|
||||
@pip install -r requirements-dev.txt
|
||||
@echo "Done!"
|
||||
|
||||
T="."
|
||||
check:
|
||||
@echo ${div}
|
||||
ruff check $(T)
|
||||
ruff format $(T) --check
|
||||
ruff check .
|
||||
ruff format . --check
|
||||
@echo "Done!"
|
||||
|
||||
fix:
|
||||
@echo ${div}
|
||||
ruff format $(T)
|
||||
ruff format .
|
||||
@echo ${div}
|
||||
ruff check $(T) --fix
|
||||
ruff check . --fix
|
||||
@echo "Done!"
|
||||
|
@ -1,25 +0,0 @@
|
||||
### aider 操作指南
|
||||
|
||||
aider是一个AI辅助的代码编辑工具,可以根据自然语言指令修改代码。
|
||||
|
||||
用途:
|
||||
根据用户提供的指令,自动分析和修改已添加到aider中的代码文件。
|
||||
|
||||
使用方法:
|
||||
1. 使用 `/aider.files.add` 命令添加需要处理的文件
|
||||
2. 输入 `/aider <message>` 命令,其中 `<message>` 是你想要aider执行的任务描述
|
||||
3. 等待aider生成建议的更改
|
||||
4. 在IDE中查看每个文件的Diff视图,选择是否接受修改
|
||||
5. 对于多个文件的更改,系统会在每个文件之后询问是否继续查看下一个文件的更改
|
||||
|
||||
注意事项:
|
||||
- 使用前必须先添加文件到aider,否则会提示使用 'aider.files.add' 命令
|
||||
- 可以使用 `aider.files.remove` 命令从aider中移除文件
|
||||
- 所有更改都会在IDE中以Diff视图形式展示,你可以决定是否应用这些更改
|
||||
- aider使用OpenAI的API,请确保已正确设置API密钥
|
||||
|
||||
示例:
|
||||
/aider 重构这段代码以提高性能
|
||||
|
||||
额外信息:
|
||||
aider支持多种编程语言,可以执行代码重构、bug修复、性能优化等任务。它会分析当前添加的所有文件,并提供整体的改进建议。
|
@ -1,213 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from devchat.ide import IDEService
|
||||
|
||||
from lib.chatmark import Button
|
||||
|
||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
||||
|
||||
|
||||
def save_config(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
|
||||
def write_python_path_to_config():
|
||||
"""
|
||||
Write the current system Python path to the configuration.
|
||||
"""
|
||||
python_path = sys.executable
|
||||
save_config(GLOBAL_CONFIG_PATH, "aider_python", python_path)
|
||||
print(f"Python path '{python_path}' has been written to the configuration.")
|
||||
|
||||
|
||||
def get_aider_files():
|
||||
"""
|
||||
从.chat/.aider_files文件中读取aider文件列表
|
||||
"""
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
if not os.path.exists(aider_files_path):
|
||||
return []
|
||||
|
||||
with open(aider_files_path, "r") as f:
|
||||
return [line.strip() for line in f if line.strip()]
|
||||
|
||||
|
||||
def run_aider(message, files):
|
||||
"""
|
||||
运行aider命令
|
||||
"""
|
||||
python = sys.executable
|
||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
|
||||
cmd = [
|
||||
python,
|
||||
"-m",
|
||||
"aider",
|
||||
"--model",
|
||||
f"openai/{model}",
|
||||
"--yes",
|
||||
"--no-auto-commits",
|
||||
"--dry-run",
|
||||
"--no-pretty",
|
||||
"--message",
|
||||
message,
|
||||
] + files
|
||||
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
has_started = False
|
||||
aider_output = ""
|
||||
for line in process.stdout:
|
||||
if "run with --help" in line or 'run "aider --help"' in line:
|
||||
has_started = True
|
||||
continue
|
||||
if has_started:
|
||||
aider_output += line
|
||||
print(line, end="", flush=True)
|
||||
|
||||
return_code = process.wait()
|
||||
|
||||
if return_code != 0:
|
||||
for line in process.stderr:
|
||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
||||
sys.exit(return_code)
|
||||
|
||||
return aider_output
|
||||
|
||||
|
||||
def apply_changes(changes, files):
|
||||
"""
|
||||
应用aider生成的更改
|
||||
"""
|
||||
changes_file = ".chat/changes.txt"
|
||||
os.makedirs(os.path.dirname(changes_file), exist_ok=True)
|
||||
with open(changes_file, "w") as f:
|
||||
f.write(changes)
|
||||
|
||||
python = sys.executable
|
||||
model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
|
||||
cmd = [
|
||||
python,
|
||||
"-m",
|
||||
"aider",
|
||||
"--model",
|
||||
f"openai/{model}",
|
||||
"--yes",
|
||||
"--no-auto-commits",
|
||||
"--apply",
|
||||
changes_file,
|
||||
] + files
|
||||
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||
|
||||
has_started = False
|
||||
for line in process.stdout:
|
||||
if "Model:" in line:
|
||||
has_started = True
|
||||
continue
|
||||
if has_started:
|
||||
print(line, end="", flush=True)
|
||||
|
||||
return_code = process.wait()
|
||||
|
||||
if return_code != 0:
|
||||
for line in process.stderr:
|
||||
print(f"Error: {line.strip()}", file=sys.stderr)
|
||||
sys.exit(return_code)
|
||||
|
||||
os.remove(changes_file)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function to run the aider command.
|
||||
|
||||
This function performs the following tasks:
|
||||
1. Checks for correct command-line usage
|
||||
2. Writes the current Python path to the configuration
|
||||
3. Retrieves the list of files to be processed
|
||||
4. Runs the aider command with the given message
|
||||
5. Applies the suggested changes
|
||||
6. Displays the differences in the IDE
|
||||
|
||||
Usage: python command.py <message>
|
||||
"""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python command.py <message>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
write_python_path_to_config()
|
||||
|
||||
message = sys.argv[1]
|
||||
files = get_aider_files()
|
||||
|
||||
if not files:
|
||||
print(
|
||||
"No files added to aider. Please add files using 'aider.files.add' command.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
print("Running aider...\n", flush=True)
|
||||
changes = run_aider(message, files)
|
||||
|
||||
if not changes:
|
||||
print("No changes suggested by aider.")
|
||||
sys.exit(0)
|
||||
|
||||
print("\nApplying changes...\n", flush=True)
|
||||
|
||||
# 保存原始文件内容
|
||||
original_contents = {}
|
||||
for file in files:
|
||||
with open(file, "r") as f:
|
||||
original_contents[file] = f.read()
|
||||
|
||||
# 应用更改
|
||||
apply_changes(changes, files)
|
||||
|
||||
# 读取更新后的文件内容
|
||||
updated_contents = {}
|
||||
for file in files:
|
||||
with open(file, "r") as f:
|
||||
updated_contents[file] = f.read()
|
||||
|
||||
# 还原原始文件内容
|
||||
for file in files:
|
||||
with open(file, "w") as f:
|
||||
f.write(original_contents[file])
|
||||
|
||||
# 使用 IDEService 展示差异
|
||||
ide_service = IDEService()
|
||||
for index, file in enumerate(files):
|
||||
ide_service.diff_apply(file, updated_contents[file])
|
||||
if index < len(files) - 1:
|
||||
# 等待用户确认
|
||||
button = Button(
|
||||
["Show Next Changes", "Cancel"],
|
||||
)
|
||||
button.render()
|
||||
|
||||
idx = button.clicked
|
||||
if idx == 0:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
|
||||
print("Changes have been displayed in the IDE.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,9 +0,0 @@
|
||||
description: "aider command"
|
||||
workflow_python:
|
||||
env_name: devchat-aider-env
|
||||
version: 3.11.0
|
||||
dependencies: requirements.txt
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $workflow_python $command_path/command.py "$input"
|
@ -1,23 +0,0 @@
|
||||
### aider.files.add
|
||||
|
||||
这个命令用于将文件添加到aider的处理列表中。
|
||||
|
||||
用途:
|
||||
添加指定文件到aider,使其包含在后续的aider操作中。
|
||||
|
||||
使用方法:
|
||||
/aider.files.add <file_path>
|
||||
|
||||
参数:
|
||||
- <file_path>: 要添加的文件路径(必需)
|
||||
|
||||
注意事项:
|
||||
- 文件路径必须是有效的格式
|
||||
- 已存在于列表中的文件不会重复添加
|
||||
- 成功添加后会显示当前的aider文件列表
|
||||
|
||||
示例:
|
||||
/aider.files.add src/main.py
|
||||
|
||||
额外信息:
|
||||
这个命令会将文件路径保存到.chat/.aider_files文件中。如果.chat目录不存在,会自动创建。
|
@ -1,66 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def is_valid_path(path):
|
||||
"""
|
||||
检查路径是否为有效的文件路径形式
|
||||
"""
|
||||
try:
|
||||
# 尝试规范化路径
|
||||
normalized_path = os.path.normpath(path)
|
||||
# 检查路径是否是绝对路径或相对路径
|
||||
return (
|
||||
os.path.isabs(normalized_path)
|
||||
or not os.path.dirname(normalized_path) == normalized_path
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def add_file(file_path):
|
||||
# 1. 检查是否为有效的文件路径形式
|
||||
if not is_valid_path(file_path):
|
||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取绝对路径
|
||||
abs_file_path = file_path.strip()
|
||||
|
||||
# 2. 将新增文件路径存储到.chat/.aider_files文件中
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat目录存在
|
||||
os.makedirs(os.path.dirname(aider_files_path), exist_ok=True)
|
||||
|
||||
# 读取现有文件列表
|
||||
existing_files = set()
|
||||
if os.path.exists(aider_files_path):
|
||||
with open(aider_files_path, "r") as f:
|
||||
existing_files = set(line.strip() for line in f)
|
||||
|
||||
# 添加新文件
|
||||
existing_files.add(abs_file_path)
|
||||
|
||||
# 写入更新后的文件列表
|
||||
with open(aider_files_path, "w") as f:
|
||||
for file in sorted(existing_files):
|
||||
f.write(f"{file}\n")
|
||||
|
||||
print(f"Added '{abs_file_path}' to aider files.")
|
||||
print("\nCurrent aider files:")
|
||||
for file in sorted(existing_files):
|
||||
print(f"- {file}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
||||
print("Usage: /aider.files.add <file_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
add_file(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: "add files to aider"
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,19 +0,0 @@
|
||||
### aider.files.list
|
||||
|
||||
这个命令用于列出当前在aider处理列表中的所有文件。
|
||||
|
||||
用途:
|
||||
显示所有已添加到aider中的文件,提供当前aider正在处理的文件概览。
|
||||
|
||||
使用方法:
|
||||
/aider.files.list
|
||||
|
||||
注意事项:
|
||||
- 如果没有文件被添加到aider,会显示相应的提示消息
|
||||
- 文件列表按字母顺序排序显示
|
||||
|
||||
示例:
|
||||
/aider.files.list
|
||||
|
||||
额外信息:
|
||||
这个命令会读取.chat/.aider_files文件的内容来获取文件列表。如果该文件不存在,会提示尚未添加任何文件。
|
@ -1,31 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def list_files():
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat/.aider_files文件存在
|
||||
if not os.path.exists(aider_files_path):
|
||||
print("No files have been added to aider yet.")
|
||||
sys.exit(0)
|
||||
|
||||
# 读取文件列表
|
||||
with open(aider_files_path, "r") as f:
|
||||
files = [line.strip() for line in f]
|
||||
|
||||
# 打印文件列表
|
||||
if files:
|
||||
print("Aider files:")
|
||||
for file in sorted(files):
|
||||
print(f"- {file}")
|
||||
else:
|
||||
print("No files found in aider.")
|
||||
|
||||
|
||||
def main():
|
||||
list_files()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: "list files in aider"
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,23 +0,0 @@
|
||||
### aider.files.remove
|
||||
|
||||
这个命令用于从aider处理列表中移除指定的文件。
|
||||
|
||||
用途:
|
||||
将指定文件从aider的处理列表中删除,使其不再包含在后续的aider操作中。
|
||||
|
||||
使用方法:
|
||||
/aider.files.remove <file_path>
|
||||
|
||||
参数:
|
||||
- <file_path>: 要移除的文件路径(必需)
|
||||
|
||||
注意事项:
|
||||
- 文件路径必须是有效的格式
|
||||
- 如果指定的文件不在列表中,会显示相应的提示消息
|
||||
- 成功移除后会显示更新后的aider文件列表
|
||||
|
||||
示例:
|
||||
/aider.files.remove src/main.py
|
||||
|
||||
额外信息:
|
||||
这个命令会更新.chat/.aider_files文件,从中删除指定的文件路径。如果文件不存在于列表中,操作会安全退出。
|
@ -1,72 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def is_valid_path(path):
|
||||
"""
|
||||
检查路径是否为有效的文件路径形式
|
||||
"""
|
||||
try:
|
||||
# 尝试规范化路径
|
||||
normalized_path = os.path.normpath(path)
|
||||
# 检查路径是否是绝对路径或相对路径
|
||||
return (
|
||||
os.path.isabs(normalized_path)
|
||||
or not os.path.dirname(normalized_path) == normalized_path
|
||||
)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def remove_file(file_path):
|
||||
# 1. 检查是否为有效的文件路径形式
|
||||
if not is_valid_path(file_path):
|
||||
print(f"Error: '{file_path}' is not a valid file path format.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 获取绝对路径
|
||||
abs_file_path = file_path.strip()
|
||||
|
||||
# 2. 从.chat/.aider_files文件中移除指定文件路径
|
||||
aider_files_path = os.path.join(".chat", ".aider_files")
|
||||
|
||||
# 确保.chat目录存在
|
||||
if not os.path.exists(aider_files_path):
|
||||
print(f"Error: '{aider_files_path}' does not exist.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# 读取现有文件列表
|
||||
existing_files = set()
|
||||
with open(aider_files_path, "r") as f:
|
||||
existing_files = set(line.strip() for line in f)
|
||||
|
||||
# 检查文件是否在列表中
|
||||
if abs_file_path not in existing_files:
|
||||
print(f"'{abs_file_path}' is not in aider files.")
|
||||
sys.exit(0)
|
||||
|
||||
# 移除文件
|
||||
existing_files.remove(abs_file_path)
|
||||
|
||||
# 写入更新后的文件列表
|
||||
with open(aider_files_path, "w") as f:
|
||||
for file in sorted(existing_files):
|
||||
f.write(f"{file}\n")
|
||||
|
||||
print(f"Removed '{abs_file_path}' from aider files.")
|
||||
print("\nCurrent aider files:")
|
||||
for file in sorted(existing_files):
|
||||
print(f"- {file}")
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2 or sys.argv[1].strip() == "":
|
||||
print("Usage: /aider.files.remove <file_path>", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
file_path = sys.argv[1]
|
||||
remove_file(file_path)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: "remove files from aider"
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,2 +0,0 @@
|
||||
git+https://gitee.com/imlaji/aider.git@main
|
||||
git+https://gitee.com/devchat-ai/devchat.git@aider
|
@ -1,24 +0,0 @@
|
||||
### code_task_summary
|
||||
|
||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
||||
|
||||
#### 用途
|
||||
- 自动生成简洁的代码任务描述
|
||||
- 帮助开发者快速理解任务要点
|
||||
- 用于更新项目配置或文档
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.code_task_summary [issue_url]`
|
||||
|
||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
||||
- 如提供issue_url,将直接使用该Issue的内容
|
||||
|
||||
#### 操作流程
|
||||
1. 获取Issue信息
|
||||
2. 生成代码任务摘要
|
||||
3. 允许用户编辑摘要
|
||||
4. 更新项目配置文件
|
||||
|
||||
#### 注意事项
|
||||
- 确保Git仓库配置正确
|
||||
- 需要有效的GitHub Token以访问API
|
@ -1,120 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
get_current_branch,
|
||||
get_github_repo,
|
||||
get_issue_info,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
)
|
||||
|
||||
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"You are a coding engineer, required to summarize the ISSUE description into a coding task description of no more than 50 words. \n" # noqa: E501
|
||||
"The ISSUE description is as follows: {issue_body}, please summarize the corresponding coding task description.\n" # noqa: E501
|
||||
'The coding task description should be output in JSON format, in the form of: {{"summary": "code task summary"}}\n' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_code_task_summary(issue_body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit code task summary:")
|
||||
def edit_code_task_summary(task_summary):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps({"id": issue["number"], "title": issue["title"], "body": issue["body"]})
|
||||
else:
|
||||
return task
|
||||
|
||||
|
||||
def get_issue_json(issue_id, task):
|
||||
issue = {"id": "no issue id", "title": "", "body": task}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start update code task summary ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
|
||||
repo_name = get_github_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id, task)
|
||||
assert_exit(not issue["body"], "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating code task summary ...", end="\n\n", flush=True)
|
||||
code_task_summary = generate_code_task_summary(issue_body=issue["body"])
|
||||
assert_exit(not code_task_summary, "Failed to generate code task summary.", exit_code=-1)
|
||||
assert_exit(
|
||||
not code_task_summary.get("summary", None),
|
||||
"Failed to generate code task summary, missing summary field in result.",
|
||||
exit_code=-1,
|
||||
)
|
||||
code_task_summary = code_task_summary["summary"]
|
||||
|
||||
# Select branch name
|
||||
code_task_summary = edit_code_task_summary(code_task_summary)
|
||||
assert_exit(not code_task_summary, "Failed to edit code task summary.", exit_code=-1)
|
||||
code_task_summary = code_task_summary[0]
|
||||
|
||||
# create and checkout branch
|
||||
print("Updating code task summary to config:")
|
||||
config_file = os.path.join(".chat", "complete.config")
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
config["taskDescription"] = code_task_summary
|
||||
else:
|
||||
config = {"taskDescription": code_task_summary}
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
print("Code task summary has updated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Generate code task summary.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1 +0,0 @@
|
||||
description: Root of github commands.
|
@ -1,23 +0,0 @@
|
||||
### commit
|
||||
|
||||
自动生成提交信息并执行Git提交。
|
||||
|
||||
#### 用途
|
||||
- 生成规范的提交信息
|
||||
- 简化Git提交流程
|
||||
- 保持提交历史的一致性
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.commit [message]`
|
||||
|
||||
- message: 可选的用户输入,用于辅助生成提交信息
|
||||
|
||||
#### 操作流程
|
||||
1. 选择要提交的文件
|
||||
2. 生成提交信息
|
||||
3. 允许用户编辑提交信息
|
||||
4. 执行Git提交
|
||||
|
||||
#### 注意事项
|
||||
- 确保已选择需要提交的文件
|
||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
@ -1,539 +0,0 @@
|
||||
# flake8: noqa: E402
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_completion_stream
|
||||
|
||||
from lib.chatmark import Button, Checkbox, Form, TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit # noqa: E402
|
||||
from git_api import (
|
||||
get_github_repo,
|
||||
get_github_repo_issues,
|
||||
get_github_username,
|
||||
get_issue_info,
|
||||
subprocess_check_output,
|
||||
subprocess_run,
|
||||
)
|
||||
|
||||
diff_too_large_message_en = (
|
||||
"Commit failed. The modified content is too long "
|
||||
"and exceeds the model's length limit. "
|
||||
"You can try to make partial changes to the file and submit multiple times. "
|
||||
"Making small changes and submitting them multiple times is a better practice."
|
||||
)
|
||||
diff_too_large_message_zh = (
|
||||
"提交失败。修改内容太长,超出模型限制长度,"
|
||||
"可以尝试选择部分修改文件多次提交,小修改多提交是更好的做法。"
|
||||
)
|
||||
|
||||
COMMIT_PROMPT_LIMIT_SIZE = 20000
|
||||
|
||||
|
||||
def extract_markdown_block(text):
|
||||
"""
|
||||
Extracts the first Markdown code block from the given text without the language specifier.
|
||||
|
||||
:param text: A string containing Markdown text
|
||||
:return: The content of the first Markdown code block, or None if not found
|
||||
"""
|
||||
# 正则表达式匹配Markdown代码块,忽略可选的语言类型标记
|
||||
pattern = r"```(?:\w+)?\s*\n(.*?)\n```"
|
||||
match = re.search(pattern, text, re.DOTALL)
|
||||
|
||||
if match:
|
||||
# 返回第一个匹配的代码块内容,去除首尾的反引号和语言类型标记
|
||||
# 去除块结束标记前的一个换行符,但保留其他内容
|
||||
block_content = match.group(1)
|
||||
return block_content
|
||||
else:
|
||||
return text
|
||||
|
||||
|
||||
# Read the prompt from the diffCommitMessagePrompt.txt file
|
||||
def read_prompt_from_file(filename):
|
||||
"""
|
||||
Reads the content of a file and returns it as a string.
|
||||
|
||||
This function is designed to read a prompt message from a text file.
|
||||
It expects the file to be encoded in UTF-8 and will strip any leading
|
||||
or trailing whitespace from the content of the file. If the file does
|
||||
not exist or an error occurs during reading, the function logs an error
|
||||
message and exits the script.
|
||||
|
||||
Parameters:
|
||||
- filename (str): The path to the file that contains the prompt message.
|
||||
|
||||
Returns:
|
||||
- str: The content of the file as a string.
|
||||
|
||||
Raises:
|
||||
- FileNotFoundError: If the file does not exist.
|
||||
- Exception: If any other error occurs during file reading.
|
||||
"""
|
||||
try:
|
||||
with open(filename, "r", encoding="utf-8") as file:
|
||||
return file.read().strip()
|
||||
except FileNotFoundError:
|
||||
IDEService().ide_logging(
|
||||
"error",
|
||||
f"File {filename} not found. "
|
||||
"Please make sure it exists in the same directory as the script.",
|
||||
)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
IDEService().ide_logging(
|
||||
"error", f"An error occurred while reading the file {filename}: {e}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Read the prompt content from the file
|
||||
script_path = os.path.dirname(__file__)
|
||||
PROMPT_FILENAME = os.path.join(script_path, "diffCommitMessagePrompt.txt")
|
||||
PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT = read_prompt_from_file(PROMPT_FILENAME)
|
||||
prompt_commit_message_by_diff_user_input_llm_config = {
|
||||
"model": os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
}
|
||||
|
||||
|
||||
language = ""
|
||||
|
||||
|
||||
def assert_value(value, message):
|
||||
"""
|
||||
判断给定的value是否为True,如果是,则输出指定的message并终止程序。
|
||||
|
||||
Args:
|
||||
value: 用于判断的值。
|
||||
message: 如果value为True时需要输出的信息。
|
||||
|
||||
Returns:
|
||||
无返回值。
|
||||
|
||||
"""
|
||||
if value:
|
||||
print(message, file=sys.stderr, flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def decode_path(encoded_path):
|
||||
octal_pattern = re.compile(r"\\[0-7]{3}")
|
||||
|
||||
if octal_pattern.search(encoded_path):
|
||||
bytes_path = encoded_path.encode("utf-8").decode("unicode_escape").encode("latin1")
|
||||
decoded_path = bytes_path.decode("utf-8")
|
||||
return decoded_path
|
||||
else:
|
||||
return encoded_path
|
||||
|
||||
|
||||
def get_modified_files():
|
||||
"""
|
||||
获取当前修改文件列表以及已经staged的文件列表
|
||||
|
||||
Args:
|
||||
无
|
||||
|
||||
Returns:
|
||||
tuple: 包含两个list的元组,第一个list包含当前修改过的文件,第二个list包含已经staged的文件
|
||||
"""
|
||||
""" 获取当前修改文件列表以及已经staged的文件列表"""
|
||||
output = subprocess_check_output(["git", "status", "-s", "-u"], text=True, encoding="utf-8")
|
||||
lines = output.split("\n")
|
||||
modified_files = []
|
||||
staged_files = []
|
||||
|
||||
def strip_file_name(file_name):
|
||||
file = file_name.strip()
|
||||
if file.startswith('"'):
|
||||
file = file[1:-1]
|
||||
return file
|
||||
|
||||
for line in lines:
|
||||
if len(line) > 2:
|
||||
status, filename = line[:2], decode_path(line[3:])
|
||||
# check wether filename is a directory
|
||||
if os.path.isdir(filename):
|
||||
continue
|
||||
modified_files.append(os.path.normpath(strip_file_name(filename)))
|
||||
if status == "M " or status == "A " or status == "D ":
|
||||
staged_files.append(os.path.normpath(strip_file_name(filename)))
|
||||
return modified_files, staged_files
|
||||
|
||||
|
||||
def get_marked_files(modified_files, staged_files):
|
||||
"""
|
||||
根据给定的参数获取用户选中以供提交的文件
|
||||
|
||||
Args:
|
||||
modified_files (List[str]): 用户已修改文件列表
|
||||
staged_files (List[str]): 用户已staged文件列表
|
||||
|
||||
Returns:
|
||||
List[str]: 用户选中的文件列表
|
||||
"""
|
||||
# Create two Checkbox instances for staged and unstaged files
|
||||
staged_checkbox = Checkbox(staged_files, [True] * len(staged_files))
|
||||
|
||||
unstaged_files = [file for file in modified_files if file not in staged_files]
|
||||
unstaged_checkbox = Checkbox(unstaged_files, [False] * len(unstaged_files))
|
||||
|
||||
# Create a Form with both Checkbox instances
|
||||
form_list = []
|
||||
if len(staged_files) > 0:
|
||||
form_list.append("Staged:\n\n")
|
||||
form_list.append(staged_checkbox)
|
||||
|
||||
if len(unstaged_files) > 0:
|
||||
form_list.append("Unstaged:\n\n")
|
||||
form_list.append(unstaged_checkbox)
|
||||
|
||||
form = Form(form_list, submit_button_name="Continue")
|
||||
|
||||
# Render the Form and get user input
|
||||
form.render()
|
||||
|
||||
# Retrieve the selected files from both Checkbox instances
|
||||
staged_checkbox_selections = staged_checkbox.selections if staged_checkbox.selections else []
|
||||
unstaged_selections = unstaged_checkbox.selections if unstaged_checkbox.selections else []
|
||||
selected_staged_files = [staged_files[idx] for idx in staged_checkbox_selections]
|
||||
selected_unstaged_files = [unstaged_files[idx] for idx in unstaged_selections]
|
||||
|
||||
# Combine the selections from both checkboxes
|
||||
selected_files = selected_staged_files + selected_unstaged_files
|
||||
|
||||
return selected_files
|
||||
|
||||
|
||||
def rebuild_stage_list(user_files):
|
||||
"""
|
||||
根据用户选中文件,重新构建stage列表
|
||||
|
||||
Args:
|
||||
user_files: 用户选中的文件列表
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
# Unstage all files
|
||||
subprocess_check_output(["git", "reset"])
|
||||
# Stage all user_files
|
||||
for file in user_files:
|
||||
subprocess_run(["git", "add", file])
|
||||
|
||||
|
||||
def get_diff():
|
||||
"""
|
||||
获取暂存区文件的Diff信息
|
||||
|
||||
Args:
|
||||
无
|
||||
|
||||
Returns:
|
||||
bytes: 返回bytes类型,是git diff --cached命令的输出结果
|
||||
|
||||
"""
|
||||
return subprocess_check_output(["git", "diff", "--cached"])
|
||||
|
||||
|
||||
def get_current_branch():
|
||||
try:
|
||||
# 使用git命令获取当前分支名称
|
||||
result = subprocess_check_output(
|
||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
current_branch = result.decode("utf-8")
|
||||
return current_branch
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
return None
|
||||
|
||||
|
||||
def generate_commit_message_base_diff(user_input, diff, issue):
|
||||
"""
|
||||
根据diff信息,通过AI生成一个commit消息
|
||||
|
||||
Args:
|
||||
user_input (str): 用户输入的commit信息
|
||||
diff (str): 提交的diff信息
|
||||
|
||||
Returns:
|
||||
str: 生成的commit消息
|
||||
|
||||
"""
|
||||
global language
|
||||
language_prompt = "You must response commit message in chinese。\n" if language == "zh" else ""
|
||||
prompt = (
|
||||
PROMPT_COMMIT_MESSAGE_BY_DIFF_USER_INPUT.replace("{__DIFF__}", f"{diff}")
|
||||
.replace("{__USER_INPUT__}", f"{user_input + language_prompt}")
|
||||
.replace("{__ISSUE__}", f"{issue}")
|
||||
)
|
||||
|
||||
model_token_limit_error = (
|
||||
diff_too_large_message_en if language == "en" else diff_too_large_message_zh
|
||||
)
|
||||
if len(str(prompt)) > COMMIT_PROMPT_LIMIT_SIZE:
|
||||
print(model_token_limit_error, flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
response = chat_completion_stream(messages, prompt_commit_message_by_diff_user_input_llm_config)
|
||||
|
||||
if (
|
||||
not response["content"]
|
||||
and response.get("error", None)
|
||||
and f"{response['error']}".find("This model's maximum context length is") > 0
|
||||
):
|
||||
print(model_token_limit_error)
|
||||
sys.exit(0)
|
||||
|
||||
assert_value(not response["content"], response.get("error", ""))
|
||||
response["content"] = extract_markdown_block(response["content"])
|
||||
return response
|
||||
|
||||
|
||||
def display_commit_message_and_commit(commit_message):
|
||||
"""
|
||||
展示提交信息并提交。
|
||||
|
||||
Args:
|
||||
commit_message: 提交信息。
|
||||
|
||||
Returns:
|
||||
None。
|
||||
|
||||
"""
|
||||
text_editor = TextEditor(commit_message, submit_button_name="Commit")
|
||||
text_editor.render()
|
||||
|
||||
new_commit_message = text_editor.new_text
|
||||
if not new_commit_message:
|
||||
return None
|
||||
return subprocess_check_output(["git", "commit", "-m", new_commit_message])
|
||||
|
||||
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "body": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
def check_git_installed():
|
||||
try:
|
||||
subprocess.run(
|
||||
["git", "--version"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("Git is not installed on your system.", file=sys.stderr, flush=True)
|
||||
except FileNotFoundError:
|
||||
print("Git is not installed on your system.", file=sys.stderr, flush=True)
|
||||
except Exception:
|
||||
print("Git is not installed on your system.", file=sys.stderr, flush=True)
|
||||
return False
|
||||
|
||||
|
||||
def ask_for_push():
|
||||
"""
|
||||
询问用户是否要推送(push)更改到远程仓库
|
||||
|
||||
Returns:
|
||||
bool: 用户是否选择推送
|
||||
"""
|
||||
|
||||
print(
|
||||
"Step 3/3: Would you like to push your commit to the remote repository?",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
button = Button(["Yes, push now", "No, I'll push later"])
|
||||
button.render()
|
||||
|
||||
return button.clicked == 0 # 如果用户点击第一个按钮(Yes),则返回True
|
||||
|
||||
|
||||
def push_changes():
|
||||
"""
|
||||
推送更改到远程仓库
|
||||
|
||||
Returns:
|
||||
bool: 推送是否成功
|
||||
"""
|
||||
try:
|
||||
current_branch = get_current_branch()
|
||||
if not current_branch:
|
||||
print(
|
||||
"Could not determine current branch. Push failed.",
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
|
||||
print(f"Pushing changes to origin/{current_branch}...", end="\n\n", flush=True)
|
||||
result = subprocess_run(
|
||||
["git", "push", "origin", current_branch],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
print(f"Push failed: {result.stderr}", end="\n\n", flush=True)
|
||||
return False
|
||||
print("Push completed successfully.", end="\n\n", flush=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Push failed: {str(e)}", end="\n\n", file=sys.stderr, flush=True)
|
||||
return False
|
||||
except Exception as e:
|
||||
print(
|
||||
f"An unexpected error occurred: {str(e)}",
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
def get_selected_issue_ids():
|
||||
"""
|
||||
获取用户选中的issue id
|
||||
|
||||
Returns:
|
||||
list: 用户选中的issue id列表
|
||||
"""
|
||||
name = get_github_username()
|
||||
issue_repo = get_github_repo(True)
|
||||
issues = get_github_repo_issues(issue_repo, assignee=name, state="open")
|
||||
if issues:
|
||||
checkbox = Checkbox(
|
||||
[f"#{issue['number']}: {issue['title']}" for issue in issues],
|
||||
title="Select the issues you want to close",
|
||||
)
|
||||
checkbox.render()
|
||||
return [issues[idx]["number"] for idx in checkbox.selections]
|
||||
|
||||
|
||||
def main():
|
||||
global language
|
||||
try:
|
||||
print("Let's follow the steps below.\n\n")
|
||||
# Ensure enough command line arguments are provided
|
||||
if len(sys.argv) < 2:
|
||||
print(
|
||||
"Usage: python script.py <user_input> <language>",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
sys.exit(-1)
|
||||
|
||||
user_input = sys.argv[1]
|
||||
language = "english"
|
||||
if len(sys.argv) > 2:
|
||||
language = sys.argv[2]
|
||||
|
||||
if not check_git_installed():
|
||||
sys.exit(-1)
|
||||
|
||||
print(
|
||||
"Step 1/3: Select the files you've changed that you wish to include in this commit, "
|
||||
"then click 'Submit'.",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
)
|
||||
modified_files, staged_files = get_modified_files()
|
||||
if len(modified_files) == 0:
|
||||
print("No files to commit.", file=sys.stderr, flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
selected_files = get_marked_files(modified_files, staged_files)
|
||||
if not selected_files:
|
||||
print("No files selected, commit aborted.")
|
||||
return
|
||||
|
||||
rebuild_stage_list(selected_files)
|
||||
|
||||
print(
|
||||
"Step 2/3: Review the commit message I've drafted for you. "
|
||||
"Edit it below if needed. Then click 'Commit' to proceed with "
|
||||
"the commit using this message.",
|
||||
end="\n\n",
|
||||
flush=True,
|
||||
)
|
||||
diff = get_diff()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
issue = str(get_issue_json(issue_id))
|
||||
if branch_name:
|
||||
user_input += "\ncurrent repo branch name is:" + branch_name
|
||||
commit_message = generate_commit_message_base_diff(user_input, diff, issue)
|
||||
|
||||
# TODO
|
||||
# remove Closes #IssueNumber in commit message
|
||||
commit_message["content"] = (
|
||||
commit_message["content"]
|
||||
.replace("Closes #IssueNumber", "")
|
||||
.replace("No specific issue to close", "")
|
||||
.replace("No specific issue mentioned.", "")
|
||||
)
|
||||
# add closes #IssueNumber in commit message from issues from user selected
|
||||
issue_ids = get_selected_issue_ids()
|
||||
if issue_ids:
|
||||
issue_repo = get_github_repo(True)
|
||||
owner_repo = get_github_repo()
|
||||
closes_issue_contents = []
|
||||
for issue_id in issue_ids:
|
||||
closes_issue_contents.append(
|
||||
f"#{issue_id}" if owner_repo == issue_repo else f"{issue_repo}#{issue_id}"
|
||||
)
|
||||
commit_message["content"] += f"\n\nCloses {', '.join(closes_issue_contents)}"
|
||||
commit_result = display_commit_message_and_commit(commit_message["content"])
|
||||
if not commit_result:
|
||||
print("Commit aborted.", flush=True)
|
||||
else:
|
||||
# 添加推送步骤
|
||||
if ask_for_push():
|
||||
if not push_changes():
|
||||
print("Push failed.", flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
print("Commit completed.", flush=True)
|
||||
sys.exit(0)
|
||||
except Exception as err:
|
||||
print("Exception:", err, file=sys.stderr, flush=True)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,45 +0,0 @@
|
||||
Objective:** Generate a commit message that succinctly describes the codebase changes reflected in the provided diff, while incorporating any extra context or guidance from the user.
|
||||
|
||||
**Commit Message Structure:**
|
||||
1. **Title Line:** Choose a type such as `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, and so on, and couple it with a succinct title. Use the format: `type: Title`. Only one title line is permissible.
|
||||
2. **Summary:** Summarize all adjustments concisely within a maximum of three detailed message lines. Prefix each line with a \"-\".
|
||||
3. **Closing Reference (Conditional):** Include the line `Closes #IssueNumber` only if a specific, relevant issue number has been mentioned in the user input.
|
||||
|
||||
**Response Format:**
|
||||
Response should be in the following markdown codeblock format:
|
||||
```commit
|
||||
type: Title
|
||||
|
||||
- Detail message line 1
|
||||
- Detail message line 2
|
||||
- Detail message line 3
|
||||
```
|
||||
Only output the commit message codeblock, don't include any other text.
|
||||
|
||||
**Constraints:**
|
||||
- Exclude markdown code block indicators (```) and the placeholder \"commit_message\" from your response.
|
||||
- Follow commit message best practices:
|
||||
- Limit the title length to 50 characters.
|
||||
- Limit each summary line to 72 characters.
|
||||
|
||||
**User Input:** `{__USER_INPUT__}`
|
||||
|
||||
Determine if `{__USER_INPUT__}` contains a reference to closing an issue. If so, include the closing reference in the commit message. Otherwise, exclude it.
|
||||
|
||||
**Code Changes:**
|
||||
```
|
||||
{__DIFF__}
|
||||
```
|
||||
|
||||
Related issue:
|
||||
{__ISSUE__}
|
||||
Utilize the provided format to craft a commit message that adheres to the stipulated criteria.
|
||||
|
||||
example output:
|
||||
```commit
|
||||
feature: add update user info API
|
||||
|
||||
- add post method api /user/update
|
||||
- implement update user info logic
|
||||
```
|
||||
|
@ -1,5 +0,0 @@
|
||||
description: '为你选定的代码变更生成格式规范的提交信息,并通过 Git 提交。如需要可包含对应 issue 编号(例如,输入“/commit to close #12”)'
|
||||
hint: to close Issue #issue_number
|
||||
input: optional
|
||||
steps:
|
||||
- run: $devchat_python $command_path/../commit.py "$input" "chinese"
|
@ -1,78 +0,0 @@
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Checkbox, Form, Radio, TextEditor
|
||||
|
||||
|
||||
def create_ui_objs(ui_decls, args):
|
||||
ui_objs = []
|
||||
editors = []
|
||||
for i, ui in enumerate(ui_decls):
|
||||
editor = ui[0](args[i])
|
||||
if ui[1]:
|
||||
# this is the title of UI object
|
||||
editors.append(ui[1])
|
||||
editors.append(editor)
|
||||
ui_objs.append(editor)
|
||||
return ui_objs, editors
|
||||
|
||||
|
||||
def edit_form(uis, args):
|
||||
ui_objs, editors = create_ui_objs(uis, args)
|
||||
form = Form(editors)
|
||||
form.render()
|
||||
|
||||
values = []
|
||||
for obj in ui_objs:
|
||||
if isinstance(obj, TextEditor):
|
||||
values.append(obj.new_text)
|
||||
elif isinstance(obj, Radio):
|
||||
values.append(obj.selection)
|
||||
else:
|
||||
# TODO
|
||||
pass
|
||||
return values
|
||||
|
||||
|
||||
def editor(description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
wrapper.uis.append((TextEditor, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def ui_edit(ui_type, description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
ui_type_class = {"editor": TextEditor, "radio": Radio, "checkbox": Checkbox}[ui_type]
|
||||
wrapper.uis.append((ui_type_class, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def assert_exit(condition, message, exit_code=-1):
|
||||
if condition:
|
||||
if exit_code == 0:
|
||||
print(message, end="\n\n", flush=True)
|
||||
else:
|
||||
print(message, end="\n\n", file=sys.stderr, flush=True)
|
||||
sys.exit(exit_code)
|
@ -1,19 +0,0 @@
|
||||
### config
|
||||
|
||||
配置GitHub工作流所需的设置。
|
||||
|
||||
#### 用途
|
||||
- 设置Issue仓库URL
|
||||
- 配置GitHub Token
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.config`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue仓库URL(可选)
|
||||
2. 输入GitHub Token
|
||||
3. 保存配置信息
|
||||
|
||||
#### 注意事项
|
||||
- GitHub Token应妥善保管,不要泄露
|
||||
- 配置信息将保存在本地文件中
|
@ -1,88 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import editor # noqa: E402
|
||||
|
||||
|
||||
def read_issue_url():
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "issue_repo" in config_data:
|
||||
return config_data["issue_repo"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_issue_url(issue_url):
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
# make dirs
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["issue_repo"] = issue_url
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_github_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "github_token" in config_data:
|
||||
return config_data["github_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_github_token(github_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["github_token"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
@editor(
|
||||
"Please specify the issue's repository, "
|
||||
"If the issue is within this repository, no need to specify. "
|
||||
"Otherwise, format as: username/repository-name"
|
||||
)
|
||||
@editor("Input your github TOKEN to access github api:")
|
||||
def edit_issue(issue_url, github_token):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
print("start config git settings ...", end="\n\n", flush=True)
|
||||
|
||||
issue_url = read_issue_url()
|
||||
github_token = read_github_token()
|
||||
|
||||
issue_url, github_token = edit_issue(issue_url, github_token)
|
||||
if issue_url:
|
||||
save_issue_url(issue_url)
|
||||
if github_token:
|
||||
save_github_token(github_token)
|
||||
else:
|
||||
print("Please specify the github token to access github api.")
|
||||
sys.exit(0)
|
||||
|
||||
print("config git settings successfully.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Config required settings for GIT workflows.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,587 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from lib.chatmark import TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
|
||||
def read_github_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "github_token" in config_data:
|
||||
return config_data["github_token"]
|
||||
|
||||
# ask user to input github token
|
||||
server_access_token_editor = TextEditor("", "Please input your GITHUB access TOKEN to access:")
|
||||
server_access_token_editor.render()
|
||||
|
||||
server_access_token = server_access_token_editor.new_text
|
||||
if not server_access_token:
|
||||
print("Please input your GITHUB access TOKEN to continue.")
|
||||
sys.exit(-1)
|
||||
return server_access_token
|
||||
|
||||
|
||||
current_repo_dir = None
|
||||
|
||||
|
||||
def get_current_repo():
|
||||
"""
|
||||
获取当前文件所在的仓库信息
|
||||
"""
|
||||
global current_repo_dir
|
||||
|
||||
if not current_repo_dir:
|
||||
selected_data = IDEService().get_selected_range().dict()
|
||||
current_file = selected_data.get("abspath", None)
|
||||
if not current_file:
|
||||
return None
|
||||
current_dir = os.path.dirname(current_file)
|
||||
try:
|
||||
# 获取仓库根目录
|
||||
current_repo_dir = (
|
||||
subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=current_dir,
|
||||
)
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,可能不在git仓库中
|
||||
return None
|
||||
return current_repo_dir
|
||||
|
||||
|
||||
def subprocess_check_output(*popenargs, timeout=None, **kwargs):
|
||||
# 将 current_dir 添加到 kwargs 中的 cwd 参数
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_output
|
||||
return subprocess.check_output(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_run(
|
||||
*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs
|
||||
):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.run
|
||||
return subprocess.run(
|
||||
*popenargs,
|
||||
input=input,
|
||||
capture_output=capture_output,
|
||||
timeout=timeout,
|
||||
check=check,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def subprocess_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.call
|
||||
return subprocess.call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_check_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_call
|
||||
return subprocess.check_call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
GITHUB_ACCESS_TOKEN = read_github_token()
|
||||
GITHUB_API_URL = "https://api.github.com"
|
||||
|
||||
|
||||
def create_issue(title, body):
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
data = {
|
||||
"title": title,
|
||||
"body": body,
|
||||
}
|
||||
issue_api_url = f"https://api.github.com/repos/{get_github_repo(True)}/issues"
|
||||
response = requests.post(issue_api_url, headers=headers, data=json.dumps(data))
|
||||
|
||||
if response.status_code == 201:
|
||||
print("Issue created successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to create issue: {response.content}", file=sys.stderr, end="\n\n")
|
||||
return None
|
||||
|
||||
|
||||
def update_issue_body(issue_url, issue_body):
|
||||
"""
|
||||
Update the body text of a GitHub issue.
|
||||
|
||||
:param issue_url: The API URL of the issue to update.
|
||||
:param issue_body: The new body text for the issue.
|
||||
"""
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
data = {
|
||||
"body": issue_body,
|
||||
}
|
||||
|
||||
issue_api_url = f"https://api.github.com/repos/{get_github_repo(True)}/issues"
|
||||
api_url = f"{issue_api_url}/{issue_url.split('/')[-1]}"
|
||||
response = requests.patch(api_url, headers=headers, data=json.dumps(data))
|
||||
|
||||
if response.status_code == 200:
|
||||
print("Issue updated successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to update issue: {response.status_code}")
|
||||
return None
|
||||
|
||||
|
||||
# parse sub tasks in issue body
|
||||
def parse_sub_tasks(body):
|
||||
sub_tasks = []
|
||||
lines = body.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("- ["):
|
||||
sub_tasks.append(line[2:])
|
||||
return sub_tasks
|
||||
|
||||
|
||||
def update_sub_tasks(body, tasks):
|
||||
# remove all existing tasks
|
||||
lines = body.split("\n")
|
||||
updated_body = "\n".join(line for line in lines if not line.startswith("- ["))
|
||||
|
||||
# add new tasks
|
||||
updated_body += "\n" + "\n".join(f"- {task}" for task in tasks)
|
||||
|
||||
return updated_body
|
||||
|
||||
|
||||
def update_task_issue_url(body, task, issue_url):
|
||||
# task is like:
|
||||
# [ ] task name
|
||||
# [x] task name
|
||||
# replace task name with issue url, like:
|
||||
# [ ] [task name](url)
|
||||
# [x] [task name](url)
|
||||
if task.find("] ") == -1:
|
||||
return None
|
||||
task = task[task.find("] ") + 2 :]
|
||||
return body.replace(task, f"[{task}]({issue_url})")
|
||||
|
||||
|
||||
def check_git_installed():
|
||||
"""
|
||||
Check if Git is installed on the local machine.
|
||||
|
||||
Tries to execute 'git --version' command to determine the presence of Git.
|
||||
|
||||
Returns:
|
||||
bool: True if Git is installed, False otherwise.
|
||||
"""
|
||||
try:
|
||||
subprocess_run(
|
||||
["git", "--version"],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("Git is not installed on your system.")
|
||||
return False
|
||||
|
||||
|
||||
def create_and_checkout_branch(branch_name):
|
||||
subprocess_run(["git", "checkout", "-b", branch_name], check=True)
|
||||
|
||||
|
||||
def is_issue_url(task):
|
||||
issue_url = f"https://github.com/{get_github_repo(True)}/issues"
|
||||
return task.strip().startswith(issue_url)
|
||||
|
||||
|
||||
def read_issue_by_url(issue_url):
|
||||
issue_number = issue_url.split("/")[-1]
|
||||
|
||||
# Construct the API endpoint URL
|
||||
issue_api_url = f"https://api.github.com/repos/{get_github_repo(True)}/issues"
|
||||
api_url = f"{issue_api_url}/{issue_number}"
|
||||
|
||||
# Send a GET request to the API endpoint
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_github_repo(issue_repo=False):
|
||||
try:
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path) and issue_repo:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "issue_repo" in config_data:
|
||||
print(
|
||||
"current issue repo:",
|
||||
config_data["issue_repo"],
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return config_data["issue_repo"]
|
||||
|
||||
# 使用git命令获取当前仓库的URL
|
||||
result = subprocess_check_output(
|
||||
["git", "remote", "get-url", "origin"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str并提取出仓库信息
|
||||
repo_url = result.decode("utf-8")
|
||||
# 假设repo_url的格式为:https://github.com/username/repo.git
|
||||
parts = repo_url.split("/")
|
||||
repo = parts[-1].replace(".git", "")
|
||||
username = parts[-2].split(":")[-1]
|
||||
github_repo = f"{username}/{repo}"
|
||||
IDEService().ide_logging("debug", f"current github repo: {github_repo}")
|
||||
return github_repo
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
# 获取当前分支名称
|
||||
def get_current_branch():
|
||||
try:
|
||||
# 使用git命令获取当前分支名称
|
||||
result = subprocess_check_output(
|
||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
current_branch = result.decode("utf-8")
|
||||
return current_branch
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
return None
|
||||
|
||||
|
||||
def get_parent_branch():
|
||||
current_branch = get_current_branch()
|
||||
if current_branch is None:
|
||||
return None
|
||||
try:
|
||||
# 使用git命令获取当前分支的父分支引用
|
||||
result = subprocess_check_output(
|
||||
["git", "rev-parse", "--abbrev-ref", f"{current_branch}@{1}"],
|
||||
stderr=subprocess.STDOUT,
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
parent_branch_ref = result.decode("utf-8")
|
||||
print("==>", parent_branch_ref)
|
||||
if parent_branch_ref == current_branch:
|
||||
# 如果父分支引用和当前分支相同,说明当前分支可能是基于一个没有父分支的提交创建的
|
||||
return None
|
||||
# 使用git命令获取父分支的名称
|
||||
result = subprocess_check_output(
|
||||
["git", "name-rev", "--name-only", "--exclude=tags/*", parent_branch_ref],
|
||||
stderr=subprocess.STDOUT,
|
||||
).strip()
|
||||
parent_branch_name = result.decode("utf-8")
|
||||
return parent_branch_name
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info(issue_id):
|
||||
# Construct the API endpoint URL
|
||||
issue_api_url = f"https://api.github.com/repos/{get_github_repo(True)}/issues"
|
||||
api_url = f"{issue_api_url}/{issue_id}"
|
||||
|
||||
# Send a GET request to the API endpoint
|
||||
headers = {
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info_by_url(issue_url):
|
||||
# get issue id from issue_url
|
||||
def get_issue_id(issue_url):
|
||||
# Extract the issue id from the issue_url
|
||||
issue_id = issue_url.split("/")[-1]
|
||||
return issue_id
|
||||
|
||||
return get_issue_info(get_issue_id(issue_url))
|
||||
|
||||
|
||||
# 获取当前分支自从与base_branch分叉以来的历史提交信息
|
||||
def get_commit_messages(base_branch):
|
||||
# 找到当前分支与base_branch的分叉点
|
||||
merge_base = subprocess_run(
|
||||
["git", "merge-base", "HEAD", base_branch],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查是否成功找到分叉点
|
||||
if merge_base.returncode != 0:
|
||||
raise RuntimeError(f"Error finding merge base: {merge_base.stderr.strip()}")
|
||||
|
||||
# 获取分叉点的提交哈希
|
||||
merge_base_commit = merge_base.stdout.strip()
|
||||
|
||||
# 获取从分叉点到当前分支的所有提交信息
|
||||
result = subprocess_run(
|
||||
["git", "log", f"{merge_base_commit}..HEAD"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查git log命令是否成功执行
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Error retrieving commit messages: {result.stderr.strip()}")
|
||||
|
||||
# 返回提交信息列表
|
||||
return result.stdout
|
||||
|
||||
|
||||
# 创建PR
|
||||
def create_pull_request(title, body, head, base, repo_name):
|
||||
url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls"
|
||||
print("url:", url, end="\n\n")
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"title": title, "body": body, "head": head, "base": base}
|
||||
response = requests.post(url, headers=headers, data=json.dumps(payload))
|
||||
if response.status_code == 201:
|
||||
return response.json()
|
||||
print(response.text, end="\n\n", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def run_command_with_retries(command, retries=3, delay=5):
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
subprocess_check_call(command)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Command failed: {e}")
|
||||
if attempt < retries - 1:
|
||||
print(f"Retrying... (attempt {attempt + 1}/{retries})")
|
||||
time.sleep(delay)
|
||||
else:
|
||||
print("All retries failed.")
|
||||
return False
|
||||
|
||||
|
||||
def check_unpushed_commits():
|
||||
try:
|
||||
# 获取当前分支的本地提交和远程提交的差异
|
||||
result = subprocess_check_output(["git", "cherry", "-v"]).decode("utf-8").strip()
|
||||
# 如果结果不为空,说明存在未push的提交
|
||||
return bool(result)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error checking for unpushed commits: {e}")
|
||||
return True
|
||||
|
||||
|
||||
def auto_push():
|
||||
# 获取当前分支名
|
||||
if not check_unpushed_commits():
|
||||
return True
|
||||
try:
|
||||
branch = (
|
||||
subprocess_check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error getting current branch: {e}")
|
||||
return False
|
||||
|
||||
# 检查当前分支是否有对应的远程分支
|
||||
remote_branch_exists = subprocess_call(["git", "ls-remote", "--exit-code", "origin", branch])
|
||||
|
||||
push_command = ["git", "push", "origin", branch]
|
||||
if remote_branch_exists == 0:
|
||||
# 如果存在远程分支,则直接push提交
|
||||
return run_command_with_retries(push_command)
|
||||
else:
|
||||
# 如果不存在远程分支,则发布并push提交
|
||||
push_command.append("-u")
|
||||
return run_command_with_retries(push_command)
|
||||
|
||||
|
||||
def get_recently_pr(repo):
|
||||
url = f"{GITHUB_API_URL}/repos/{repo}/pulls?state=open&sort=updated"
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
print("=>:", url)
|
||||
|
||||
branch_name = get_current_branch()
|
||||
|
||||
if response.status_code == 200:
|
||||
prs = response.json()
|
||||
for pr in prs:
|
||||
if pr["head"]["ref"] == branch_name:
|
||||
return pr
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def update_pr(pr_number, title, body, repo_name):
|
||||
url = f"{GITHUB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {"title": title, "body": body}
|
||||
response = requests.patch(url, headers=headers, data=json.dumps(payload))
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"PR updated successfully: {response.json()['html_url']}")
|
||||
return response.json()
|
||||
else:
|
||||
print("Failed to update PR.")
|
||||
return None
|
||||
|
||||
|
||||
def get_last_base_branch(default_branch):
|
||||
"""read last base branch from config file"""
|
||||
|
||||
def read_config_item(config_path, item):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get(item)
|
||||
return None
|
||||
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
last_base_branch = read_config_item(project_config_path, "last_base_branch")
|
||||
if last_base_branch:
|
||||
return last_base_branch
|
||||
return default_branch
|
||||
|
||||
|
||||
def save_last_base_branch(base_branch=None):
|
||||
"""save last base branch to config file"""
|
||||
|
||||
def save_config_item(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
if not base_branch:
|
||||
base_branch = get_current_branch()
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
save_config_item(project_config_path, "last_base_branch", base_branch)
|
||||
|
||||
|
||||
def get_github_username():
|
||||
url = f"{GITHUB_API_URL}/user"
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
return response.json()["login"]
|
||||
|
||||
|
||||
def get_github_repo_issues(
|
||||
owner_repo,
|
||||
milestone=None,
|
||||
state=None,
|
||||
assignee=None,
|
||||
creator=None,
|
||||
mentioned=None,
|
||||
labels=None,
|
||||
sort=None,
|
||||
direction=None,
|
||||
since=None,
|
||||
per_page=None,
|
||||
page=None,
|
||||
):
|
||||
url = f"{GITHUB_API_URL}/repos/{owner_repo}/issues"
|
||||
headers = {
|
||||
"Authorization": f"token {GITHUB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
params = {
|
||||
"milestone": milestone,
|
||||
"state": state,
|
||||
"assignee": assignee,
|
||||
"creator": creator,
|
||||
"mentioned": mentioned,
|
||||
"labels": labels,
|
||||
"sort": sort,
|
||||
"direction": direction,
|
||||
"since": since,
|
||||
"per_page": per_page,
|
||||
"page": page,
|
||||
}
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
return None
|
@ -1,19 +0,0 @@
|
||||
### list_issue_tasks
|
||||
|
||||
列出指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 查看Issue中的子任务
|
||||
- 跟踪任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取指定Issue的信息
|
||||
2. 解析Issue内容中的任务列表
|
||||
3. 显示任务列表
|
||||
|
||||
#### 注意事项
|
||||
- 需要提供有效的Issue URL
|
||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
@ -1,53 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and body using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed body for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output format: {{"title": "<title>", "body": "<body>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue body:")
|
||||
def edit_issue(title, body):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["body"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "body:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["html_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'List issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_branch
|
||||
|
||||
基于当前分支创建新分支并切换到新分支。
|
||||
|
||||
#### 用途
|
||||
- 快速创建新的功能或修复分支
|
||||
- 保持工作区隔离
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_branch <description>`
|
||||
|
||||
- description: 新分支的简短描述或相关Issue URL
|
||||
|
||||
#### 操作流程
|
||||
1. 生成多个分支名建议
|
||||
2. 用户选择或编辑分支名
|
||||
3. 创建新分支并切换
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支的更改已提交
|
||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
@ -1,95 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
create_and_checkout_branch,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"Give me 5 different git branch names, "
|
||||
"mainly hoping to express: {task}, "
|
||||
"Good branch name should looks like: <type>/<main content>,"
|
||||
"the final result is output in JSON format, "
|
||||
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_branch_name(task):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a branch name")
|
||||
def select_branch_name_ui(branch_names):
|
||||
pass
|
||||
|
||||
|
||||
def select_branch_name(branch_names):
|
||||
[branch_selection] = select_branch_name_ui(branch_names)
|
||||
assert_exit(branch_selection is None, "No branch selected.", exit_code=0)
|
||||
return branch_names[branch_selection]
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps(
|
||||
{"id": issue["number"], "title": issue["title"], "body": issue["body"]}
|
||||
), issue["number"]
|
||||
else:
|
||||
return task, None
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start create branch ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
assert_exit(
|
||||
not task,
|
||||
"You need input something about the new branch, or input a issue url.",
|
||||
exit_code=-1,
|
||||
)
|
||||
|
||||
# read issue by url
|
||||
task, issue_id = get_issue_or_task(task)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating branch names ...", end="\n\n", flush=True)
|
||||
branch_names = generate_branch_name(task=task)
|
||||
assert_exit(not branch_names, "Failed to generate branch names.", exit_code=-1)
|
||||
branch_names = branch_names["names"]
|
||||
for index, branch_name in enumerate(branch_names):
|
||||
if issue_id:
|
||||
branch_names[index] = f"{branch_name}-#{issue_id}"
|
||||
|
||||
# Select branch name
|
||||
selected_branch = select_branch_name(branch_names)
|
||||
|
||||
# save base branch name
|
||||
save_last_base_branch()
|
||||
|
||||
# create and checkout branch
|
||||
print(f"Creating and checking out branch: {selected_branch}")
|
||||
create_and_checkout_branch(selected_branch)
|
||||
print("Branch has create and checkout")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new branch based current branch, and checkout new branch.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_issue
|
||||
|
||||
创建新的GitHub Issue。
|
||||
|
||||
#### 用途
|
||||
- 快速创建标准格式的Issue
|
||||
- 记录任务、bug或功能请求
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_issue <description>`
|
||||
|
||||
- description: Issue的简短描述
|
||||
|
||||
#### 操作流程
|
||||
1. 基于描述生成Issue标题和正文
|
||||
2. 允许用户编辑Issue内容
|
||||
3. 创建GitHub Issue
|
||||
|
||||
#### 注意事项
|
||||
- 需要有创建Issue的权限
|
||||
- 生成的内容可能需要进一步完善
|
@ -1,52 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and body using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed body for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output as valid JSON format: {{"title": "<title>", "body": "<body> use \\n as new line flag."}} ' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue body:")
|
||||
def edit_issue(title, body):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["body"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "body:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["html_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new issue.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,94 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
|
||||
|
||||
from common_util import assert_exit, editor, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
create_issue,
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_task_issue_url,
|
||||
)
|
||||
|
||||
# Function to generate issue title and body using LLM
|
||||
PROMPT = (
|
||||
"Following is parent issue content:\n"
|
||||
"{issue_content}\n\n"
|
||||
"Based on the following issue task: {task}"
|
||||
"suggest a title and a detailed body for a GitHub issue:\n\n"
|
||||
'Output format: {{"title": "<title>", "body": "<body>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(issue_content, task):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue body:")
|
||||
def edit_issue(title, body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a task to create issue:")
|
||||
def select_task(tasks):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["number"],
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
issue_url = sys.argv[1]
|
||||
|
||||
old_issue = get_issue_json(issue_url)
|
||||
assert_exit(not old_issue, "Failed to retrieve issue with: {issue_url}", exit_code=-1)
|
||||
tasks = parse_sub_tasks(old_issue["body"])
|
||||
assert_exit(not tasks, "No tasks in issue body.")
|
||||
|
||||
# select task from tasks
|
||||
[task] = select_task(tasks)
|
||||
assert_exit(task is None, "No task selected.")
|
||||
task = tasks[task]
|
||||
print("task:", task, end="\n\n", flush=True)
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(issue_content=old_issue, task=task)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["body"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "body:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["html_url"], end="\n\n", flush=True)
|
||||
|
||||
# update issue task with new issue url
|
||||
new_body = update_task_issue_url(old_issue["body"], task, issue["html_url"])
|
||||
assert_exit(not new_body, f"{task} parse error.")
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue body.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new issue.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,22 +0,0 @@
|
||||
### new_pr
|
||||
|
||||
创建新的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 自动生成PR标题和描述
|
||||
- 简化代码审查流程
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_pr [additional_info]`
|
||||
|
||||
- additional_info: 可选的附加信息
|
||||
|
||||
#### 操作流程
|
||||
1. 获取当前分支信息和相关Issue
|
||||
2. 生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 创建Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支有未合并的更改
|
||||
- 需要有创建PR的权限
|
@ -1,120 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
create_pull_request,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_github_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and body based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR body as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"Other information:\n{user_input}\n\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "body": "pr body"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_message, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages, user_input):
|
||||
response = generate_pr_content_llm(
|
||||
issue=json.dumps(issue), commit_messages=commit_messages, user_input=user_input
|
||||
)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("body")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR body:")
|
||||
def edit_pr(title, body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "body": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start new_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_github_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
print("generating pr title and body ...", end="\n\n", flush=True)
|
||||
user_input = sys.argv[1]
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages, user_input)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = create_pull_request(pr_title, pr_body, branch_name, base_branch, repo_name)
|
||||
assert_exit(not pr, "Failed to create PR.", exit_code=-1)
|
||||
|
||||
print(f"PR created successfully: {pr['html_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new PR.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,22 +0,0 @@
|
||||
### update_issue_tasks
|
||||
|
||||
更新指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 添加、修改或删除Issue中的子任务
|
||||
- 更新任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_issue_tasks`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue URL
|
||||
2. 显示当前任务列表
|
||||
3. 用户输入更新建议
|
||||
4. 生成新的任务列表
|
||||
5. 允许用户编辑新任务列表
|
||||
6. 更新Issue内容
|
||||
|
||||
#### 注意事项
|
||||
- 需要有编辑Issue的权限
|
||||
- 小心不要删除或覆盖重要信息
|
@ -1,101 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_sub_tasks,
|
||||
)
|
||||
|
||||
TASKS_PROMPT = (
|
||||
"Following is my git issue content.\n"
|
||||
"{issue_data}\n\n"
|
||||
"Sub task in issue is like:- [ ] task name\n"
|
||||
"'[ ] task name' will be as sub task content\n\n"
|
||||
"Following is my idea to update sub tasks:\n"
|
||||
"{user_input}\n\n"
|
||||
"Please output all tasks in JSON format as:"
|
||||
'{{"tasks": ["[ ] task1", "[ ] task2"]}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=TASKS_PROMPT)
|
||||
def generate_issue_tasks(issue_data, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def to_task_str(tasks):
|
||||
task_str = ""
|
||||
for task in tasks:
|
||||
task_str += task + "\n"
|
||||
return task_str
|
||||
|
||||
|
||||
@editor("Edit issue old tasks:")
|
||||
@editor("Edit issue new tasks:")
|
||||
def edit_issue_tasks(old_tasks, new_tasks):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Input ISSUE url:")
|
||||
def input_issue_url(url):
|
||||
pass
|
||||
|
||||
|
||||
@editor("How to update tasks:")
|
||||
def update_tasks_input(user_input):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["number"],
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start issue tasks update ...", end="\n\n", flush=True)
|
||||
|
||||
[issue_url] = input_issue_url("")
|
||||
assert_exit(not issue_url, "No issue url.")
|
||||
print("issue url:", issue_url, end="\n\n", flush=True)
|
||||
|
||||
issue = get_issue_json(issue_url)
|
||||
old_tasks = parse_sub_tasks(issue["body"])
|
||||
|
||||
print(f"```tasks\n{to_task_str(old_tasks)}\n```", end="\n\n", flush=True)
|
||||
|
||||
[user_input] = update_tasks_input("")
|
||||
assert_exit(not user_input, "No user input")
|
||||
|
||||
new_tasks = generate_issue_tasks(issue_data=issue, user_input=user_input)
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new_tasks:", new_tasks, end="\n\n", flush=True)
|
||||
assert_exit(not new_tasks.get("tasks", []), "No new tasks.")
|
||||
print("new tasks:", to_task_str(new_tasks["tasks"]), end="\n\n", flush=True)
|
||||
new_tasks = new_tasks["tasks"]
|
||||
|
||||
[old_tasks, new_tasks] = edit_issue_tasks(to_task_str(old_tasks), to_task_str(new_tasks))
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new tasks:", new_tasks, end="\n\n", flush=True)
|
||||
|
||||
new_body = update_sub_tasks(issue["body"], new_tasks.split("\n"))
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue body.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,20 +0,0 @@
|
||||
### update_pr
|
||||
|
||||
更新现有的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 更新PR的标题和描述
|
||||
- 反映最新的代码变更
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_pr`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取最近的PR信息
|
||||
2. 重新生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 更新Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保有更新PR的权限
|
||||
- 更新前请确认是否有新的提交需要推送
|
@ -1,122 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import ( # noqa: E402
|
||||
chat_json,
|
||||
)
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_github_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
get_recently_pr,
|
||||
save_last_base_branch,
|
||||
update_pr,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and body based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR body as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "body": "pr body"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_messages):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages):
|
||||
response = generate_pr_content_llm(issue=json.dumps(issue), commit_messages=commit_messages)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("body")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR body:")
|
||||
def edit_pr(title, body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "body": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, "Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"html_url": issue["html_url"],
|
||||
"title": issue["title"],
|
||||
"body": issue["body"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start update_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_github_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
recent_pr = get_recently_pr(repo_name)
|
||||
assert_exit(not recent_pr, "Failed to get recent PR.", exit_code=-1)
|
||||
|
||||
print("generating pr title and body ...", end="\n\n", flush=True)
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = update_pr(recent_pr["number"], pr_title, pr_body, repo_name)
|
||||
assert_exit(not pr, "Failed to update PR.", exit_code=-1)
|
||||
|
||||
print(f"PR updated successfully: {pr['html_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update PR.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,24 +0,0 @@
|
||||
### code_task_summary
|
||||
|
||||
根据当前分支或指定的Issue,生成代码任务摘要。
|
||||
|
||||
#### 用途
|
||||
- 自动生成简洁的代码任务描述
|
||||
- 帮助开发者快速理解任务要点
|
||||
- 用于更新项目配置或文档
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.code_task_summary [issue_url]`
|
||||
|
||||
- 如不提供issue_url,将基于当前分支名称提取Issue信息
|
||||
- 如提供issue_url,将直接使用该Issue的内容
|
||||
|
||||
#### 操作流程
|
||||
1. 获取Issue信息
|
||||
2. 生成代码任务摘要
|
||||
3. 允许用户编辑摘要
|
||||
4. 更新项目配置文件
|
||||
|
||||
#### 注意事项
|
||||
- 确保Git仓库配置正确
|
||||
- 需要有效的GitHub Token以访问API
|
@ -1,124 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
)
|
||||
|
||||
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"You are a coding engineer, required to summarize the ISSUE description into a coding task description of no more than 50 words. \n" # noqa: E501
|
||||
"The ISSUE description is as follows: {issue_body}, please summarize the corresponding coding task description.\n" # noqa: E501
|
||||
'The coding task description should be output in JSON format, in the form of: {{"summary": "code task summary"}}\n' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_code_task_summary(issue_body):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit code task summary:")
|
||||
def edit_code_task_summary(task_summary):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps(
|
||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
||||
)
|
||||
else:
|
||||
return task
|
||||
|
||||
|
||||
def get_issue_json(issue_id, task):
|
||||
issue = {"id": "no issue id", "title": "", "description": task}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start update code task summary ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id, task)
|
||||
assert_exit(
|
||||
not issue["description"], f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1
|
||||
)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating code task summary ...", end="\n\n", flush=True)
|
||||
code_task_summary = generate_code_task_summary(issue_body=issue["description"])
|
||||
assert_exit(not code_task_summary, "Failed to generate code task summary.", exit_code=-1)
|
||||
assert_exit(
|
||||
not code_task_summary.get("summary", None),
|
||||
"Failed to generate code task summary, missing summary field in result.",
|
||||
exit_code=-1,
|
||||
)
|
||||
code_task_summary = code_task_summary["summary"]
|
||||
|
||||
# Select branch name
|
||||
code_task_summary = edit_code_task_summary(code_task_summary)
|
||||
assert_exit(not code_task_summary, "Failed to edit code task summary.", exit_code=-1)
|
||||
code_task_summary = code_task_summary[0]
|
||||
|
||||
# create and checkout branch
|
||||
print("Updating code task summary to config:")
|
||||
config_file = os.path.join(".chat", "complete.config")
|
||||
if os.path.exists(config_file):
|
||||
with open(config_file, "r") as f:
|
||||
config = json.load(f)
|
||||
config["taskDescription"] = code_task_summary
|
||||
else:
|
||||
config = {"taskDescription": code_task_summary}
|
||||
with open(config_file, "w") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
print("Code task summary has updated")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Generate code task summary.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1 +0,0 @@
|
||||
description: Root of gitlab commands.
|
@ -1,23 +0,0 @@
|
||||
### commit
|
||||
|
||||
自动生成提交信息并执行Git提交。
|
||||
|
||||
#### 用途
|
||||
- 生成规范的提交信息
|
||||
- 简化Git提交流程
|
||||
- 保持提交历史的一致性
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.commit [message]`
|
||||
|
||||
- message: 可选的用户输入,用于辅助生成提交信息
|
||||
|
||||
#### 操作流程
|
||||
1. 选择要提交的文件
|
||||
2. 生成提交信息
|
||||
3. 允许用户编辑提交信息
|
||||
4. 执行Git提交
|
||||
|
||||
#### 注意事项
|
||||
- 确保已选择需要提交的文件
|
||||
- 生成的提交信息可能需要进一步修改以符合项目规范
|
@ -1,6 +0,0 @@
|
||||
description: 'Writes a well-formatted commit message for selected code changes and commits them via Git. Include an issue number if desired (e.g., input "/commit to close #12").'
|
||||
hint: to close Issue #issue_number
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/commit.py "$input" "english"
|
@ -1,5 +0,0 @@
|
||||
description: '为你选定的代码变更生成格式规范的提交信息,并通过 Git 提交。如需要可包含对应 issue 编号(例如,输入“/commit to close #12”)'
|
||||
hint: to close Issue #issue_number
|
||||
input: optional
|
||||
steps:
|
||||
- run: $devchat_python $command_path/../commit.py "$input" "chinese"
|
@ -1,78 +0,0 @@
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Checkbox, Form, Radio, TextEditor
|
||||
|
||||
|
||||
def create_ui_objs(ui_decls, args):
|
||||
ui_objs = []
|
||||
editors = []
|
||||
for i, ui in enumerate(ui_decls):
|
||||
editor = ui[0](args[i])
|
||||
if ui[1]:
|
||||
# this is the title of UI object
|
||||
editors.append(ui[1])
|
||||
editors.append(editor)
|
||||
ui_objs.append(editor)
|
||||
return ui_objs, editors
|
||||
|
||||
|
||||
def edit_form(uis, args):
|
||||
ui_objs, editors = create_ui_objs(uis, args)
|
||||
form = Form(editors)
|
||||
form.render()
|
||||
|
||||
values = []
|
||||
for obj in ui_objs:
|
||||
if isinstance(obj, TextEditor):
|
||||
values.append(obj.new_text)
|
||||
elif isinstance(obj, Radio):
|
||||
values.append(obj.selection)
|
||||
else:
|
||||
# TODO
|
||||
pass
|
||||
return values
|
||||
|
||||
|
||||
def editor(description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
wrapper.uis.append((TextEditor, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def ui_edit(ui_type, description):
|
||||
def decorator_edit(func):
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
uis = wrapper.uis[::-1]
|
||||
return edit_form(uis, args)
|
||||
|
||||
if hasattr(func, "uis"):
|
||||
wrapper.uis = func.uis
|
||||
else:
|
||||
wrapper.uis = []
|
||||
ui_type_class = {"editor": TextEditor, "radio": Radio, "checkbox": Checkbox}[ui_type]
|
||||
wrapper.uis.append((ui_type_class, description))
|
||||
return wrapper
|
||||
|
||||
return decorator_edit
|
||||
|
||||
|
||||
def assert_exit(condition, message, exit_code=-1):
|
||||
if condition:
|
||||
if exit_code == 0:
|
||||
print(message, end="\n\n", flush=True)
|
||||
else:
|
||||
print(message, end="\n\n", file=sys.stderr, flush=True)
|
||||
sys.exit(exit_code)
|
@ -1,19 +0,0 @@
|
||||
### config
|
||||
|
||||
配置GitHub工作流所需的设置。
|
||||
|
||||
#### 用途
|
||||
- 设置Issue仓库URL
|
||||
- 配置GitHub Token
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.config`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue仓库URL(可选)
|
||||
2. 输入GitHub Token
|
||||
3. 保存配置信息
|
||||
|
||||
#### 注意事项
|
||||
- GitHub Token应妥善保管,不要泄露
|
||||
- 配置信息将保存在本地文件中
|
@ -1,88 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import editor # noqa: E402
|
||||
|
||||
|
||||
def read_issue_url():
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "git_issue_repo" in config_data:
|
||||
return config_data["git_issue_repo"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_issue_url(issue_url):
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
# make dirs
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["git_issue_repo"] = issue_url
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_gitlab_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "gitlab_token" in config_data:
|
||||
return config_data["gitlab_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_gitlab_token(github_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["gitlab_token"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
@editor(
|
||||
"Please specify the issue's repository, "
|
||||
"If the issue is within this repository, no need to specify. "
|
||||
"Otherwise, format as: username/repository-name"
|
||||
)
|
||||
@editor("Input your github TOKEN to access github api:")
|
||||
def edit_issue(issue_url, github_token):
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
print("start config git settings ...", end="\n\n", flush=True)
|
||||
|
||||
issue_url = read_issue_url()
|
||||
github_token = read_gitlab_token()
|
||||
|
||||
issue_url, github_token = edit_issue(issue_url, github_token)
|
||||
if issue_url:
|
||||
save_issue_url(issue_url)
|
||||
if github_token:
|
||||
save_gitlab_token(github_token)
|
||||
else:
|
||||
print("Please specify the github token to access github api.")
|
||||
sys.exit(0)
|
||||
|
||||
print("config git settings successfully.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Config required settings for GIT workflows.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,611 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
import requests
|
||||
|
||||
from lib.chatmark import TextEditor
|
||||
from lib.ide_service import IDEService
|
||||
|
||||
|
||||
def read_gitlab_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "gitlab_token" in config_data:
|
||||
return config_data["gitlab_token"]
|
||||
|
||||
# ask user to input gitlab token
|
||||
server_access_token_editor = TextEditor("", "Please input your GitLab access TOKEN to access:")
|
||||
server_access_token_editor.render()
|
||||
|
||||
server_access_token = server_access_token_editor.new_text
|
||||
if not server_access_token:
|
||||
print("Please input your GitLab access TOKEN to continue.")
|
||||
sys.exit(-1)
|
||||
return server_access_token
|
||||
|
||||
|
||||
current_repo_dir = None
|
||||
|
||||
|
||||
def get_current_repo():
|
||||
"""
|
||||
获取当前文件所在的仓库信息
|
||||
"""
|
||||
global current_repo_dir
|
||||
|
||||
if not current_repo_dir:
|
||||
selected_data = IDEService().get_selected_range().dict()
|
||||
current_file = selected_data.get("abspath", None)
|
||||
if not current_file:
|
||||
return None
|
||||
current_dir = os.path.dirname(current_file)
|
||||
try:
|
||||
# 获取仓库根目录
|
||||
current_repo_dir = (
|
||||
subprocess.check_output(
|
||||
["git", "rev-parse", "--show-toplevel"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
cwd=current_dir,
|
||||
)
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,可能不在git仓库中
|
||||
return None
|
||||
return current_repo_dir
|
||||
|
||||
|
||||
def subprocess_check_output(*popenargs, timeout=None, **kwargs):
|
||||
# 将 current_dir 添加到 kwargs 中的 cwd 参数
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_output
|
||||
return subprocess.check_output(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_run(
|
||||
*popenargs, input=None, capture_output=False, timeout=None, check=False, **kwargs
|
||||
):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.run
|
||||
return subprocess.run(
|
||||
*popenargs,
|
||||
input=input,
|
||||
capture_output=capture_output,
|
||||
timeout=timeout,
|
||||
check=check,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
def subprocess_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.call
|
||||
return subprocess.call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
def subprocess_check_call(*popenargs, timeout=None, **kwargs):
|
||||
current_repo = get_current_repo()
|
||||
if current_repo:
|
||||
kwargs["cwd"] = kwargs.get("cwd", current_repo)
|
||||
|
||||
# 调用 subprocess.check_call
|
||||
return subprocess.check_call(*popenargs, timeout=timeout, **kwargs)
|
||||
|
||||
|
||||
GITLAB_ACCESS_TOKEN = read_gitlab_token()
|
||||
GITLAB_API_URL = "https://gitlab.com/api/v4"
|
||||
|
||||
|
||||
def create_issue(title, description):
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
}
|
||||
project_id = get_gitlab_project_id()
|
||||
issue_api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues"
|
||||
response = requests.post(issue_api_url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 201:
|
||||
print("Issue created successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to create issue: {response.content}", file=sys.stderr, end="\n\n")
|
||||
return None
|
||||
|
||||
|
||||
def update_issue_body(issue_iid, issue_body):
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = {
|
||||
"description": issue_body,
|
||||
}
|
||||
|
||||
project_id = get_gitlab_project_id()
|
||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_iid}"
|
||||
response = requests.put(api_url, headers=headers, json=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("Issue updated successfully!")
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to update issue: {response.status_code}")
|
||||
return None
|
||||
|
||||
|
||||
def get_gitlab_project_id():
|
||||
try:
|
||||
result = subprocess_check_output(
|
||||
["git", "remote", "get-url", "origin"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
repo_url = result.decode("utf-8")
|
||||
print(f"Original repo URL: {repo_url}", file=sys.stderr)
|
||||
|
||||
if repo_url.startswith("git@"):
|
||||
# Handle SSH URL format
|
||||
parts = repo_url.split(":")
|
||||
project_path = parts[1].replace(".git", "")
|
||||
elif repo_url.startswith("https://"):
|
||||
# Handle HTTPS URL format
|
||||
parts = repo_url.split("/")
|
||||
project_path = "/".join(parts[3:]).replace(".git", "")
|
||||
else:
|
||||
raise ValueError(f"Unsupported Git URL format: {repo_url}")
|
||||
|
||||
print(f"Extracted project path: {project_path}", file=sys.stderr)
|
||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
||||
print(f"Encoded project path: {encoded_project_path}", file=sys.stderr)
|
||||
return encoded_project_path
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error executing git command: {e}", file=sys.stderr)
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error in get_gitlab_project_id: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
# parse sub tasks in issue description
|
||||
def parse_sub_tasks(description):
|
||||
sub_tasks = []
|
||||
lines = description.split("\n")
|
||||
for line in lines:
|
||||
if line.startswith("- ["):
|
||||
sub_tasks.append(line[2:])
|
||||
return sub_tasks
|
||||
|
||||
|
||||
def update_sub_tasks(description, tasks):
|
||||
# remove all existing tasks
|
||||
lines = description.split("\n")
|
||||
updated_body = "\n".join(line for line in lines if not line.startswith("- ["))
|
||||
|
||||
# add new tasks
|
||||
updated_body += "\n" + "\n".join(f"- {task}" for task in tasks)
|
||||
|
||||
return updated_body
|
||||
|
||||
|
||||
def update_task_issue_url(description, task, issue_url):
|
||||
# task is like:
|
||||
# [ ] task name
|
||||
# [x] task name
|
||||
# replace task name with issue url, like:
|
||||
# [ ] [task name](url)
|
||||
# [x] [task name](url)
|
||||
if task.find("] ") == -1:
|
||||
return None
|
||||
task = task[task.find("] ") + 2 :]
|
||||
return description.replace(task, f"[{task}]({issue_url})")
|
||||
|
||||
|
||||
def check_git_installed():
|
||||
"""
|
||||
Check if Git is installed on the local machine.
|
||||
|
||||
Tries to execute 'git --version' command to determine the presence of Git.
|
||||
|
||||
Returns:
|
||||
bool: True if Git is installed, False otherwise.
|
||||
"""
|
||||
try:
|
||||
subprocess_run(
|
||||
["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
print("Git is not installed on your system.")
|
||||
return False
|
||||
|
||||
|
||||
def create_and_checkout_branch(branch_name):
|
||||
subprocess_run(["git", "checkout", "-b", branch_name], check=True)
|
||||
|
||||
|
||||
def is_issue_url(task):
|
||||
task = task.strip()
|
||||
|
||||
# 使用正则表达式匹配 http 或 https 开头,issues/数字 结尾的 URL
|
||||
pattern = r"^(http|https)://.*?/issues/\d+$"
|
||||
|
||||
is_issue = bool(re.match(pattern, task))
|
||||
|
||||
# print(f"Task to check: {task}", file=sys.stderr)
|
||||
# print(f"Is issue URL: {is_issue}", file=sys.stderr)
|
||||
|
||||
return is_issue
|
||||
|
||||
|
||||
def read_issue_by_url(issue_url):
|
||||
# Extract the issue number and project path from the URL
|
||||
issue_url = issue_url.replace("/-/", "/")
|
||||
parts = issue_url.split("/")
|
||||
issue_number = parts[-1]
|
||||
project_path = "/".join(
|
||||
parts[3:-2]
|
||||
) # Assumes URL format: https://gitlab.com/project/path/-/issues/number
|
||||
|
||||
# URL encode the project path
|
||||
encoded_project_path = requests.utils.quote(project_path, safe="")
|
||||
|
||||
# Construct the API endpoint URL
|
||||
api_url = f"{GITLAB_API_URL}/projects/{encoded_project_path}/issues/{issue_number}"
|
||||
|
||||
# Send a GET request to the API endpoint
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Error fetching issue: {response.status_code}", file=sys.stderr)
|
||||
print(f"Response content: {response.text}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_gitlab_issue_repo(issue_repo=False):
|
||||
try:
|
||||
config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
if os.path.exists(config_path) and issue_repo:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
if "git_issue_repo" in config_data:
|
||||
issue_repo = requests.utils.quote(config_data["git_issue_repo"], safe="")
|
||||
print(
|
||||
"current issue repo:",
|
||||
config_data["git_issue_repo"],
|
||||
end="\n\n",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
return config_data["git_issue_repo"]
|
||||
|
||||
return get_gitlab_project_id()
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
# 获取当前分支名称
|
||||
def get_current_branch():
|
||||
try:
|
||||
# 使用git命令获取当前分支名称
|
||||
result = subprocess_check_output(
|
||||
["git", "branch", "--show-current"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
current_branch = result.decode("utf-8")
|
||||
return current_branch
|
||||
except subprocess.CalledProcessError:
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
return None
|
||||
|
||||
|
||||
def get_parent_branch():
|
||||
current_branch = get_current_branch()
|
||||
if current_branch is None:
|
||||
return None
|
||||
try:
|
||||
# 使用git命令获取当前分支的父分支引用
|
||||
result = subprocess_check_output(
|
||||
["git", "rev-parse", "--abbrev-ref", f"{current_branch}@{1}"], stderr=subprocess.STDOUT
|
||||
).strip()
|
||||
# 将结果从bytes转换为str
|
||||
parent_branch_ref = result.decode("utf-8")
|
||||
if parent_branch_ref == current_branch:
|
||||
# 如果父分支引用和当前分支相同,说明当前分支可能是基于一个没有父分支的提交创建的
|
||||
return None
|
||||
# 使用git命令获取父分支的名称
|
||||
result = subprocess_check_output(
|
||||
["git", "name-rev", "--name-only", "--exclude=tags/*", parent_branch_ref],
|
||||
stderr=subprocess.STDOUT,
|
||||
).strip()
|
||||
parent_branch_name = result.decode("utf-8")
|
||||
return parent_branch_name
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e)
|
||||
# 如果发生错误,打印错误信息
|
||||
return None
|
||||
except FileNotFoundError:
|
||||
# 如果未找到git命令,可能是没有安装git或者不在PATH中
|
||||
print("==> File not found...")
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info(issue_id):
|
||||
# 获取 GitLab 项目 ID
|
||||
project_id = get_gitlab_issue_repo()
|
||||
# 构造 GitLab API 端点 URL
|
||||
api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_id}"
|
||||
|
||||
# 发送 GET 请求到 API 端点
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(api_url, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
print(f"Failed to get issue info. Status code: {response.status_code}", file=sys.stderr)
|
||||
print(f"Response content: {response.text}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_issue_info_by_url(issue_url):
|
||||
# get issue id from issue_url
|
||||
def get_issue_id(issue_url):
|
||||
# Extract the issue id from the issue_url
|
||||
issue_id = issue_url.split("/")[-1]
|
||||
return issue_id
|
||||
|
||||
return get_issue_info(get_issue_id(issue_url))
|
||||
|
||||
|
||||
# 获取当前分支自从与base_branch分叉以来的历史提交信息
|
||||
def get_commit_messages(base_branch):
|
||||
# 找到当前分支与base_branch的分叉点
|
||||
merge_base = subprocess_run(
|
||||
["git", "merge-base", "HEAD", base_branch],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查是否成功找到分叉点
|
||||
if merge_base.returncode != 0:
|
||||
raise RuntimeError(f"Error finding merge base: {merge_base.stderr.strip()}")
|
||||
|
||||
# 获取分叉点的提交哈希
|
||||
merge_base_commit = merge_base.stdout.strip()
|
||||
|
||||
# 获取从分叉点到当前分支的所有提交信息
|
||||
result = subprocess_run(
|
||||
["git", "log", f"{merge_base_commit}..HEAD"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# 检查git log命令是否成功执行
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(f"Error retrieving commit messages: {result.stderr.strip()}")
|
||||
|
||||
# 返回提交信息列表
|
||||
return result.stdout
|
||||
|
||||
|
||||
# 创建PR
|
||||
def create_pull_request(title, description, source_branch, target_branch, project_id):
|
||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests"
|
||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"source_branch": source_branch,
|
||||
"target_branch": target_branch,
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
if response.status_code == 201:
|
||||
response_json = response.json()
|
||||
return response_json
|
||||
|
||||
print(response.text, end="\n\n", file=sys.stderr)
|
||||
return None
|
||||
|
||||
|
||||
def get_recently_mr(project_id):
|
||||
project_id = requests.utils.quote(project_id, safe="")
|
||||
url = (
|
||||
f"{GITLAB_API_URL}/projects/{project_id}/"
|
||||
"merge_requests?state=opened&order_by=updated_at&sort=desc"
|
||||
)
|
||||
headers = {
|
||||
"Private-Token": GITLAB_ACCESS_TOKEN,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
branch_name = get_current_branch()
|
||||
|
||||
if response.status_code == 200:
|
||||
mrs = response.json()
|
||||
for mr in mrs:
|
||||
if mr["source_branch"] == branch_name:
|
||||
return mr
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def run_command_with_retries(command, retries=3, delay=5):
|
||||
for attempt in range(retries):
|
||||
try:
|
||||
subprocess_check_call(command)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Command failed: {e}")
|
||||
if attempt < retries - 1:
|
||||
print(f"Retrying... (attempt {attempt + 1}/{retries})")
|
||||
time.sleep(delay)
|
||||
else:
|
||||
print("All retries failed.")
|
||||
return False
|
||||
|
||||
|
||||
def update_mr(project_id, mr_iid, title, description):
|
||||
project_id = requests.utils.quote(project_id, safe="")
|
||||
url = f"{GITLAB_API_URL}/projects/{project_id}/merge_requests/{mr_iid}"
|
||||
headers = {"Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json"}
|
||||
payload = {"title": title, "description": description}
|
||||
response = requests.put(url, headers=headers, json=payload)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"MR updated successfully: {response.json()['web_url']}")
|
||||
return response.json()
|
||||
else:
|
||||
print("Failed to update MR.")
|
||||
return None
|
||||
|
||||
|
||||
def check_unpushed_commits():
|
||||
try:
|
||||
# 获取当前分支的本地提交和远程提交的差异
|
||||
result = subprocess_check_output(["git", "cherry", "-v"]).decode("utf-8").strip()
|
||||
# 如果结果不为空,说明存在未push的提交
|
||||
return bool(result)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error checking for unpushed commits: {e}")
|
||||
return True
|
||||
|
||||
|
||||
def auto_push():
|
||||
# 获取当前分支名
|
||||
if not check_unpushed_commits():
|
||||
return True
|
||||
try:
|
||||
branch = (
|
||||
subprocess_check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"])
|
||||
.strip()
|
||||
.decode("utf-8")
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error getting current branch: {e}")
|
||||
return False
|
||||
|
||||
# 检查当前分支是否有对应的远程分支
|
||||
remote_branch_exists = subprocess_call(["git", "ls-remote", "--exit-code", "origin", branch])
|
||||
|
||||
push_command = ["git", "push", "origin", branch]
|
||||
if remote_branch_exists == 0:
|
||||
# 如果存在远程分支,则直接push提交
|
||||
return run_command_with_retries(push_command)
|
||||
else:
|
||||
# 如果不存在远程分支,则发布并push提交
|
||||
push_command.append("-u")
|
||||
return run_command_with_retries(push_command)
|
||||
|
||||
|
||||
def get_recently_pr(repo):
|
||||
url = f"{GITLAB_API_URL}/repos/{repo}/pulls?state=open&sort=updated"
|
||||
headers = {
|
||||
"Authorization": f"token {GITLAB_ACCESS_TOKEN}",
|
||||
"Accept": "application/vnd.github.v3+json",
|
||||
}
|
||||
response = requests.get(url, headers=headers)
|
||||
|
||||
branch_name = get_current_branch()
|
||||
|
||||
if response.status_code == 200:
|
||||
prs = response.json()
|
||||
for pr in prs:
|
||||
if pr["head"]["ref"] == branch_name:
|
||||
return pr
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def update_pr(pr_number, title, description, repo_name):
|
||||
url = f"{GITLAB_API_URL}/repos/{repo_name}/pulls/{pr_number}"
|
||||
headers = {"Authorization": f"token {GITLAB_ACCESS_TOKEN}", "Content-Type": "application/json"}
|
||||
payload = {"title": title, "description": description}
|
||||
response = requests.patch(url, headers=headers, data=json.dumps(payload))
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"PR updated successfully: {response.json()['web_url']}")
|
||||
return response.json()
|
||||
else:
|
||||
print("Failed to update PR.")
|
||||
return None
|
||||
|
||||
|
||||
def get_last_base_branch(default_branch):
|
||||
"""read last base branch from config file"""
|
||||
|
||||
def read_config_item(config_path, item):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get(item)
|
||||
return None
|
||||
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
last_base_branch = read_config_item(project_config_path, "last_base_branch")
|
||||
if last_base_branch:
|
||||
return last_base_branch
|
||||
return default_branch
|
||||
|
||||
|
||||
def save_last_base_branch(base_branch=None):
|
||||
"""save last base branch to config file"""
|
||||
|
||||
def save_config_item(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
if not base_branch:
|
||||
base_branch = get_current_branch()
|
||||
project_config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json")
|
||||
save_config_item(project_config_path, "last_base_branch", base_branch)
|
@ -1,19 +0,0 @@
|
||||
### list_issue_tasks
|
||||
|
||||
列出指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 查看Issue中的子任务
|
||||
- 跟踪任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.list_issue_tasks <issue_url>`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取指定Issue的信息
|
||||
2. 解析Issue内容中的任务列表
|
||||
3. 显示任务列表
|
||||
|
||||
#### 注意事项
|
||||
- 需要提供有效的Issue URL
|
||||
- 任务应以特定格式在Issue中列出(如: - [ ] 任务描述)
|
@ -1,53 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'List issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_branch
|
||||
|
||||
基于当前分支创建新分支并切换到新分支。
|
||||
|
||||
#### 用途
|
||||
- 快速创建新的功能或修复分支
|
||||
- 保持工作区隔离
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_branch <description>`
|
||||
|
||||
- description: 新分支的简短描述或相关Issue URL
|
||||
|
||||
#### 操作流程
|
||||
1. 生成多个分支名建议
|
||||
2. 用户选择或编辑分支名
|
||||
3. 创建新分支并切换
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支的更改已提交
|
||||
- 如提供Issue URL,会自动关联Issue编号到分支名
|
@ -1,95 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from devchat.llm import chat_json
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
check_git_installed,
|
||||
create_and_checkout_branch,
|
||||
is_issue_url,
|
||||
read_issue_by_url,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
# Function to generate a random branch name
|
||||
PROMPT = (
|
||||
"Give me 5 different git branch names, "
|
||||
"mainly hoping to express: {task}, "
|
||||
"Good branch name should looks like: <type>/<main content>,"
|
||||
"the final result is output in JSON format, "
|
||||
'as follows: {{"names":["name1", "name2", .. "name5"]}}\n'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_branch_name(task):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a branch name")
|
||||
def select_branch_name_ui(branch_names):
|
||||
pass
|
||||
|
||||
|
||||
def select_branch_name(branch_names):
|
||||
[branch_selection] = select_branch_name_ui(branch_names)
|
||||
assert_exit(branch_selection is None, "No branch selected.", exit_code=0)
|
||||
return branch_names[branch_selection]
|
||||
|
||||
|
||||
def get_issue_or_task(task):
|
||||
if is_issue_url(task):
|
||||
issue = read_issue_by_url(task.strip())
|
||||
assert_exit(not issue, "Failed to read issue.", exit_code=-1)
|
||||
|
||||
return json.dumps(
|
||||
{"id": issue["iid"], "title": issue["title"], "description": issue["description"]}
|
||||
), issue["iid"]
|
||||
else:
|
||||
return task, None
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("Start create branch ...", end="\n\n", flush=True)
|
||||
|
||||
is_git_installed = check_git_installed()
|
||||
assert_exit(not is_git_installed, "Git is not installed.", exit_code=-1)
|
||||
|
||||
task = sys.argv[1]
|
||||
assert_exit(
|
||||
not task,
|
||||
"You need input something about the new branch, or input a issue url.",
|
||||
exit_code=-1,
|
||||
)
|
||||
|
||||
# read issue by url
|
||||
task, issue_id = get_issue_or_task(task)
|
||||
|
||||
# Generate 5 branch names
|
||||
print("Generating branch names ...", end="\n\n", flush=True)
|
||||
branch_names = generate_branch_name(task=task)
|
||||
assert_exit(not branch_names, "Failed to generate branch names.", exit_code=-1)
|
||||
branch_names = branch_names["names"]
|
||||
for index, branch_name in enumerate(branch_names):
|
||||
if issue_id:
|
||||
branch_names[index] = f"{branch_name}-#{issue_id}"
|
||||
|
||||
# Select branch name
|
||||
selected_branch = select_branch_name(branch_names)
|
||||
|
||||
# save base branch name
|
||||
save_last_base_branch()
|
||||
|
||||
# create and checkout branch
|
||||
print(f"Creating and checking out branch: {selected_branch}")
|
||||
create_and_checkout_branch(selected_branch)
|
||||
print("Branch has create and checkout")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new branch based current branch, and checkout new branch.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,21 +0,0 @@
|
||||
### new_issue
|
||||
|
||||
创建新的GitHub Issue。
|
||||
|
||||
#### 用途
|
||||
- 快速创建标准格式的Issue
|
||||
- 记录任务、bug或功能请求
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_issue <description>`
|
||||
|
||||
- description: Issue的简短描述
|
||||
|
||||
#### 操作流程
|
||||
1. 基于描述生成Issue标题和正文
|
||||
2. 允许用户编辑Issue内容
|
||||
3. 创建GitHub Issue
|
||||
|
||||
#### 注意事项
|
||||
- 需要有创建Issue的权限
|
||||
- 生成的内容可能需要进一步完善
|
@ -1,52 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import create_issue # noqa: E402
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Based on the following description, "
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
"Description: {description}\n\n"
|
||||
'Output as valid JSON format: {{"title": "<title>", "description": "<description> use \\n as new line flag."}} ' # noqa: E501
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(description):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
description = sys.argv[1]
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(description=description)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new issue.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,94 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
|
||||
|
||||
from common_util import assert_exit, editor, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
create_issue,
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_task_issue_url,
|
||||
)
|
||||
|
||||
# Function to generate issue title and description using LLM
|
||||
PROMPT = (
|
||||
"Following is parent issue content:\n"
|
||||
"{issue_content}\n\n"
|
||||
"Based on the following issue task: {task}"
|
||||
"suggest a title and a detailed description for a GitHub issue:\n\n"
|
||||
'Output format: {{"title": "<title>", "description": "<description>"}} '
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_issue_content(issue_content, task):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Edit issue title:")
|
||||
@editor("Edit issue description:")
|
||||
def edit_issue(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="radio", description="Select a task to create issue:")
|
||||
def select_task(tasks):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["iid"],
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start new_issue ...", end="\n\n", flush=True)
|
||||
|
||||
assert_exit(len(sys.argv) < 2, "Missing argument.", exit_code=-1)
|
||||
issue_url = sys.argv[1]
|
||||
|
||||
old_issue = get_issue_json(issue_url)
|
||||
assert_exit(not old_issue, "Failed to retrieve issue with: {issue_url}", exit_code=-1)
|
||||
tasks = parse_sub_tasks(old_issue["get_issue_json"])
|
||||
assert_exit(not tasks, "No tasks in issue description.")
|
||||
|
||||
# select task from tasks
|
||||
[task] = select_task(tasks)
|
||||
assert_exit(task is None, "No task selected.")
|
||||
task = tasks[task]
|
||||
print("task:", task, end="\n\n", flush=True)
|
||||
|
||||
print("Generating issue content ...", end="\n\n", flush=True)
|
||||
issue_json_ob = generate_issue_content(issue_content=old_issue, task=task)
|
||||
assert_exit(not issue_json_ob, "Failed to generate issue content.", exit_code=-1)
|
||||
|
||||
issue_title, issue_body = edit_issue(issue_json_ob["title"], issue_json_ob["description"])
|
||||
assert_exit(not issue_title, "Issue creation cancelled.", exit_code=0)
|
||||
print("New Issue:", issue_title, "description:", issue_body, end="\n\n", flush=True)
|
||||
|
||||
print("Creating issue ...", end="\n\n", flush=True)
|
||||
issue = create_issue(issue_title, issue_body)
|
||||
assert_exit(not issue, "Failed to create issue.", exit_code=-1)
|
||||
print("New Issue:", issue["web_url"], end="\n\n", flush=True)
|
||||
|
||||
# update issue task with new issue url
|
||||
new_body = update_task_issue_url(old_issue["description"], task, issue["web_url"])
|
||||
assert_exit(not new_body, f"{task} parse error.")
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue description.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new issue.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,22 +0,0 @@
|
||||
### new_pr
|
||||
|
||||
创建新的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 自动生成PR标题和描述
|
||||
- 简化代码审查流程
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.new_pr [additional_info]`
|
||||
|
||||
- additional_info: 可选的附加信息
|
||||
|
||||
#### 操作流程
|
||||
1. 获取当前分支信息和相关Issue
|
||||
2. 生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 创建Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保当前分支有未合并的更改
|
||||
- 需要有创建PR的权限
|
@ -1,120 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
create_pull_request,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
save_last_base_branch,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and description based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"Other information:\n{user_input}\n\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "description": "pr description"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_message, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages, user_input):
|
||||
response = generate_pr_content_llm(
|
||||
issue=json.dumps(issue), commit_messages=commit_messages, user_input=user_input
|
||||
)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("description")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
||||
def edit_pr(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start new_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
||||
user_input = sys.argv[1]
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages, user_input)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = create_pull_request(pr_title, pr_body, branch_name, base_branch, repo_name)
|
||||
assert_exit(not pr, "Failed to create PR.", exit_code=-1)
|
||||
|
||||
print(f"PR created successfully: {pr['web_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Create new PR.'
|
||||
input: optional
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,22 +0,0 @@
|
||||
### update_issue_tasks
|
||||
|
||||
更新指定Issue中的任务列表。
|
||||
|
||||
#### 用途
|
||||
- 添加、修改或删除Issue中的子任务
|
||||
- 更新任务进度
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_issue_tasks`
|
||||
|
||||
#### 操作流程
|
||||
1. 输入Issue URL
|
||||
2. 显示当前任务列表
|
||||
3. 用户输入更新建议
|
||||
4. 生成新的任务列表
|
||||
5. 允许用户编辑新任务列表
|
||||
6. 更新Issue内容
|
||||
|
||||
#### 注意事项
|
||||
- 需要有编辑Issue的权限
|
||||
- 小心不要删除或覆盖重要信息
|
@ -1,101 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
from common_util import assert_exit, editor # noqa: E402
|
||||
from devchat.llm import chat_json # noqa: E402
|
||||
from git_api import ( # noqa: E402
|
||||
get_issue_info_by_url,
|
||||
parse_sub_tasks,
|
||||
update_issue_body,
|
||||
update_sub_tasks,
|
||||
)
|
||||
|
||||
TASKS_PROMPT = (
|
||||
"Following is my git issue content.\n"
|
||||
"{issue_data}\n\n"
|
||||
"Sub task in issue is like:- [ ] task name\n"
|
||||
"'[ ] task name' will be as sub task content\n\n"
|
||||
"Following is my idea to update sub tasks:\n"
|
||||
"{user_input}\n\n"
|
||||
"Please output all tasks in JSON format as:"
|
||||
'{{"tasks": ["[ ] task1", "[ ] task2"]}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=TASKS_PROMPT)
|
||||
def generate_issue_tasks(issue_data, user_input):
|
||||
pass
|
||||
|
||||
|
||||
def to_task_str(tasks):
|
||||
task_str = ""
|
||||
for task in tasks:
|
||||
task_str += task + "\n"
|
||||
return task_str
|
||||
|
||||
|
||||
@editor("Edit issue old tasks:")
|
||||
@editor("Edit issue new tasks:")
|
||||
def edit_issue_tasks(old_tasks, new_tasks):
|
||||
pass
|
||||
|
||||
|
||||
@editor("Input ISSUE url:")
|
||||
def input_issue_url(url):
|
||||
pass
|
||||
|
||||
|
||||
@editor("How to update tasks:")
|
||||
def update_tasks_input(user_input):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_url):
|
||||
issue = get_issue_info_by_url(issue_url)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_url}", exit_code=-1)
|
||||
return {
|
||||
"id": issue["iid"],
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
|
||||
|
||||
# Main function
|
||||
def main():
|
||||
print("start issue tasks update ...", end="\n\n", flush=True)
|
||||
|
||||
[issue_url] = input_issue_url("")
|
||||
assert_exit(not issue_url, "No issue url.")
|
||||
print("issue url:", issue_url, end="\n\n", flush=True)
|
||||
|
||||
issue = get_issue_json(issue_url)
|
||||
old_tasks = parse_sub_tasks(issue["description"])
|
||||
|
||||
print(f"```tasks\n{to_task_str(old_tasks)}\n```", end="\n\n", flush=True)
|
||||
|
||||
[user_input] = update_tasks_input("")
|
||||
assert_exit(not user_input, "No user input")
|
||||
|
||||
new_tasks = generate_issue_tasks(issue_data=issue, user_input=user_input)
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new_tasks:", new_tasks, end="\n\n", flush=True)
|
||||
assert_exit(not new_tasks.get("tasks", []), "No new tasks.")
|
||||
print("new tasks:", to_task_str(new_tasks["tasks"]), end="\n\n", flush=True)
|
||||
new_tasks = new_tasks["tasks"]
|
||||
|
||||
[old_tasks, new_tasks] = edit_issue_tasks(to_task_str(old_tasks), to_task_str(new_tasks))
|
||||
assert_exit(not new_tasks, "No new tasks.")
|
||||
print("new tasks:", new_tasks, end="\n\n", flush=True)
|
||||
|
||||
new_body = update_sub_tasks(issue["description"], new_tasks.split("\n"))
|
||||
new_issue = update_issue_body(issue_url, new_body)
|
||||
assert_exit(not new_issue, "Failed to update issue description.")
|
||||
|
||||
print("Issue tasks updated successfully!", end="\n\n", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update issue tasks.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,20 +0,0 @@
|
||||
### update_pr
|
||||
|
||||
更新现有的Pull Request。
|
||||
|
||||
#### 用途
|
||||
- 更新PR的标题和描述
|
||||
- 反映最新的代码变更
|
||||
|
||||
#### 使用方法
|
||||
执行命令: `/github.update_pr`
|
||||
|
||||
#### 操作流程
|
||||
1. 获取最近的PR信息
|
||||
2. 重新生成PR标题和描述
|
||||
3. 允许用户编辑PR内容
|
||||
4. 更新Pull Request
|
||||
|
||||
#### 注意事项
|
||||
- 确保有更新PR的权限
|
||||
- 更新前请确认是否有新的提交需要推送
|
@ -1,122 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".."))
|
||||
|
||||
|
||||
from common_util import assert_exit, ui_edit # noqa: E402
|
||||
from devchat.llm import ( # noqa: E402
|
||||
chat_json,
|
||||
)
|
||||
from git_api import ( # noqa: E402
|
||||
auto_push,
|
||||
get_commit_messages,
|
||||
get_current_branch,
|
||||
get_gitlab_issue_repo,
|
||||
get_issue_info,
|
||||
get_last_base_branch,
|
||||
get_recently_pr,
|
||||
save_last_base_branch,
|
||||
update_pr,
|
||||
)
|
||||
|
||||
|
||||
# 从分支名称中提取issue id
|
||||
def extract_issue_id(branch_name):
|
||||
if "#" in branch_name:
|
||||
return branch_name.split("#")[-1]
|
||||
return None
|
||||
|
||||
|
||||
# 使用LLM模型生成PR内容
|
||||
PROMPT = (
|
||||
"Create a pull request title and description based on "
|
||||
"the following issue and commit messages, if there is an "
|
||||
"issue, close that issue in PR description as <user>/<repo>#issue_id:\n"
|
||||
"Issue: {issue}\n"
|
||||
"Commits:\n{commit_messages}\n"
|
||||
"The response result should format as JSON object as following:\n"
|
||||
'{{"title": "pr title", "description": "pr description"}}'
|
||||
)
|
||||
|
||||
|
||||
@chat_json(prompt=PROMPT)
|
||||
def generate_pr_content_llm(issue, commit_messages):
|
||||
pass
|
||||
|
||||
|
||||
def generate_pr_content(issue, commit_messages):
|
||||
response = generate_pr_content_llm(issue=json.dumps(issue), commit_messages=commit_messages)
|
||||
assert_exit(not response, "Failed to generate PR content.", exit_code=-1)
|
||||
return response.get("title"), response.get("description")
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit PR title:")
|
||||
@ui_edit(ui_type="editor", description="Edit PR description:")
|
||||
def edit_pr(title, description):
|
||||
pass
|
||||
|
||||
|
||||
@ui_edit(ui_type="editor", description="Edit base branch:")
|
||||
def edit_base_branch(base_branch):
|
||||
pass
|
||||
|
||||
|
||||
def get_issue_json(issue_id):
|
||||
issue = {"id": "no issue id", "title": "", "description": ""}
|
||||
if issue_id:
|
||||
issue = get_issue_info(issue_id)
|
||||
assert_exit(not issue, f"Failed to retrieve issue with ID: {issue_id}", exit_code=-1)
|
||||
issue = {
|
||||
"id": issue_id,
|
||||
"web_url": issue["web_url"],
|
||||
"title": issue["title"],
|
||||
"description": issue["description"],
|
||||
}
|
||||
return issue
|
||||
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
print("start update_pr ...", end="\n\n", flush=True)
|
||||
|
||||
base_branch = get_last_base_branch("main")
|
||||
base_branch = edit_base_branch(base_branch)
|
||||
if isinstance(base_branch, list) and len(base_branch) > 0:
|
||||
base_branch = base_branch[0]
|
||||
save_last_base_branch(base_branch)
|
||||
|
||||
repo_name = get_gitlab_issue_repo()
|
||||
branch_name = get_current_branch()
|
||||
issue_id = extract_issue_id(branch_name)
|
||||
|
||||
# print basic info, repo_name, branch_name, issue_id
|
||||
print("repo name:", repo_name, end="\n\n")
|
||||
print("branch name:", branch_name, end="\n\n")
|
||||
print("issue id:", issue_id, end="\n\n")
|
||||
|
||||
issue = get_issue_json(issue_id)
|
||||
commit_messages = get_commit_messages(base_branch)
|
||||
|
||||
recent_pr = get_recently_pr(repo_name)
|
||||
assert_exit(not recent_pr, "Failed to get recent PR.", exit_code=-1)
|
||||
|
||||
print("generating pr title and description ...", end="\n\n", flush=True)
|
||||
pr_title, pr_body = generate_pr_content(issue, commit_messages)
|
||||
assert_exit(not pr_title, "Failed to generate PR content.", exit_code=-1)
|
||||
|
||||
pr_title, pr_body = edit_pr(pr_title, pr_body)
|
||||
assert_exit(not pr_title, "PR creation cancelled.", exit_code=0)
|
||||
|
||||
is_push_success = auto_push()
|
||||
assert_exit(not is_push_success, "Failed to push changes.", exit_code=-1)
|
||||
|
||||
pr = update_pr(recent_pr["iid"], pr_title, pr_body, repo_name)
|
||||
assert_exit(not pr, "Failed to update PR.", exit_code=-1)
|
||||
|
||||
print(f"PR updated successfully: {pr['web_url']}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,5 +0,0 @@
|
||||
description: 'Update PR.'
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py "$input"
|
@ -1,34 +0,0 @@
|
||||
# pr 命令
|
||||
|
||||
pr命令是一个用于处理Pull Requests (PRs)的主命令。它本身不执行具体操作,而是通过子命令来完成特定功能。
|
||||
|
||||
## 可用子命令
|
||||
|
||||
1. pr.review - 生成PR代码评审描述
|
||||
2. pr.improve - 生成PR的代码建议
|
||||
3. pr.describe - 生成PR描述
|
||||
4. pr.custom_suggestions - 生成PR的自定义代码建议
|
||||
|
||||
## 使用方法
|
||||
|
||||
要使用pr命令的功能,请使用以下格式调用相应的子命令:
|
||||
|
||||
/pr.<子命令> <PR_URL>
|
||||
|
||||
例如:
|
||||
- /pr.review https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.improve https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.describe https://github.com/devchat-ai/devchat/pull/301
|
||||
- /pr.custom_suggestions https://github.com/devchat-ai/devchat/pull/301
|
||||
|
||||
## 子命令说明
|
||||
|
||||
1. pr.review: 分析PR并生成代码评审描述,帮助审阅者快速了解PR的内容和影响。
|
||||
|
||||
2. pr.improve: 分析PR并提供代码改进建议,帮助开发者优化其代码。
|
||||
|
||||
3. pr.describe: 自动生成PR的描述,总结PR的主要变更和目的。
|
||||
|
||||
4. pr.custom_suggestions: 根据特定需求生成自定义的PR代码建议。
|
||||
|
||||
请根据您的具体需求选择适当的子命令。每个子命令都专注于PR处理的不同方面,帮助您更高效地管理和改进Pull Requests。
|
@ -1,196 +0,0 @@
|
||||
"""
|
||||
/pr.describe https://github.com/devchat-ai/devchat-vscode/pull/25
|
||||
"""
|
||||
# ruff: noqa: E402
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# add the current directory to the path
|
||||
# add new model configs to algo.MAX_TOKENS
|
||||
import pr_agent.algo as algo
|
||||
|
||||
from lib.ide_service import IDEService
|
||||
from merico.pr.config_util import get_model_max_input
|
||||
|
||||
algo.MAX_TOKENS["gpt-4-turbo-preview"] = 128000
|
||||
algo.MAX_TOKENS["claude-3-opus"] = 100000
|
||||
algo.MAX_TOKENS["claude-3-sonnet"] = 100000
|
||||
algo.MAX_TOKENS["claude-3-haiku"] = 100000
|
||||
algo.MAX_TOKENS["ERNIE-Bot-4.0"] = 8000
|
||||
algo.MAX_TOKENS["GLM-4"] = 8000
|
||||
algo.MAX_TOKENS["hzwxai/Mixtral-8x7B-Instruct-v0.1-GPTQ"] = 16000
|
||||
algo.MAX_TOKENS["minimax/abab6-chat"] = 8000
|
||||
algo.MAX_TOKENS["xinghuo-3.5"] = 8000
|
||||
algo.MAX_TOKENS["llama-2-70b-chat"] = 4000
|
||||
algo.MAX_TOKENS["togetherai/codellama/CodeLlama-70b-Instruct-hf"] = 4000
|
||||
algo.MAX_TOKENS["togetherai/mistralai/Mixtral-8x7B-Instruct-v0.1"] = 16000
|
||||
algo.MAX_TOKENS["text-embedding-ada-002"] = 8000
|
||||
algo.MAX_TOKENS["text-embedding-3-small"] = 8000
|
||||
algo.MAX_TOKENS["text-embedding-3-large"] = 8000
|
||||
algo.MAX_TOKENS["embedding-v1"] = 8000
|
||||
algo.MAX_TOKENS["embedding-2"] = 8000
|
||||
algo.MAX_TOKENS["togethercomputer/m2-bert-80M-2k-retrieval"] = 2048
|
||||
algo.MAX_TOKENS["togethercomputer/m2-bert-80M-8k-retrieval"] = 8192
|
||||
algo.MAX_TOKENS["togethercomputer/m2-bert-80M-32k-retrieval"] = 32768
|
||||
algo.MAX_TOKENS["WhereIsAI/UAE-Large-V1"] = 512
|
||||
algo.MAX_TOKENS["BAAI/bge-large-en-v1.5"] = 512
|
||||
algo.MAX_TOKENS["BAAI/bge-base-en-v1.5"] = 512
|
||||
algo.MAX_TOKENS["sentence-transformers/msmarco-bert-base-dot-v5"] = 512
|
||||
algo.MAX_TOKENS["bert-base-uncased"] = 512
|
||||
|
||||
current_model = os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")
|
||||
algo.MAX_TOKENS[current_model] = get_model_max_input(current_model)
|
||||
|
||||
|
||||
# add new git provider
|
||||
def get_git_provider():
|
||||
from pr_agent.config_loader import get_settings
|
||||
|
||||
_git_provider_old_ = get_settings().config.git_provider
|
||||
get_settings().config.git_provider = "devchat"
|
||||
provider = _get_git_provider_old()
|
||||
get_settings().config.git_provider = _git_provider_old_
|
||||
return provider
|
||||
|
||||
|
||||
import pr_agent.git_providers as git_providers
|
||||
|
||||
from merico.pr.providers.devchat_provider import DevChatProvider
|
||||
|
||||
git_providers._GIT_PROVIDERS["devchat"] = DevChatProvider
|
||||
_get_git_provider_old = git_providers.get_git_provider
|
||||
git_providers.get_git_provider = get_git_provider
|
||||
|
||||
|
||||
from pr_agent.cli import run
|
||||
from pr_agent.config_loader import get_settings
|
||||
|
||||
# mock logging method, to redirect log to IDE
|
||||
from pr_agent.log import inv_analytics_filter, setup_logger
|
||||
|
||||
|
||||
class CustomOutput:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def write(self, message):
|
||||
IDEService().ide_logging("info", message.strip())
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
log_level = os.environ.get("LOG_LEVEL", "INFO")
|
||||
logger = setup_logger(log_level)
|
||||
|
||||
logger.remove(None)
|
||||
logger.add(
|
||||
CustomOutput(),
|
||||
level=logging.DEBUG,
|
||||
format="{message}",
|
||||
colorize=False,
|
||||
filter=inv_analytics_filter,
|
||||
)
|
||||
|
||||
|
||||
from merico.pr.config_util import (
|
||||
get_gitlab_host,
|
||||
get_repo_type,
|
||||
read_review_inline_config,
|
||||
read_server_access_token_with_input,
|
||||
)
|
||||
from merico.pr.custom_suggestions_config import get_custom_suggestions_system_prompt
|
||||
|
||||
# set openai key and api base
|
||||
get_settings().set("OPENAI.KEY", os.environ.get("OPENAI_API_KEY", ""))
|
||||
get_settings().set("OPENAI.API_BASE", os.environ.get("OPENAI_API_BASE", ""))
|
||||
get_settings().set("LLM.CUSTOM_LLM_PROVIDER", "openai")
|
||||
|
||||
|
||||
# set github token
|
||||
access_token = read_server_access_token_with_input(sys.argv[1])
|
||||
if not access_token:
|
||||
print("Command has been canceled.", flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
repo_type = get_repo_type(sys.argv[1])
|
||||
IDEService().ide_logging("debug", f"repo type: {repo_type}")
|
||||
if repo_type == "github":
|
||||
get_settings().set("GITHUB.USER_TOKEN", access_token)
|
||||
elif repo_type == "gitlab":
|
||||
get_settings().set("GITLAB.PERSONAL_ACCESS_TOKEN", access_token)
|
||||
host = get_gitlab_host(sys.argv[1])
|
||||
if host:
|
||||
IDEService().ide_logging("debug", f"gitlab host: {host}")
|
||||
get_settings().set("GITLAB.URL", host)
|
||||
else:
|
||||
print(
|
||||
"Unsupported git hosting service, input pr url is:",
|
||||
sys.argv[1],
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# USER TOKEN
|
||||
|
||||
# set git provider, default is devchat
|
||||
# in devchat provider, we will create actual repo provider
|
||||
# get_settings().set("CONFIG.GIT_PROVIDER", "devchat")
|
||||
|
||||
# set model
|
||||
get_settings().set("CONFIG.MODEL", os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106"))
|
||||
get_settings().set("CONFIG.MODEL_TURBO", os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106"))
|
||||
get_settings().set("CONFIG.FALLBACK_MODELS", [os.environ.get("LLM_MODEL", "gpt-3.5-turbo-1106")])
|
||||
|
||||
# disable help text as default config
|
||||
get_settings().set("PR_REVIEWER.ENABLE_HELP_TEXT", False)
|
||||
get_settings().set("PR_DESCRIPTION.ENABLE_HELP_TEXT", False)
|
||||
get_settings().set("PR_DESCRIPTION.ENABLE_HELP_COMMENT", False)
|
||||
get_settings().set("PR_CODE_SUGGESTIONS.ENABLE_HELP_TEXT", False)
|
||||
get_settings().set("PR_TEST.ENABLE_HELP_TEXT", False)
|
||||
get_settings().set("CHECKS.ENABLE_HELP_TEXT", False)
|
||||
# get_settings().set("PR_CODE_SUGGESTIONS.SUMMARIZE", False)
|
||||
|
||||
|
||||
# handle custom suggestions command
|
||||
if sys.argv[2] == "custom_suggestions":
|
||||
get_settings().pr_code_suggestions_prompt.system = get_custom_suggestions_system_prompt()
|
||||
sys.argv[2] = "improve"
|
||||
|
||||
# get current language config
|
||||
language = IDEService().ide_language()
|
||||
language_prompt = "\n\n输出内容使用中文输出。\n" if language == "zh" else ""
|
||||
get_settings().pr_code_suggestions_prompt.system += language_prompt
|
||||
get_settings().pr_review_prompt.system += language_prompt
|
||||
get_settings().pr_description_prompt.system += language_prompt
|
||||
if read_review_inline_config():
|
||||
get_settings().pr_reviewer.inline_code_comments = True
|
||||
|
||||
# config for find similar issues
|
||||
get_settings().set("PR_SIMILAR_ISSUE.VECTORDB", "lancedb")
|
||||
get_settings().set("LANCEDB.URI", "data/lancedb")
|
||||
|
||||
# set git provider type, devchat provider will create actual repo provider based on this type
|
||||
pr_provider_type = get_repo_type(sys.argv[1])
|
||||
if not pr_provider_type:
|
||||
print(
|
||||
"Unsupported git hosting service, input pr url is:",
|
||||
sys.argv[1],
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
get_settings().set("CONFIG.GIT_PROVIDER", pr_provider_type)
|
||||
os.environ["CONFIG.GIT_PROVIDER_TYPE"] = pr_provider_type
|
||||
# os.environ['ENABLE_PUBLISH_LABELS'] = "1"
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.argv = [sys.executable, "--pr_url", sys.argv[1].strip(), sys.argv[2].strip()]
|
||||
run()
|
@ -1,6 +0,0 @@
|
||||
description: "pr command"
|
||||
workflow_python:
|
||||
env_name: devchat-pr-env4
|
||||
version: 3.11.0
|
||||
dependencies: requirements.txt
|
||||
help: README.md
|
@ -1,24 +0,0 @@
|
||||
# pr.config
|
||||
|
||||
**/pr.config命令用于配置Git工作流所需的设置。**
|
||||
|
||||
|
||||
该命令允许用户配置以下选项:
|
||||
|
||||
1. PR Review Inline: 启用或禁用PR内联评审功能。
|
||||
|
||||
使用方式:
|
||||
直接运行 /pr.config 命令,无需额外参数。
|
||||
|
||||
命令会引导用户通过交互式界面进行配置。用户可以选择是否启用PR内联评审功能。
|
||||
|
||||
|
||||
配置文件位置:
|
||||
全局配置文件保存在用户主目录下的 ~/.chat/.workflow_config.json
|
||||
|
||||
|
||||
注意:
|
||||
- 如需修改访问令牌或主机URL,请直接编辑配置文件。
|
||||
- 配置更改后会立即生效。
|
||||
|
||||
这个命令可以帮助用户快速设置Git工作流所需的重要配置项,提高使用效率。
|
@ -1,72 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.chatmark import Checkbox
|
||||
|
||||
# Configuration items
|
||||
CONFIG_ITEMS = {
|
||||
"pr_review_inline": "PR Review Inline Enabled",
|
||||
}
|
||||
|
||||
# Configuration file paths
|
||||
GLOBAL_CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".chat", ".workflow_config.json")
|
||||
|
||||
|
||||
def read_config(config_path, item):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
return config.get(item)
|
||||
return None
|
||||
|
||||
|
||||
def save_config(config_path, item, value):
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = json.load(f)
|
||||
else:
|
||||
config = {}
|
||||
|
||||
config[item] = value
|
||||
with open(config_path, "w", encoding="utf-8") as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
|
||||
def is_pre_review_inline_enabled(current_value=False):
|
||||
print("\n\nEnable PR Review Inline:\n\n")
|
||||
checkbox = Checkbox(
|
||||
[
|
||||
"PR Review Inline Enabled",
|
||||
],
|
||||
[current_value],
|
||||
)
|
||||
checkbox.render()
|
||||
|
||||
print(f"\n\ncheckbox.selections: {checkbox.selections}\n\n")
|
||||
if len(checkbox.selections) > 0:
|
||||
return True
|
||||
if checkbox.selections is None:
|
||||
return None
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
print("Starting configuration of workflow settings...", end="\n\n", flush=True)
|
||||
print(
|
||||
"If you want to change access token or host url, "
|
||||
"please edit the configuration file directly."
|
||||
)
|
||||
print("Configuration file is located at:", GLOBAL_CONFIG_PATH, end="\n\n", flush=True)
|
||||
|
||||
pr_review_inline_enable = read_config(GLOBAL_CONFIG_PATH, "pr_review_inline")
|
||||
|
||||
pr_review_inline_enable = is_pre_review_inline_enabled(pr_review_inline_enable or False)
|
||||
if pr_review_inline_enable is not None:
|
||||
save_config(GLOBAL_CONFIG_PATH, "pr_review_inline", pr_review_inline_enable)
|
||||
print("Workflow settings configuration successful.")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -1,4 +0,0 @@
|
||||
description: 'Config required settings for GIT workflows.'
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,252 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
from lib.chatmark import Radio, TextEditor
|
||||
|
||||
|
||||
def _parse_pr_host(pr_url):
|
||||
fields = pr_url.split("/")
|
||||
for field in fields:
|
||||
if field.find(".") > 0:
|
||||
return field
|
||||
return pr_url
|
||||
|
||||
|
||||
def _read_config_value(key):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if key in config_data:
|
||||
return config_data[key]
|
||||
return None
|
||||
|
||||
|
||||
def _save_config_value(key, value):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data[key] = value
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
# 根据PR URL获取不同的仓库管理类型
|
||||
# 支持的类型有:github gitlab bitbucket bitbucket_server azure codecommit gerrit
|
||||
def get_repo_type(url):
|
||||
# 根据URL的特征判断仓库管理类型
|
||||
if "github.com" in url:
|
||||
return "github"
|
||||
elif "gitlab.com" in url or "gitlab" in url:
|
||||
return "gitlab"
|
||||
elif "bitbucket.org" in url:
|
||||
return "bitbucket"
|
||||
elif "bitbucket-server" in url:
|
||||
return "bitbucket_server"
|
||||
elif "dev.azure.com" in url or "visualstudio.com" in url:
|
||||
return "azure"
|
||||
elif "codecommit" in url:
|
||||
return "codecommit"
|
||||
elif "gerrit" in url:
|
||||
return "gerrit"
|
||||
else:
|
||||
pr_host = _parse_pr_host(url)
|
||||
repo_type_map = _read_config_value("repo_type_map")
|
||||
if repo_type_map and pr_host in repo_type_map:
|
||||
return repo_type_map[pr_host]
|
||||
if not repo_type_map:
|
||||
repo_type_map = {}
|
||||
|
||||
radio = Radio(
|
||||
["github", "gitlab", "bitbucket", "bitbucket_server", "azure", "codecommit", "gerrit"],
|
||||
title="Choose the type of your repo:",
|
||||
)
|
||||
radio.render()
|
||||
if radio.selection is None:
|
||||
return None
|
||||
|
||||
rtype = [
|
||||
"github",
|
||||
"gitlab",
|
||||
"bitbucket",
|
||||
"bitbucket_server",
|
||||
"azure",
|
||||
"codecommit",
|
||||
"gerrit",
|
||||
][radio.selection]
|
||||
repo_type_map[pr_host] = rtype
|
||||
_save_config_value("repo_type_map", repo_type_map)
|
||||
return rtype
|
||||
|
||||
|
||||
def read_github_token():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "github_token" in config_data:
|
||||
return config_data["github_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_server_access_token(repo_type):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if repo_type in config_data and "access_token" in config_data[repo_type]:
|
||||
return config_data[repo_type]["access_token"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_gitlab_host():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "gitlab_host" in config_data:
|
||||
return config_data["gitlab_host"]
|
||||
return ""
|
||||
|
||||
|
||||
def read_review_inline_config():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "pr_review_inline" in config_data:
|
||||
return config_data["pr_review_inline"]
|
||||
return False
|
||||
|
||||
|
||||
def save_github_token(github_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["github_token"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def save_gitlab_host(github_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["gitlab_host"] = github_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def save_server_access_token(repo_type, access_token):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
if repo_type not in config_data:
|
||||
config_data[repo_type] = {}
|
||||
config_data[repo_type]["access_token"] = access_token
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_github_token_with_input():
|
||||
github_token = read_github_token()
|
||||
if not github_token:
|
||||
# Input your github TOKEN to access github api:
|
||||
github_token_editor = TextEditor("", "Please input your github TOKEN to access:")
|
||||
github_token = github_token_editor.new_text
|
||||
if not github_token:
|
||||
return github_token
|
||||
save_github_token(github_token)
|
||||
return github_token
|
||||
|
||||
|
||||
def read_server_access_token_with_input(pr_url):
|
||||
repo_type = get_repo_type(pr_url)
|
||||
if not repo_type:
|
||||
return ""
|
||||
|
||||
pr_host = _parse_pr_host(pr_url)
|
||||
if repo_type == "gitlab":
|
||||
# get gitlab host
|
||||
gitlab_host_map = _read_config_value("gitlab_host_map")
|
||||
if gitlab_host_map and pr_host in gitlab_host_map:
|
||||
repo_type = gitlab_host_map[pr_host]
|
||||
else:
|
||||
if not gitlab_host_map:
|
||||
gitlab_host_map = {}
|
||||
gitlab_host_editor = TextEditor(
|
||||
"", "Please input your gitlab host(for example: https://www.gitlab.com):"
|
||||
)
|
||||
gitlab_host_editor.render()
|
||||
gitlab_host = gitlab_host_editor.new_text
|
||||
if not gitlab_host:
|
||||
return ""
|
||||
gitlab_host_map[pr_host] = gitlab_host
|
||||
_save_config_value("gitlab_host_map", gitlab_host_map)
|
||||
repo_type = gitlab_host
|
||||
|
||||
server_access_token = read_server_access_token(repo_type)
|
||||
if not server_access_token:
|
||||
# Input your server access TOKEN to access server api:
|
||||
server_access_token_editor = TextEditor(
|
||||
"", f"Please input your {repo_type} access TOKEN to access:"
|
||||
)
|
||||
server_access_token_editor.render()
|
||||
|
||||
server_access_token = server_access_token_editor.new_text
|
||||
if not server_access_token:
|
||||
return server_access_token
|
||||
save_server_access_token(repo_type, server_access_token)
|
||||
return server_access_token
|
||||
|
||||
|
||||
def get_gitlab_host(pr_url):
|
||||
pr_host = _parse_pr_host(pr_url)
|
||||
gitlab_host_map = _read_config_value("gitlab_host_map")
|
||||
if gitlab_host_map and pr_host in gitlab_host_map:
|
||||
return gitlab_host_map[pr_host]
|
||||
if not gitlab_host_map:
|
||||
gitlab_host_map = {}
|
||||
|
||||
gitlab_host_editor = TextEditor(
|
||||
"https://www.gitlab.com",
|
||||
"Please input your gitlab host(for example: https://www.gitlab.com):",
|
||||
)
|
||||
gitlab_host_editor.render()
|
||||
host = gitlab_host_editor.new_text
|
||||
if host:
|
||||
gitlab_host_map[pr_host] = host
|
||||
_save_config_value("gitlab_host_map", gitlab_host_map)
|
||||
return host
|
||||
|
||||
|
||||
def get_model_max_input(model):
|
||||
config_file = os.path.expanduser("~/.chat/config.yml")
|
||||
try:
|
||||
with open(config_file, "r", encoding="utf-8") as file:
|
||||
yaml_contents = file.read()
|
||||
parsed_yaml = yaml.safe_load(yaml_contents)
|
||||
for model_t in parsed_yaml.get("models", {}):
|
||||
if model_t == model:
|
||||
return parsed_yaml["models"][model_t].get("max_input_tokens", 6000)
|
||||
return 6000
|
||||
except Exception:
|
||||
return 6000
|
@ -1,6 +0,0 @@
|
||||
|
||||
# pr.custom_suggestions
|
||||
**/pr.custom_suggestions命令用于生成PR的代码建议。**
|
||||
|
||||
使用方式为:/pr.improve <PR_URL>, 例如:
|
||||
/pr.improve https://github.com/devchat-ai/devchat/pull/301
|
@ -1,5 +0,0 @@
|
||||
description: "Generate custom suggestions for PR."
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $workflow_python $command_path/../command.py "$input" custom_suggestions
|
@ -1,49 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from lib.chatmark import TextEditor
|
||||
|
||||
|
||||
def read_custom_suggestions():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "custom_suggestions" in config_data:
|
||||
return config_data["custom_suggestions"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_custom_suggestions(custom_suggestions):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["custom_suggestions"] = custom_suggestions
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def config_custom_suggestions_with():
|
||||
custom_suggestions = read_custom_suggestions()
|
||||
if not custom_suggestions:
|
||||
custom_suggestions = "- make sure the code is efficient\n"
|
||||
|
||||
# Input your github TOKEN to access github api:
|
||||
custom_suggestions_editor = TextEditor(
|
||||
custom_suggestions, "Please input your custom suggestions:"
|
||||
)
|
||||
custom_suggestions_editor.render()
|
||||
|
||||
custom_suggestions = custom_suggestions_editor.new_text
|
||||
if not custom_suggestions:
|
||||
return
|
||||
|
||||
save_custom_suggestions(custom_suggestions)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
config_custom_suggestions_with()
|
@ -1,3 +0,0 @@
|
||||
description: "edit custom suggestions"
|
||||
steps:
|
||||
- run: $devchat_python $command_path/command.py
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"sha": "91da857973f5684d0956fa6e623f14edc99adad5504632d6ee4ddac6ae501761",
|
||||
"command_python": "/Users/admin/miniconda3/envs/pr_test2/bin/python"
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
# ruff: noqa: E501
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.chatmark import TextEditor
|
||||
|
||||
|
||||
def read_custom_suggestions():
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
if "custom_suggestions" in config_data:
|
||||
return config_data["custom_suggestions"]
|
||||
return ""
|
||||
|
||||
|
||||
def save_custom_suggestions(custom_suggestions):
|
||||
config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json")
|
||||
|
||||
config_data = {}
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
config_data["custom_suggestions"] = custom_suggestions
|
||||
with open(config_path, "w+", encoding="utf-8") as f:
|
||||
json.dump(config_data, f, indent=4)
|
||||
|
||||
|
||||
def read_custom_suggestions_with_input():
|
||||
custom_suggestions = read_custom_suggestions()
|
||||
if not custom_suggestions:
|
||||
# Input your github TOKEN to access github api:
|
||||
custom_suggestions_editor = TextEditor(
|
||||
"- make sure the code is efficient\n", "Please input your custom suggestions:"
|
||||
)
|
||||
custom_suggestions_editor.render()
|
||||
|
||||
custom_suggestions = custom_suggestions_editor.new_text
|
||||
if not custom_suggestions:
|
||||
return custom_suggestions
|
||||
save_custom_suggestions(custom_suggestions)
|
||||
return custom_suggestions
|
||||
|
||||
|
||||
def get_custom_suggestions_system_prompt():
|
||||
custom_suggestions = read_custom_suggestions_with_input()
|
||||
if not custom_suggestions:
|
||||
print("Command has been canceled.", flush=True)
|
||||
sys.exit(0)
|
||||
|
||||
system_prompt = (
|
||||
"""You are PR-Reviewer, a language model that specializes in suggesting ways to improve for a Pull Request (PR) code.
|
||||
Your task is to provide meaningful and actionable code suggestions, to improve the new code presented in a PR diff.
|
||||
|
||||
|
||||
The format we will use to present the PR code diff:
|
||||
======
|
||||
## file: 'src/file1.py'
|
||||
|
||||
@@ ... @@ def func1():
|
||||
__new hunk__
|
||||
12 code line1 that remained unchanged in the PR
|
||||
13 +new hunk code line2 added in the PR
|
||||
14 code line3 that remained unchanged in the PR
|
||||
__old hunk__
|
||||
code line1 that remained unchanged in the PR
|
||||
-old hunk code line2 that was removed in the PR
|
||||
code line3 that remained unchanged in the PR
|
||||
|
||||
@@ ... @@ def func2():
|
||||
__new hunk__
|
||||
...
|
||||
__old hunk__
|
||||
...
|
||||
|
||||
|
||||
## file: 'src/file2.py'
|
||||
...
|
||||
======
|
||||
- In this format, we separated each hunk of code to '__new hunk__' and '__old hunk__' sections. The '__new hunk__' section contains the new code of the chunk, and the '__old hunk__' section contains the old code that was removed.
|
||||
- Code lines are prefixed symbols ('+', '-', ' '). The '+' symbol indicates new code added in the PR, the '-' symbol indicates code removed in the PR, and the ' ' symbol indicates unchanged code.
|
||||
- We also added line numbers for the '__new hunk__' sections, to help you refer to the code lines in your suggestions. These line numbers are not part of the actual code, and are only used for reference.
|
||||
|
||||
|
||||
Specific instructions for generating code suggestions:
|
||||
- Provide up to {{ num_code_suggestions }} code suggestions. The suggestions should be diverse and insightful.
|
||||
- The suggestions should focus on ways to improve the new code in the PR, meaning focusing on lines from '__new hunk__' sections, starting with '+'. Use the '__old hunk__' sections to understand the context of the code changes.
|
||||
- Prioritize suggestions that address possible issues, major problems, and bugs in the PR code.
|
||||
- Don't suggest to add docstring, type hints, or comments, or to remove unused imports.
|
||||
- Suggestions should not repeat code already present in the '__new hunk__' sections.
|
||||
- Provide the exact line numbers range (inclusive) for each suggestion. Use the line numbers from the '__new hunk__' sections.
|
||||
- When quoting variables or names from the code, use backticks (`) instead of single quote (').
|
||||
- Take into account that you are reviewing a PR code diff, and that the entire codebase is not available for you as context. Hence, avoid suggestions that might conflict with unseen parts of the codebase.
|
||||
|
||||
|
||||
|
||||
Instructions from the user, that should be taken into account with high priority:
|
||||
"""
|
||||
+ custom_suggestions
|
||||
+ """
|
||||
|
||||
|
||||
{%- if extra_instructions %}
|
||||
|
||||
|
||||
Extra instructions from the user, that should be taken into account with high priority:
|
||||
======
|
||||
{{ extra_instructions }}
|
||||
======
|
||||
{%- endif %}
|
||||
|
||||
|
||||
The output must be a YAML object equivalent to type $PRCodeSuggestions, according to the following Pydantic definitions:
|
||||
=====
|
||||
class CodeSuggestion(BaseModel):
|
||||
relevant_file: str = Field(description="the relevant file full path")
|
||||
language: str = Field(description="the code language of the relevant file")
|
||||
suggestion_content: str = Field(description="an actionable suggestion for meaningfully improving the new code introduced in the PR")
|
||||
existing_code: str = Field(description="a short code snippet from a '__new hunk__' section to illustrate the relevant existing code. Don't show the line numbers.")
|
||||
improved_code: str = Field(description="a short code snippet to illustrate the improved code, after applying the suggestion.")
|
||||
one_sentence_summary:str = Field(description="a short summary of the suggestion action, in a single sentence. Focus on the 'what'. Be general, and avoid method or variable names.")
|
||||
relevant_lines_start: int = Field(description="The relevant line number, from a '__new hunk__' section, where the suggestion starts (inclusive). Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above")
|
||||
relevant_lines_end: int = Field(description="The relevant line number, from a '__new hunk__' section, where the suggestion ends (inclusive). Should be derived from the hunk line numbers, and correspond to the 'existing code' snippet above")
|
||||
label: str = Field(description="a single label for the suggestion, to help the user understand the suggestion type. For example: 'security', 'possible bug', 'possible issue', 'performance', 'enhancement', 'best practice', 'maintainability', etc. Other labels are also allowed")
|
||||
|
||||
class PRCodeSuggestions(BaseModel):
|
||||
code_suggestions: List[CodeSuggestion]
|
||||
=====
|
||||
|
||||
|
||||
Example output:
|
||||
```yaml
|
||||
code_suggestions:
|
||||
- relevant_file: |
|
||||
src/file1.py
|
||||
language: |
|
||||
python
|
||||
suggestion_content: |
|
||||
...
|
||||
existing_code: |
|
||||
...
|
||||
improved_code: |
|
||||
...
|
||||
one_sentence_summary: |
|
||||
...
|
||||
relevant_lines_start: 12
|
||||
relevant_lines_end: 13
|
||||
label: |
|
||||
...
|
||||
```
|
||||
|
||||
|
||||
Each YAML output MUST be after a newline, indented, with block scalar indicator ('|').
|
||||
"""
|
||||
)
|
||||
return system_prompt
|
@ -1,6 +0,0 @@
|
||||
|
||||
# pr.describe
|
||||
**/pr.describe命令用于生成PR描述。**
|
||||
|
||||
使用方式为:/pr.describe <PR_URL>, 例如:
|
||||
/pr.describe https://github.com/devchat-ai/devchat/pull/301
|
@ -1,5 +0,0 @@
|
||||
description: "Describe PR."
|
||||
input: required
|
||||
help: README.md
|
||||
steps:
|
||||
- run: $workflow_python $command_path/../command.py "$input" describe
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user