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

Add project baseconfig #147

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
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/
28 changes: 28 additions & 0 deletions docs/guide/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,14 @@ Display detailed information about the current git repository. This command prov
- Version control information (latest tag, branch count, untracked files)
- Advanced details (submodules, latest merge commit, file type statistics)

### `gcop init-project`

Initialize the GCOP configuration in the current project. This command creates the `.gcop/config.yaml` file in the project root directory, which contains the default GCOP configuration.

### `gcop show-config`

The current GCOP configuration is displayed. This command outputs all GCOP configuration items currently in effect, including model configuration, submit templates, and so on.

## Usage Examples

1. Generate and apply an AI commit message:
Expand Down Expand Up @@ -131,4 +139,24 @@ Some advanced features like line count by language require additional tools (e.g
The `git amend` command modifies Git history. Use it with caution, especially if you've already pushed the commit you're amending to a shared repository.
:::

7. To initialize the GCOP configuration in the project:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我理解这个不算第七点,算一个新的 example case。


```
gcop init-project
```

This will create the `.gcop/config.yaml `file in the project root. If the configuration file already exists, the command prompts that it has been initialized.

8. View the current GCOP configuration

```
gcop show-config
```

This displays all currently active GCOP configuration items and is useful for debugging and validating configurations.

::: tip
The project level configuration (.gcop/config.yaml) overrides the global configuration. This allows you to set different configurations for different projects.
:::

For more detailed information on each command, refer to the [Quick Start](/guide/quick-start.md) section in the guide.
9 changes: 5 additions & 4 deletions docs/guide/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Before you begin, ensure you have the following:
How to config your model? Please refer to [How to Config Model](/other/how-to-config-model)

The `config.yaml` file will be stored in:

- Windows: `%USERPROFILE%\.zeeland\gcop\config.yaml`
- Linux: `~/.zeeland/gcop/config.yaml`
- MacOS: `~/.zeeland/gcop/config.yaml`
Expand Down Expand Up @@ -101,11 +102,11 @@ Finally, you can see the commit message like this:

```bash
(gcop) D:\Projects\gcop\docs>git ac
[Code diff]
[Code diff]
...


[Thought] The changes involve updating the VitePress configuration to use an environment variable for the website ID, adding a reference to a new documentation page in the quick-start guide, and correcting a URL in the model configuration documentation. These changes are primarily
[Thought] The changes involve updating the VitePress configuration to use an environment variable for the website ID, adding a reference to a new documentation page in the quick-start guide, and correcting a URL in the model configuration documentation. These changes are primarily
related to documentation and configuration updates.
[Generated commit message]
docs: update VitePress config and add model config reference
Expand Down Expand Up @@ -172,5 +173,5 @@ For more detailed information on each command, refer to the [Commands](./command

- Visit our [How to guide](/guide/how-to-guide) for common questions and troubleshooting
- Check out the [How to Config Model](/other/how-to-config-model) guide for advanced configuration options

Start enhancing your Git workflow with GCOP today!
- How to [setting different configuration options for different projects](/other/config-your-project-config)
Start enhancing your Git workflow with GCOP today!
65 changes: 65 additions & 0 deletions docs/other/config-your-project-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Configure Your Project
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以改成 Project based configuration


GCOP supports project-level configuration, allowing you to set different configuration options for different projects. This guide will help you understand how to configure your project.

## Initialize Project Configuration

To initialize GCOP configuration in your project, simply run in the project root directory:

```bash
gcop init-project
```

This command creates a `.gcop/config.yaml` file in your project root directory. If the configuration file already exists, the command will indicate that it has been initialized.

## Configuration File Structure

The default content of the project configuration file `.gcop/config.yaml` is as follows:

```yaml
commit_template: null # Commit message template
enable_data_improvement: false # Whether to enable data improvement
include_git_history: false # Whether to include git history in the prompt
model:
api_base: eg:https://api.openai.com/v1
api_key: sk-xxx
model_name: provider/name,eg openai/gpt-4o
```

## Configuration Priority

GCOP's configuration priority is as follows (from highest to lowest):

1. Project-level configuration (`.gcop/config.yaml`)
2. Global configuration (`~/.gcop/config.yaml`)

This means that project-level configuration will override global configuration, allowing you to set different configurations for different projects.

## View Current Configuration

To view the current effective configuration, use:

```bash
gcop show-config
```

This command displays all currently effective GCOP configuration items, including model configuration, commit templates, etc.

## Important Notes

1. Make sure to add `.gcop/` to your `.gitignore` file to avoid committing sensitive information like API keys to version control.

2. It's recommended to use environment variables for storing API keys rather than writing them directly in the configuration file.

3. Changes to the project configuration file take effect immediately, no need to restart GCOP.

## Troubleshooting

If you encounter configuration-related issues, you can:

1. Use `gcop show-config` to check the current configuration
2. Ensure the configuration file format is correct (valid YAML format)
3. Verify that the API key and model name are correct
4. Check file permissions

For more help, refer to the [Configuration Guide](/guide/configuration) or submit a GitHub issue.
60 changes: 55 additions & 5 deletions gcop/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
import questionary
import requests
import typer
import yaml
from dotenv import load_dotenv
from pydantic import BaseModel, Field

from gcop import prompt, version
from gcop.config import ModelConfig, get_config
from gcop.config import EXAMPLE_CONFIG, ModelConfig, get_config
from gcop.utils import check_version_update, migrate_config_if_needed
from gcop.utils.logger import Color, logger

Expand Down Expand Up @@ -55,8 +56,27 @@ 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
"""
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 +93,14 @@ def generate_commit_message(
str: git commit message with ai generated.
"""
gcop_config = get_config()

commit_template = 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 +473,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 +483,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 +513,35 @@ def commit_command(
actions[response]()


@app.command(name="init-project")
@check_version_before_command
def init_project_command():
"""Initialize gcop config"""
project_path = Path.cwd()
config_folder_path = project_path / ".gcop" / "config.yaml"
if config_folder_path.exists():
logger.color_info(
"Gcop config already exists in the current project.", color=Color.YELLOW
)
return
try:
config_folder_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_folder_path, "w") as f:
yaml.dump(EXAMPLE_CONFIG, f, default_flow_style=False)
logger.color_info("Gcop config initialized successfully.")
except Exception as e:
logger.color_info(f"Failed to initialize gcop config: {e}", color=Color.RED)
return


@app.command(name="show-config")
@check_version_before_command
def show_config_command():
"""command to show the current gcop config"""
config = get_config()
logger.color_info(f"Current gcop config: {config}")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个输出要格式化一下



@app.command(name="help")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

help 需要加上对应的命令

@check_version_before_command
def help_command():
Expand Down
60 changes: 58 additions & 2 deletions gcop/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from dataclasses import dataclass
from typing import Optional
from pathlib import Path
from typing import Dict, Optional

from zeeland import Singleton

Expand Down Expand Up @@ -124,10 +125,65 @@ def from_yaml(cls, config_path: Optional[str] = None) -> "GcopConfig":
def model_config(self) -> ModelConfig:
return self.model

@staticmethod
def get_example_config() -> Dict:
return {
"model": {
"model_name": "provider/name,eg openai/gpt-4o",
"api_key": "sk-xxx",
"api_base": "eg:https://api.openai.com/v1",
},
"commit_template": None,
"include_git_history": False,
"enable_data_improvement": False,
}


EXAMPLE_CONFIG = GcopConfig.get_example_config()


def check_model_config(new_model: Dict) -> bool:
"""
check if the new model config is valid

Args:
new_model: the model config of project config by reading ~/.gcop/config.yaml

Returns:
True if the new model config is valid, False otherwise
"""
example_model_config = EXAMPLE_CONFIG["model"]

if not new_model:
return False

for key in example_model_config:
if (
(key not in new_model)
or (not new_model.get(key))
or (new_model[key] == example_model_config[key])
):
return False
return True


def get_config() -> GcopConfig:
"""Get the global config instance, loading it if necessary."""
"""Get the config instance, loading it if necessary.
If an attribute is defined in the local configuration,
the original value will be overwritten.
"""
if not hasattr(get_config, "_instance"):
get_config._instance = GcopConfig.from_yaml()

project_config_path = Path.cwd() / ".gcop" / "config.yaml"
if project_config_path.exists():
project_config = read_yaml(project_config_path)
for k, v in project_config.items():
if isinstance(v, str) and not v.strip():
continue
if hasattr(get_config._instance, k):
if k == "model" and isinstance(v, dict) and check_model_config(v):
setattr(get_config._instance, k, ModelConfig(**v))
elif k != "model" and k in EXAMPLE_CONFIG.keys():
setattr(get_config._instance, k, v)
return get_config._instance
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 _
14 changes: 14 additions & 0 deletions tests/test_get_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from pathlib import Path

import pytest

from gcop.config import GcopConfig, get_config


def test_get_config():
"""test get_config function"""
project_config_path = Path.cwd() / ".gcop" / "config.yaml"
if not project_config_path.exists():
pytest.skip("Configuration file does not exist. Skip the test")
config = get_config()
assert isinstance(config, GcopConfig)
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