Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto learn from history #129

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -618,3 +618,4 @@ healthchecksdb
MigrationBackup/

# End of https://www.gitignore.io/api/osx,python,pycharm,windows,visualstudio,visualstudiocode
.gcop/
44 changes: 40 additions & 4 deletions gcop/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
from gcop import prompt, version
from gcop.config import ModelConfig, get_config
from gcop.utils import check_version_update, migrate_config_if_needed
from gcop.utils.init_config import (
INIT_CONFIG_COMMAND,
InitConfigCommand,
get_local_config,
)
from gcop.utils.logger import Color, logger

load_dotenv()
Expand Down Expand Up @@ -55,8 +60,29 @@ def get_git_diff(diff_type: Literal["--staged", "--cached"]) -> str:
raise ValueError(f"Error getting git diff: {e}")


def get_git_history(log_type: Literal["--oneline", "--stat"]) -> str:
"""Get git history

Args:
log_type(str): log type, --oneline or --stat

Returns:
str: git history
"""
if not get_local_config().historyLearning:
return ""
try:
result = subprocess.check_output(
["git", "log", log_type], text=True, encoding="utf-8"
)
return result
except subprocess.CalledProcessError as e:
raise ValueError(f"Error getting git history: {e}")


def generate_commit_message(
diff: str,
commit_message_history: Optional[str] = None,
instruction: Optional[str] = None,
previous_commit_message: Optional[str] = None,
) -> CommitMessage:
Expand All @@ -73,14 +99,14 @@ def generate_commit_message(
str: git commit message with ai generated.
"""
gcop_config = get_config()

commit_template = get_local_config().gcoprule or gcop_config.commit_template
instruction: str = prompt.get_commit_instrcution(
diff=diff,
commit_template=gcop_config.commit_template,
commmit_message_history=commit_message_history,
commit_template=commit_template,
instruction=instruction,
previous_commit_message=previous_commit_message,
)

model_config: ModelConfig = gcop_config.model_config
return pne.chat(
messages=instruction,
Expand Down Expand Up @@ -453,6 +479,7 @@ def commit_command(
process, please select "exit".
"""
diff: str = get_git_diff("--staged")
commit_message_history: str = get_git_history("--staged")

if not diff:
logger.color_info("No staged changes", color=Color.YELLOW)
Expand All @@ -462,7 +489,7 @@ def commit_command(
logger.color_info("[On Ready] Generating commit message...")

commit_messages: CommitMessage = generate_commit_message(
diff, instruction, previous_commit_message
diff, commit_message_history, instruction, previous_commit_message
)

logger.color_info(f"[Thought] {commit_messages.thought}")
Expand Down Expand Up @@ -492,6 +519,15 @@ def commit_command(
actions[response]()


@app.command(name=INIT_CONFIG_COMMAND)
def init_project_command():
"""Initialize gcop config"""
logger.color_info("Initializing gcop config...")
command = InitConfigCommand()
if command.handle():
logger.color_info("Gcop config initialized successfully.")


@app.command(name="help")
@check_version_before_command
def help_command():
Expand Down
4 changes: 3 additions & 1 deletion gcop/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@

def get_commit_instrcution(
diff: str,
commmit_message_history: Optional[str] = None,
commit_template: Optional[str] = None,
instruction: Optional[str] = None,
previous_commit_message: Optional[str] = None,
Expand Down Expand Up @@ -115,5 +116,6 @@ def get_commit_instrcution(

if instruction:
_ += f"<user_feedback>{instruction}</user_feedback>"

if commmit_message_history:
_ += f"<commit_message_history>{commmit_message_history}</commit_message_history>" # noqa: E501
return _
76 changes: 76 additions & 0 deletions gcop/utils/init_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from dataclasses import dataclass
from pathlib import Path
from typing import Dict

from gcop.utils import Color, logger, read_yaml

INIT_CONFIG_COMMAND = "init_project"


@dataclass
class LocalConfig:
gcoprule: str
historyLearning: bool = False

@classmethod
def from_yaml(cls) -> "LocalConfig":
config: dict = read_yaml(InitConfigCommand().config_file_path)
return cls(**config)


class ConfigFileHandleMixin:
def __init__(self) -> None:
self.project_path = Path.cwd()
self.config_folder_path = self.project_path / ".gcop"
self.config_file_path = self.config_folder_path / "config.yaml"

def _init_project(self):
try:
self.config_folder_path.mkdir(parents=True, exist_ok=True)
self.config_file_path.touch(exist_ok=True)
except (OSError, FileNotFoundError) as e:
logger.color_info(f"File create error: {e}", color=Color.RED)

def _check_config_exist(self):
return self.config_file_path.exists()


class InitConfigCommand(ConfigFileHandleMixin):
def __init__(self) -> None:
super().__init__()

def handle(self):
if self._check_config_exist():
logger.color_info(
"""The configuration file already exists, so there is no need to \
initialize again.""",
color=Color.DEFAULT,
)
return False
self._init_project()
return True


def check_config_exist(func):
def wrapper(*args, **kwargs):
command = InitConfigCommand()
if command._check_config_exist:
func(*args, **kwargs)
else:
logger.color_info(
f"""The configuration file does not exist.\
Please first initialize the project using\
the command '{INIT_CONFIG_COMMAND}'.""",
color=Color.RED,
)

return wrapper


def get_local_config() -> "LocalConfig":
config = LocalConfig.from_yaml()
return config


if __name__ == "__main__":
print(get_local_config())
12 changes: 12 additions & 0 deletions tests/test_init_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pytest

from gcop.utils.init_config import InitConfigCommand, get_local_config

local_conifig_file_path = InitConfigCommand().config_file_path
local_config_exits = local_conifig_file_path.exists()


@pytest.mark.skipif(not local_config_exits, reason="local config file not exits")
def test_get_local_config():
local_config = get_local_config()
assert local_config is not None
13 changes: 13 additions & 0 deletions tests/test_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,35 @@ def test_get_commit_instruction_with_instruction():
assert "test diff" in result


def test_get_commit_instruction_with_history():
"""Test commit instruction with commit message history."""
diff = "test diff"
history = "commit message history"
result = get_commit_instrcution(diff, commmit_message_history=history)
assert isinstance(result, str)
assert "commit message history" in result
assert "test diff" in result


def test_get_commit_instruction_all_params():
"""Test commit instruction with all parameters."""
diff = "test diff"
template = "<example>template</example>"
prev_msg = "previous message"
instruction = "make it better"
commit_message_history = "commit message history"

result = get_commit_instrcution(
diff,
commmit_message_history=commit_message_history,
commit_template=template,
previous_commit_message=prev_msg,
instruction=instruction,
)

assert isinstance(result, str)
assert "test diff" in result
assert "commit message history"
assert "template" in result
assert "previous message" in result
assert "make it better" in result
Loading